From d48c5e7c30b1e942c4f782bcc09824731b1c2e55 Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Mon, 07 Nov 2022 11:38:07 +0000 Subject: [PATCH] Merge branch 'develop' into feature/ci-refactoring --- --- a/.docker/forkless-config/launch-config-forkless-data.j2 +++ b/.docker/forkless-config/launch-config-forkless-data.j2 @@ -107,7 +107,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace" + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug" ] }, { @@ -119,7 +119,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace" + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug" ] } ] --- a/.docker/forkless-config/launch-config-forkless-nodata.j2 +++ b/.docker/forkless-config/launch-config-forkless-nodata.j2 @@ -101,7 +101,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace", + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--ws-max-connections=1000" ] }, @@ -114,7 +114,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace", + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--ws-max-connections=1000" ] } --- a/.docker/forkless-config/launch-config-node-update-only-v3.j2 +++ b/.docker/forkless-config/launch-config-node-update-only-v3.j2 @@ -101,7 +101,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace", + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--ws-max-connections=1000" ] }, @@ -114,7 +114,7 @@ "--rpc-cors=all", "--unsafe-rpc-external", "--unsafe-ws-external", - "-lxcm=trace", + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--ws-max-connections=1000" ] } --- a/.docker/xcm-config/launch-config-xcm-opal.json +++ b/.docker/xcm-config/launch-config-xcm-opal.json @@ -91,6 +91,7 @@ "name": "alice", "flags": [ "--unsafe-rpc-external", + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--unsafe-ws-external" ] } --- a/.docker/xcm-config/launch-config-xcm-quartz.json +++ b/.docker/xcm-config/launch-config-xcm-quartz.json @@ -90,6 +90,7 @@ "rpcPort": 9933, "name": "alice", "flags": [ + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--unsafe-rpc-external", "--unsafe-ws-external" ] --- a/.docker/xcm-config/launch-config-xcm-unique.json +++ b/.docker/xcm-config/launch-config-xcm-unique.json @@ -90,6 +90,7 @@ "rpcPort": 9933, "name": "alice", "flags": [ + "-lxcm=trace,parity_ws::handler=debug,jsonrpsee_core=trace,jsonrpsee-core=trace,jsonrpsee_ws_server=debug", "--unsafe-rpc-external", "--unsafe-ws-external" ] --- a/.maintain/scripts/compile_stub.sh +++ b/.maintain/scripts/compile_stub.sh @@ -6,6 +6,7 @@ tmp=$(mktemp -d) cd $tmp cp $dir/$INPUT input.sol +echo "Tmp file: $tmp/input.sol" solcjs --optimize --bin input.sol -o $PWD mv input_sol_$(basename $OUTPUT .raw).bin out.bin --- a/.maintain/scripts/generate_sol.sh +++ b/.maintain/scripts/generate_sol.sh @@ -4,7 +4,7 @@ PRETTIER_CONFIG="$(pwd)""/.prettierrc" tmp=$(mktemp) -cargo test --package $PACKAGE -- $NAME --exact --nocapture --ignored | tee $tmp +cargo test --package=$PACKAGE --features=stubgen -- $NAME --exact --nocapture --ignored | tee $tmp raw=$(mktemp --suffix .sol) sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw --- a/Cargo.lock +++ b/Cargo.lock @@ -252,11 +252,12 @@ [[package]] name = "async-lock" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] @@ -977,9 +978,9 @@ [[package]] name = "clap" -version = "3.2.22" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -1871,9 +1872,9 @@ [[package]] name = "cxx" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" dependencies = [ "cc", "cxxbridge-flags", @@ -1883,9 +1884,9 @@ [[package]] name = "cxx-build" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" dependencies = [ "cc", "codespan-reporting", @@ -1898,15 +1899,15 @@ [[package]] name = "cxxbridge-flags" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" [[package]] name = "cxxbridge-macro" -version = "1.0.78" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" dependencies = [ "proc-macro2", "quote", @@ -2128,15 +2129,15 @@ [[package]] name = "ed25519-zebra" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek 3.2.0", + "hashbrown", "hex", "rand_core 0.6.4", "sha2 0.9.9", - "thiserror", "zeroize", ] @@ -2331,7 +2332,7 @@ [[package]] name = "evm-coder" -version = "0.1.3" +version = "0.1.4" dependencies = [ "concat-idents", "ethereum", @@ -2341,14 +2342,17 @@ "hex", "hex-literal", "impl-trait-for-tuples", + "pallet-evm", "primitive-types", + "sha3-const", "similar-asserts", "sp-std", + "trybuild", ] [[package]] name = "evm-coder-procedural" -version = "0.2.0" +version = "0.2.1" dependencies = [ "Inflector", "hex", @@ -3336,9 +3340,9 @@ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -3561,9 +3565,9 @@ [[package]] name = "iana-time-zone-haiku" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", @@ -3702,9 +3706,9 @@ [[package]] name = "io-lifetimes" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +checksum = "e6e481ccbe3dea62107216d0d1138bb8ad8e5e5c43009a098bd1990272c497b0" [[package]] name = "ip_network" @@ -4093,9 +4097,9 @@ [[package]] name = "libc" -version = "0.2.135" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" @@ -4967,14 +4971,14 @@ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -5826,7 +5830,7 @@ [[package]] name = "pallet-common" -version = "0.1.9" +version = "0.1.10" dependencies = [ "ethereum", "evm-coder", @@ -6116,7 +6120,7 @@ [[package]] name = "pallet-fungible" -version = "0.1.5" +version = "0.1.6" dependencies = [ "ethereum", "evm-coder", @@ -6359,7 +6363,7 @@ [[package]] name = "pallet-nonfungible" -version = "0.1.6" +version = "0.1.7" dependencies = [ "ethereum", "evm-coder", @@ -6481,7 +6485,7 @@ [[package]] name = "pallet-refungible" -version = "0.2.5" +version = "0.2.6" dependencies = [ "derivative", "ethereum", @@ -7248,9 +7252,9 @@ [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platforms" @@ -8501,9 +8505,9 @@ [[package]] name = "polling" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -9408,9 +9412,9 @@ [[package]] name = "rustix" -version = "0.35.11" +version = "0.35.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb2fda4666def1433b1b05431ab402e42a1084285477222b72d6c564c417cef" +checksum = "985947f9b6423159c4726323f373be0a21bdb514c5af06a849cb3d2dce2d01e8" dependencies = [ "bitflags", "errno", @@ -10683,9 +10687,9 @@ [[package]] name = "secp256k1" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" +checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" dependencies = [ "secp256k1-sys", ] @@ -10766,18 +10770,18 @@ [[package]] name = "serde" -version = "1.0.146" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df50b7a60a0ad48e1b42eb38373eac8ff785d619fb14db917b4e63d5439361f" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.146" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a714fd32ba1d66047ce7d53dabd809e9922d538f9047de13cc4cffca47b36205" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -10786,9 +10790,9 @@ [[package]] name = "serde_json" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -10875,6 +10879,12 @@ ] [[package]] +name = "sha3-const" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04774de876479a8f712e787f8271b14712971329a4be66c6dff144db7cfc343" + +[[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12067,9 +12077,9 @@ [[package]] name = "syn" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -12198,9 +12208,9 @@ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -12643,6 +12653,21 @@ ] [[package]] +name = "trybuild" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d" +dependencies = [ + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] + +[[package]] name = "tt-call" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" --- a/crates/evm-coder/CHANGELOG.md +++ b/crates/evm-coder/CHANGELOG.md @@ -2,13 +2,17 @@ All notable changes to this project will be documented in this file. -## [0.1.3] - 2022-08-29 + +## [v0.1.4] - 2022-11-02 +### Added + - Named structures support. + +## [v0.1.3] - 2022-08-29 ### Fixed - Parsing simple values. - ## [v0.1.2] 2022-08-19 ### Added --- a/crates/evm-coder/Cargo.toml +++ b/crates/evm-coder/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "evm-coder" -version = "0.1.3" +version = "0.1.4" license = "GPLv3" edition = "2021" [dependencies] +sha3-const = { version = "0.1.1", default-features = false } # evm-coder reexports those proc-macro evm-coder-procedural = { path = "./procedural" } # Evm uses primitive-types for H160, H256 and others @@ -18,12 +19,15 @@ # We have tuple-heavy code in solidity.rs impl-trait-for-tuples = "0.2.2" +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.30" } + [dev-dependencies] # We want to assert some large binary blobs equality in tests hex = "0.4.3" hex-literal = "0.3.4" similar-asserts = "1.4.2" concat-idents = "1.1.3" +trybuild = "1.0" [features] default = ["std"] @@ -33,3 +37,5 @@ "evm-core/std", "frame-support/std", ] +# Stub/interface generation +stubgen = [] --- a/crates/evm-coder/procedural/Cargo.toml +++ b/crates/evm-coder/procedural/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-coder-procedural" -version = "0.2.0" +version = "0.2.1" license = "GPLv3" edition = "2021" --- a/crates/evm-coder/procedural/src/lib.rs +++ b/crates/evm-coder/procedural/src/lib.rs @@ -107,7 +107,7 @@ if path.segments.len() != 1 { return Err(syn::Error::new( path.span(), - "expected path to have only segment", + "expected path to have only one segment", )); } let last_segment = &path.segments.last().unwrap(); --- a/crates/evm-coder/procedural/src/solidity_interface.rs +++ b/crates/evm-coder/procedural/src/solidity_interface.rs @@ -20,20 +20,20 @@ // about Procedural Macros in Rust book: // https://doc.rust-lang.org/reference/procedural-macros.html -use quote::{quote, ToTokens}; +use proc_macro2::TokenStream; +use quote::{quote, format_ident}; use inflector::cases; -use std::fmt::Write; use syn::{ - Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta, - MetaNameValue, PatType, PathArguments, ReturnType, Type, + Expr, FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta, MetaNameValue, + PatType, ReturnType, Type, spanned::Spanned, parse::{Parse, ParseStream}, parenthesized, Token, LitInt, LitStr, }; use crate::{ - fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment, - parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal, + parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment, parse_result_ok, + pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal, snake_ident_to_screaming, }; @@ -328,155 +328,47 @@ } } -enum AbiType { - // type - Plain(Ident), - // (type1,type2) - Tuple(Vec), - // type[] - Vec(Box), - // type[20] - Array(Box, usize), +trait AbiType { + fn plain(&self) -> syn::Result<&Ident>; + fn is_value(&self) -> bool; + fn is_caller(&self) -> bool; + fn is_special(&self) -> bool; } -impl AbiType { - fn try_from(value: &Type) -> syn::Result { - let value = Self::try_maybe_special_from(value)?; - if value.is_special() { - return Err(syn::Error::new(value.span(), "unexpected special type")); + +impl AbiType for Type { + fn plain(&self) -> syn::Result<&Ident> { + let path = parse_path(self)?; + let segment = parse_path_segment(path)?; + if !segment.arguments.is_empty() { + return Err(syn::Error::new(self.span(), "Not plain type")); } - Ok(value) + Ok(&segment.ident) } - fn try_maybe_special_from(value: &Type) -> syn::Result { - match value { - Type::Array(arr) => { - let wrapped = AbiType::try_from(&arr.elem)?; - match &arr.len { - Expr::Lit(l) => match &l.lit { - Lit::Int(i) => { - let num = i.base10_parse::()?; - Ok(AbiType::Array(Box::new(wrapped), num as usize)) - } - _ => Err(syn::Error::new(arr.len.span(), "should be int literal")), - }, - _ => Err(syn::Error::new(arr.len.span(), "should be literal")), - } - } - Type::Path(_) => { - let path = parse_path(value)?; - let segment = parse_path_segment(path)?; - if segment.ident == "Vec" { - let args = match &segment.arguments { - PathArguments::AngleBracketed(e) => e, - _ => { - return Err(syn::Error::new( - segment.arguments.span(), - "missing Vec generic", - )) - } - }; - let args = &args.args; - if args.len() != 1 { - return Err(syn::Error::new( - args.span(), - "expected only one generic for vec", - )); - } - let arg = args.first().expect("first arg"); - - let ty = match arg { - GenericArgument::Type(ty) => ty, - _ => { - return Err(syn::Error::new( - arg.span(), - "expected first generic to be type", - )) - } - }; - let wrapped = AbiType::try_from(ty)?; - Ok(Self::Vec(Box::new(wrapped))) - } else { - if !segment.arguments.is_empty() { - return Err(syn::Error::new( - segment.arguments.span(), - "unexpected generic arguments for non-vec type", - )); - } - Ok(Self::Plain(segment.ident.clone())) - } - } - Type::Tuple(t) => { - let mut out = Vec::with_capacity(t.elems.len()); - for el in t.elems.iter() { - out.push(AbiType::try_from(el)?) - } - Ok(Self::Tuple(out)) - } - _ => Err(syn::Error::new( - value.span(), - "unexpected type, only arrays, plain types and tuples are supported", - )), - } - } fn is_value(&self) -> bool { - matches!(self, Self::Plain(v) if v == "value") + if let Ok(ident) = self.plain() { + return ident == "value"; + } + false } + fn is_caller(&self) -> bool { - matches!(self, Self::Plain(v) if v == "caller") + if let Ok(ident) = self.plain() { + return ident == "caller"; + } + false } + fn is_special(&self) -> bool { self.is_caller() || self.is_value() } - fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result { - match self { - AbiType::Plain(t) => { - write!(buf, "{}", t) - } - AbiType::Tuple(t) => { - write!(buf, "(")?; - for (i, t) in t.iter().enumerate() { - if i != 0 { - write!(buf, ",")?; - } - t.selector_ty_buf(buf)?; - } - write!(buf, ")") - } - AbiType::Vec(v) => { - v.selector_ty_buf(buf)?; - write!(buf, "[]") - } - AbiType::Array(v, len) => { - v.selector_ty_buf(buf)?; - write!(buf, "[{}]", len) - } - } - } - fn selector_ty(&self) -> String { - let mut out = String::new(); - self.selector_ty_buf(&mut out).expect("no fmt error"); - out - } } -impl ToTokens for AbiType { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - match self { - AbiType::Plain(t) => tokens.extend(quote! {#t}), - AbiType::Tuple(t) => { - tokens.extend(quote! {( - #(#t),* - )}); - } - AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}), - AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}), - } - } -} +#[derive(Debug)] struct MethodArg { name: Ident, camel_name: String, - ty: AbiType, + ty: Type, } impl MethodArg { fn try_from(value: &PatType) -> syn::Result { @@ -484,7 +376,7 @@ Ok(Self { camel_name: cases::camelcase::to_camel_case(&name.to_string()), name, - ty: AbiType::try_maybe_special_from(&value.ty)?, + ty: value.ty.as_ref().clone(), }) } fn is_value(&self) -> bool { @@ -496,10 +388,6 @@ fn is_special(&self) -> bool { self.ty.is_special() } - fn selector_ty(&self) -> String { - assert!(!self.is_special()); - self.ty.selector_ty() - } fn expand_call_def(&self) -> proc_macro2::TokenStream { assert!(!self.is_special()); @@ -514,8 +402,9 @@ fn expand_parse(&self) -> proc_macro2::TokenStream { assert!(!self.is_special()); let name = &self.name; + let ty = &self.ty; quote! { - #name: reader.abi_read()? + #name: <#ty>::abi_read(reader)? } } @@ -575,8 +464,6 @@ camel_name: String, pascal_name: Ident, screaming_name: Ident, - selector_str: String, - selector: u32, hide: bool, args: Vec, has_normal_args: bool, @@ -667,27 +554,14 @@ let camel_name = info .rename_selector .unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string())); - let mut selector_str = camel_name.clone(); - selector_str.push('('); - let mut has_normal_args = false; - for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() { - if i != 0 { - selector_str.push(','); - } - write!(selector_str, "{}", arg.selector_ty()).unwrap(); - has_normal_args = true; - } + let has_normal_args = args.iter().filter(|arg| !arg.is_special()).count() != 0; let has_value_args = args.iter().any(|a| a.is_value()); - selector_str.push(')'); - let selector = fn_selector_str(&selector_str); Ok(Self { name: ident.clone(), camel_name, pascal_name: snake_ident_to_pascal(ident), screaming_name: snake_ident_to_screaming(ident), - selector_str, - selector, hide: info.hide, args, has_normal_args, @@ -728,11 +602,20 @@ fn expand_const(&self) -> proc_macro2::TokenStream { let screaming_name = &self.screaming_name; - let selector = u32::to_be_bytes(self.selector); - let selector_str = &self.selector_str; + let screaming_name_signature = format_ident!("{}_SIGNATURE", &self.screaming_name); + let custom_signature = self.expand_custom_signature(); quote! { - #[doc = #selector_str] - const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*]; + const #screaming_name_signature: ::evm_coder::custom_signature::SignatureUnit = #custom_signature; + const #screaming_name: ::evm_coder::types::bytes4 = { + let mut sum = ::evm_coder::sha3_const::Keccak256::new(); + let mut pos = 0; + while pos < Self::#screaming_name_signature.len { + sum = sum.update(&[Self::#screaming_name_signature.data[pos]; 1]); + pos += 1; + } + let a = sum.finalize(); + [a[0], a[1], a[2], a[3]] + }; } } @@ -836,6 +719,26 @@ } } + fn expand_custom_signature(&self) -> proc_macro2::TokenStream { + let mut args = TokenStream::new(); + + let mut has_params = false; + for arg in self.args.iter().filter(|a| !a.is_special()) { + has_params = true; + let ty = &arg.ty; + args.extend(quote! {nameof(<#ty>::SIGNATURE)}); + args.extend(quote! {fixed(",")}) + } + + // Remove trailing comma + if has_params { + args.extend(quote! {shift_left(1)}) + } + + let func_name = self.camel_name.clone(); + quote! { ::evm_coder::make_signature!(new fixed(#func_name) fixed("(") #args fixed(")")) } + } + fn expand_solidity_function(&self) -> proc_macro2::TokenStream { let camel_name = &self.camel_name; let mutability = match self.mutability { @@ -851,16 +754,17 @@ .filter(|a| !a.is_special()) .map(MethodArg::expand_solidity_argument); let docs = &self.docs; - let selector_str = &self.selector_str; - let selector = self.selector; + let screaming_name = &self.screaming_name; let hide = self.hide; + let custom_signature = self.expand_custom_signature(); let is_payable = self.has_value_args; + quote! { SolidityFunction { docs: &[#(#docs),*], - selector_str: #selector_str, - selector: #selector, hide: #hide, + selector: u32::from_be_bytes(Self::#screaming_name), + custom_signature: #custom_signature, name: #camel_name, mutability: #mutability, is_payable: #is_payable, @@ -1027,30 +931,6 @@ let docs = &self.docs; - if let Some(expect_selector) = &self.info.expect_selector { - if !self.info.inline_is.0.is_empty() { - return syn::Error::new( - name.span(), - "expect_selector is not compatible with inline_is", - ) - .to_compile_error(); - } - let selector = self - .methods - .iter() - .map(|m| m.selector) - .fold(0, |a, b| a ^ b); - - if *expect_selector != selector { - let mut methods = String::new(); - for meth in self.methods.iter() { - write!(methods, "\n- {}", meth.selector_str).expect("write to string"); - } - return syn::Error::new(name.span(), format!("expected selector mismatch, expected {expect_selector:0>8x}, but implementation has {selector:0>8x}{methods}")).to_compile_error(); - } - } - // let methods = self.methods.iter().map(Method::solidity_def); - quote! { #[derive(Debug)] #(#[doc = #docs])* @@ -1076,6 +956,7 @@ u32::to_be_bytes(interface_id) } /// Generate solidity definitions for methods described in this interface + #[cfg(feature = "stubgen")] pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) { use evm_coder::solidity::*; use core::fmt::Write; --- a/crates/evm-coder/procedural/src/to_log.rs +++ b/crates/evm-coder/procedural/src/to_log.rs @@ -194,7 +194,9 @@ #( #consts )* + /// Generate solidity definitions for methods described in this interface + #[cfg(feature = "stubgen")] pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) { use evm_coder::solidity::*; use core::fmt::Write; --- a/crates/evm-coder/src/abi.rs +++ b/crates/evm-coder/src/abi.rs @@ -26,6 +26,8 @@ use crate::{ execution::{Error, ResultWithPostInfo, WithPostDispatchInfo}, types::*, + make_signature, + custom_signature::{SignatureUnit}, }; use crate::execution::Result; @@ -340,18 +342,18 @@ } } -/// [`AbiReader`] implements reading of many types, but it should -/// be limited to types defined in spec -/// -/// As this trait can't be made sealed, -/// instead of having `impl AbiRead for T`, we have `impl AbiRead for AbiReader` -pub trait AbiRead { +/// [`AbiReader`] implements reading of many types. +pub trait AbiRead { /// Read item from current position, advanding decoder - fn abi_read(&mut self) -> Result; + fn abi_read(reader: &mut AbiReader) -> Result + where + Self: Sized; } macro_rules! impl_abi_readable { ($ty:ty, $method:ident, $dynamic:literal) => { + impl sealed::CanBePlacedInVec for $ty {} + impl TypeHelper for $ty { fn is_dynamic() -> bool { $dynamic @@ -361,16 +363,16 @@ ABI_ALIGNMENT } } - impl AbiRead<$ty> for AbiReader<'_> { - fn abi_read(&mut self) -> Result<$ty> { - self.$method() + + impl AbiRead for $ty { + fn abi_read(reader: &mut AbiReader) -> Result<$ty> { + reader.$method() } } }; } impl_abi_readable!(bool, bool, false); -impl_abi_readable!(uint8, uint8, false); impl_abi_readable!(uint32, uint32, false); impl_abi_readable!(uint64, uint64, false); impl_abi_readable!(uint128, uint128, false); @@ -378,8 +380,21 @@ impl_abi_readable!(bytes4, bytes4, false); impl_abi_readable!(address, address, false); impl_abi_readable!(string, string, true); -// impl_abi_readable!(bytes, bytes, true); +impl TypeHelper for uint8 { + fn is_dynamic() -> bool { + false + } + fn size() -> usize { + ABI_ALIGNMENT + } +} +impl AbiRead for uint8 { + fn abi_read(reader: &mut AbiReader) -> Result { + reader.uint8() + } +} + impl TypeHelper for bytes { fn is_dynamic() -> bool { true @@ -388,9 +403,9 @@ ABI_ALIGNMENT } } -impl AbiRead for AbiReader<'_> { - fn abi_read(&mut self) -> Result { - Ok(bytes(self.bytes()?)) +impl AbiRead for bytes { + fn abi_read(reader: &mut AbiReader) -> Result { + Ok(bytes(reader.bytes()?)) } } @@ -399,26 +414,57 @@ pub trait CanBePlacedInVec {} } -impl sealed::CanBePlacedInVec for U256 {} -impl sealed::CanBePlacedInVec for string {} -impl sealed::CanBePlacedInVec for H160 {} - -impl AbiRead> for AbiReader<'_> -where - Self: AbiRead, -{ - fn abi_read(&mut self) -> Result> { - let mut sub = self.subresult(None)?; +impl AbiRead for Vec { + fn abi_read(reader: &mut AbiReader) -> Result> { + let mut sub = reader.subresult(None)?; let size = sub.uint32()? as usize; sub.subresult_offset = sub.offset; let mut out = Vec::with_capacity(size); for _ in 0..size { - out.push(>::abi_read(&mut sub)?); + out.push(::abi_read(&mut sub)?); } Ok(out) } } +impl Signature for Vec { + const SIGNATURE: SignatureUnit = make_signature!(new nameof(R::SIGNATURE) fixed("[]")); +} + +impl sealed::CanBePlacedInVec for EthCrossAccount {} + +impl TypeHelper for EthCrossAccount { + fn is_dynamic() -> bool { + address::is_dynamic() || uint256::is_dynamic() + } + + fn size() -> usize { +
::size() + ::size() + } +} + +impl AbiRead for EthCrossAccount { + fn abi_read(reader: &mut AbiReader) -> Result { + let size = if !EthCrossAccount::is_dynamic() { + Some(::size()) + } else { + None + }; + let mut subresult = reader.subresult(size)?; + let eth =
::abi_read(&mut subresult)?; + let sub = ::abi_read(&mut subresult)?; + + Ok(EthCrossAccount { eth, sub }) + } +} + +impl AbiWrite for EthCrossAccount { + fn abi_write(&self, writer: &mut AbiWriter) { + self.eth.abi_write(writer); + self.sub.abi_write(writer); + } +} + macro_rules! impl_tuples { ($($ident:ident)+) => { impl<$($ident: TypeHelper,)+> TypeHelper for ($($ident,)+) @@ -438,22 +484,23 @@ 0 $(+ <$ident>::size())+ } } + impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {} - impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_> + + impl<$($ident),+> AbiRead for ($($ident,)+) where - $( - Self: AbiRead<$ident>, - )+ + $($ident: AbiRead,)+ ($($ident,)+): TypeHelper, { - fn abi_read(&mut self) -> Result<($($ident,)+)> { + fn abi_read(reader: &mut AbiReader) -> Result<($($ident,)+)> { let size = if !<($($ident,)+)>::is_dynamic() { Some(<($($ident,)+)>::size()) } else { None }; - let mut subresult = self.subresult(size)?; + let mut subresult = reader.subresult(size)?; Ok(( - $(>::abi_read(&mut subresult)?,)+ + $(<$ident>::abi_read(&mut subresult)?,)+ )) } } + #[allow(non_snake_case)] impl<$($ident),+> AbiWrite for ($($ident,)+) where @@ -470,6 +517,18 @@ } } } + + impl<$($ident),+> Signature for ($($ident,)+) + where + $($ident: Signature,)+ + { + const SIGNATURE: SignatureUnit = make_signature!( + new fixed("(") + $(nameof(<$ident>::SIGNATURE) fixed(","))+ + shift_left(1) + fixed(")") + ); + } }; } @@ -630,7 +689,7 @@ let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap(); assert_eq!(call, u32::to_be_bytes(function_identifier)); - let data = as AbiRead<$type>>::abi_read(&mut decoder).unwrap(); + let data = <$type>::abi_read(&mut decoder).unwrap(); assert_eq!(data, decoded_data); let mut writer = AbiWriter::new_call(function_identifier); @@ -699,13 +758,13 @@ 1ACF2D55 0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[] 0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[] - + 0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address 000000000000000000000000000000000000000000000000000000000000000A // uint256 - + 000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address 0000000000000000000000000000000000000000000000000000000000000014 // uint256 - + 0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address 000000000000000000000000000000000000000000000000000000000000001E // uint256 " @@ -836,8 +895,7 @@ let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap(); assert_eq!(call, u32::to_be_bytes(decoded_data.0)); let address = decoder.address().unwrap(); - let data = - as AbiRead>>::abi_read(&mut decoder).unwrap(); + let data = >::abi_read(&mut decoder).unwrap(); assert_eq!(data, decoded_data.1); let mut writer = AbiWriter::new_call(decoded_data.0); --- /dev/null +++ b/crates/evm-coder/src/custom_signature.rs @@ -0,0 +1,326 @@ +//! # A module for custom signature support. +//! +//! ## Overview +//! This module allows you to create arbitrary signatures for types and functions in compile time. +//! +//! ### Type signatures +//! To create the desired type signature, you need to create your own trait with the `SIGNATURE` constant. +//! Then in the implementation, for the required type, use the macro [`make_signature`] +//! #### Example +//! ``` +//! use std::str::from_utf8; +//! use evm_coder::{custom_signature::SignatureUnit, make_signature}; +//! +//! // Create trait for our signature +//! trait SoliditySignature { +//! const SIGNATURE: SignatureUnit; +//! +//! fn name() -> &'static str { +//! from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") +//! } +//! } +//! +//! // Make signatures for some types +//! impl SoliditySignature for u8 { +//! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8")); +//! } +//! impl SoliditySignature for u32 { +//! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint32")); +//! } +//! impl SoliditySignature for Vec { +//! const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); +//! } +//! impl SoliditySignature for (A, B) { +//! const SIGNATURE: SignatureUnit = make_signature!(new fixed("(") nameof(A::SIGNATURE) fixed(",") nameof(B::SIGNATURE) fixed(")")); +//! } +//! impl SoliditySignature for (A,) { +//! const SIGNATURE: SignatureUnit = make_signature!(new fixed("(") nameof(A::SIGNATURE) fixed(",") shift_left(1) fixed(")")); +//! } +//! +//! assert_eq!(u8::name(), "uint8"); +//! assert_eq!(>::name(), "uint8[]"); +//! assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)"); +//! ``` +//! +//! ### Function signatures +//! To create a function signature, the macro [`make_signature`] is also used, which accepts +//! settings for the function format [`SignaturePreferences`] and function parameters [`SignatureUnit`] +//! #### Example +//! ``` +//! use core::str::from_utf8; +//! use evm_coder::{custom_signature::SignatureUnit, make_signature}; +//! // Trait for our signature +//! trait SoliditySignature { +//! const SIGNATURE: SignatureUnit; +//! +//! fn name() -> &'static str { +//! from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") +//! } +//! } +//! +//! // Make signatures for some types +//! impl SoliditySignature for u8 { +//! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8")); +//! } +//! impl SoliditySignature for Vec { +//! const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); +//! } +//! ``` + +/// The maximum length of the signature. +pub const SIGNATURE_SIZE_LIMIT: usize = 256; + +/// Storage for the signature or its elements. +#[derive(Debug)] +pub struct SignatureUnit { + /// Signature data. + pub data: [u8; SIGNATURE_SIZE_LIMIT], + /// The actual size of the data. + pub len: usize, +} + +impl SignatureUnit { + /// Create a signature from `&str'. + pub const fn new(name: &'static str) -> SignatureUnit { + let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT]; + let name = name.as_bytes(); + let name_len = name.len(); + let mut dst_offset = 0; + crate::make_signature!(@copy(name, signature, name_len, dst_offset)); + SignatureUnit { + data: signature, + len: name_len, + } + } + /// String conversion + pub fn as_str(&self) -> Option<&str> { + core::str::from_utf8(&self.data[0..self.len]).ok() + } +} + +/// ### Macro to create signatures of types and functions. +/// +/// Format for creating a type of signature: +/// ```ignore +/// make_signature!(new fixed("uint8")); // Simple type +/// make_signature!(new fixed("(") nameof(u8) fixed(",") nameof(u8) fixed(")")); // Composite type +/// ``` +#[macro_export] +macro_rules! make_signature { + (new $($tt:tt)*) => { + ($crate::custom_signature::SignatureUnit { + data: { + let mut out = [0u8; $crate::custom_signature::SIGNATURE_SIZE_LIMIT]; + let mut dst_offset = 0; + $crate::make_signature!(@data(out, dst_offset); $($tt)*); + out + }, + len: {0 + $crate::make_signature!(@size; $($tt)*)}, + }) + }; + + (@size;) => { + 0 + }; + (@size; fixed($expr:expr) $($tt:tt)*) => { + $expr.len() + $crate::make_signature!(@size; $($tt)*) + }; + (@size; nameof($expr:expr) $($tt:tt)*) => { + $expr.len + $crate::make_signature!(@size; $($tt)*) + }; + (@size; numof($expr:expr) $($tt:tt)*) => { + { + let mut out = 0; + let mut v = $expr; + if v == 0 { + out = 1; + } else { + while v > 0 { + out += 1; + v /= 10; + } + } + out + } + $crate::make_signature!(@size; $($tt)*) + }; + (@size; shift_left($expr:expr) $($tt:tt)*) => { + $crate::make_signature!(@size; $($tt)*) - $expr + }; + + (@data($dst:ident, $dst_offset:ident);) => {}; + (@data($dst:ident, $dst_offset:ident); fixed($expr:expr) $($tt:tt)*) => { + { + let data = $expr.as_bytes(); + let data_len = data.len(); + $crate::make_signature!(@copy(data, $dst, data_len, $dst_offset)); + } + $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + (@data($dst:ident, $dst_offset:ident); nameof($expr:expr) $($tt:tt)*) => { + { + $crate::make_signature!(@copy(&$expr.data, $dst, $expr.len, $dst_offset)); + } + $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + (@data($dst:ident, $dst_offset:ident); numof($expr:expr) $($tt:tt)*) => { + { + let mut v = $expr; + let mut need_to_swap = 0; + if v == 0 { + $dst[$dst_offset] = b'0'; + $dst_offset += 1; + } else { + while v > 0 { + let n = (v % 10) as u8; + $dst[$dst_offset] = b'0' + n; + v /= 10; + need_to_swap += 1; + $dst_offset += 1; + } + } + let mut i = 0; + #[allow(clippy::manual_swap)] + while i < need_to_swap / 2 { + let a = $dst_offset - i - 1; + let b = $dst_offset - need_to_swap + i; + let v = $dst[a]; + $dst[a] = $dst[b]; + $dst[b] = v; + i += 1; + } + } + $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + (@data($dst:ident, $dst_offset:ident); shift_left($expr:expr) $($tt:tt)*) => { + $dst_offset -= $expr; + $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + + (@copy($src:expr, $dst:expr, $src_len:expr, $dst_offset:ident)) => { + { + let mut src_offset = 0; + let src_len: usize = $src_len; + while src_offset < src_len { + $dst[$dst_offset] = $src[src_offset]; + $dst_offset += 1; + src_offset += 1; + } + } + } +} + +#[cfg(test)] +mod test { + use core::str::from_utf8; + + use super::{SIGNATURE_SIZE_LIMIT, SignatureUnit}; + + trait Name { + const NAME: SignatureUnit; + + fn name() -> &'static str { + from_utf8(&Self::NAME.data[..Self::NAME.len]).expect("bad utf-8") + } + } + + impl Name for u8 { + const NAME: SignatureUnit = make_signature!(new fixed("uint8")); + } + impl Name for u32 { + const NAME: SignatureUnit = make_signature!(new fixed("uint32")); + } + impl Name for Vec { + const NAME: SignatureUnit = make_signature!(new nameof(T::NAME) fixed("[]")); + } + impl Name for (A, B) { + const NAME: SignatureUnit = + make_signature!(new fixed("(") nameof(A::NAME) fixed(",") nameof(B::NAME) fixed(")")); + } + impl Name for (A,) { + const NAME: SignatureUnit = + make_signature!(new fixed("(") nameof(A::NAME) fixed(",") shift_left(1) fixed(")")); + } + impl Name for [A; SIZE] { + const NAME: SignatureUnit = + make_signature!(new nameof(A::NAME) fixed("[") numof(SIZE) fixed("]")); + } + + struct MaxSize(); + impl Name for MaxSize { + const NAME: SignatureUnit = SignatureUnit { + data: [b'!'; SIGNATURE_SIZE_LIMIT], + len: SIGNATURE_SIZE_LIMIT, + }; + } + + #[test] + fn simple() { + assert_eq!(u8::name(), "uint8"); + assert_eq!(u32::name(), "uint32"); + } + + #[test] + fn vector_of_simple() { + assert_eq!(>::name(), "uint8[]"); + assert_eq!(>::name(), "uint32[]"); + } + + #[test] + fn vector_of_vector() { + assert_eq!(>>::name(), "uint8[][]"); + } + + #[test] + fn tuple_of_simple() { + assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)"); + } + + #[test] + fn tuple_of_tuple() { + assert_eq!( + <((u32, u8), (u8, u32))>::name(), + "((uint32,uint8),(uint8,uint32))" + ); + } + + #[test] + fn vector_of_tuple() { + assert_eq!(>::name(), "(uint32,uint8)[]"); + } + + #[test] + fn tuple_of_vector() { + assert_eq!(<(Vec, u8)>::name(), "(uint32[],uint8)"); + } + + #[test] + fn complex() { + assert_eq!( + <(Vec, (u32, Vec))>::name(), + "(uint32[],(uint32,uint8[]))" + ); + } + + #[test] + fn max_size() { + assert_eq!(::name(), "!".repeat(SIGNATURE_SIZE_LIMIT)); + } + + #[test] + fn shift() { + assert_eq!(<(u32,)>::name(), "(uint32)"); + } + + #[test] + fn num() { + assert_eq!(<[u8; 0]>::name(), "uint8[0]"); + assert_eq!(<[u8; 1234]>::name(), "uint8[1234]"); + assert_eq!(<[u8; 12345]>::name(), "uint8[12345]"); + } + + #[test] + fn over_max_size() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/build_failed/custom_signature_over_max_size.rs"); + } +} --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -16,6 +16,7 @@ #![doc = include_str!("../README.md")] #![deny(missing_docs)] +#![macro_use] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(feature = "std"))] extern crate alloc; @@ -26,6 +27,8 @@ pub use events::{ToLog, ToTopic}; use execution::DispatchInfo; pub mod execution; +#[macro_use] +pub mod custom_signature; /// Derives call enum implementing [`crate::Callable`], [`crate::Weighted`] /// and [`crate::Call`] from impl block. @@ -90,6 +93,7 @@ pub use evm_coder_procedural::solidity; /// See [`solidity_interface`] pub use evm_coder_procedural::weight; +pub use sha3_const; /// Derives [`ToLog`] for enum /// @@ -104,6 +108,7 @@ #[doc(hidden)] pub mod events; #[doc(hidden)] +#[cfg(feature = "stubgen")] pub mod solidity; /// Solidity type definitions (aliases from solidity name to rust type) @@ -114,28 +119,55 @@ #[cfg(not(feature = "std"))] use alloc::{vec::Vec}; + use pallet_evm::account::CrossAccountId; use primitive_types::{U256, H160, H256}; + use core::str::from_utf8; + + use crate::custom_signature::SignatureUnit; + + pub trait Signature { + const SIGNATURE: SignatureUnit; + + fn as_str() -> &'static str { + from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") + } + } + + impl Signature for bool { + const SIGNATURE: SignatureUnit = make_signature!(new fixed("bool")); + } - pub type address = H160; + macro_rules! define_simple_type { + (type $ident:ident = $ty:ty) => { + pub type $ident = $ty; + impl Signature for $ty { + const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($ident))); + } + }; + } - pub type uint8 = u8; - pub type uint16 = u16; - pub type uint32 = u32; - pub type uint64 = u64; - pub type uint128 = u128; - pub type uint256 = U256; + define_simple_type!(type address = H160); - pub type bytes4 = [u8; 4]; + define_simple_type!(type uint8 = u8); + define_simple_type!(type uint16 = u16); + define_simple_type!(type uint32 = u32); + define_simple_type!(type uint64 = u64); + define_simple_type!(type uint128 = u128); + define_simple_type!(type uint256 = U256); + define_simple_type!(type bytes4 = [u8; 4]); - pub type topic = H256; + define_simple_type!(type topic = H256); #[cfg(not(feature = "std"))] - pub type string = ::alloc::string::String; + define_simple_type!(type string = ::alloc::string::String); #[cfg(feature = "std")] - pub type string = ::std::string::String; + define_simple_type!(type string = ::std::string::String); #[derive(Default, Debug)] pub struct bytes(pub Vec); + impl Signature for bytes { + const SIGNATURE: SignatureUnit = make_signature!(new fixed("bytes")); + } /// Solidity doesn't have `void` type, however we have special implementation /// for empty tuple return type @@ -166,6 +198,7 @@ } } + #[allow(clippy::from_over_into)] impl Into> for bytes { fn into(self) -> Vec { self.0 @@ -183,6 +216,76 @@ self.len() == 0 } } + + #[derive(Debug, Default)] + pub struct EthCrossAccount { + pub(crate) eth: address, + pub(crate) sub: uint256, + } + + impl EthCrossAccount { + pub fn from_sub_cross_account(cross_account_id: &T::CrossAccountId) -> Self + where + T: pallet_evm::account::Config, + T::AccountId: AsRef<[u8; 32]>, + { + if cross_account_id.is_canonical_substrate() { + Self { + eth: Default::default(), + sub: convert_cross_account_to_uint256::(cross_account_id), + } + } else { + Self { + eth: *cross_account_id.as_eth(), + sub: Default::default(), + } + } + } + + pub fn into_sub_cross_account(&self) -> crate::execution::Result + where + T: pallet_evm::account::Config, + T::AccountId: From<[u8; 32]>, + { + if self.eth == Default::default() && self.sub == Default::default() { + Err("All fields of cross account is zeroed".into()) + } else if self.eth == Default::default() { + Ok(convert_uint256_to_cross_account::(self.sub)) + } else if self.sub == Default::default() { + Ok(T::CrossAccountId::from_eth(self.eth)) + } else { + Err("All fields of cross account is non zeroed".into()) + } + } + } + + impl Signature for EthCrossAccount { + const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)")); + } + + /// Convert `CrossAccountId` to `uint256`. + pub fn convert_cross_account_to_uint256( + from: &T::CrossAccountId, + ) -> uint256 + where + T::AccountId: AsRef<[u8; 32]>, + { + let slice = from.as_sub().as_ref(); + uint256::from_big_endian(slice) + } + + /// Convert `uint256` to `CrossAccountId`. + pub fn convert_uint256_to_cross_account( + from: uint256, + ) -> T::CrossAccountId + where + T::AccountId: From<[u8; 32]>, + { + let mut new_admin_arr = [0_u8; 32]; + from.to_big_endian(&mut new_admin_arr); + let account_id = T::AccountId::from(new_admin_arr); + T::CrossAccountId::from_sub(account_id) + } } /// Parseable EVM call, this trait should be implemented with [`solidity_interface`] macro @@ -239,7 +342,7 @@ return Ok(None); } Ok(Some(Self::SupportsInterface { - interface_id: input.abi_read()?, + interface_id: types::bytes4::abi_read(input)?, })) } } @@ -252,6 +355,7 @@ #[macro_export] macro_rules! generate_stubgen { ($name:ident, $decl:ty, $is_impl:literal) => { + #[cfg(feature = "stubgen")] #[test] #[ignore] fn $name() { --- a/crates/evm-coder/src/solidity.rs +++ b/crates/evm-coder/src/solidity.rs @@ -32,7 +32,7 @@ cmp::Reverse, }; use impl_trait_for_tuples::impl_for_tuples; -use crate::types::*; +use crate::{types::*, custom_signature::SignatureUnit}; #[derive(Default)] pub struct TypeCollector { @@ -72,6 +72,10 @@ self.anonymous.borrow_mut().insert(names, id); format!("Tuple{}", id) } + pub fn collect_struct(&self) -> String { + self.collect(::declaration()); + ::name() + } pub fn finish(self) -> Vec { let mut data = self.structs.into_inner().into_iter().collect::>(); data.sort_by_key(|(_, id)| Reverse(*id)); @@ -79,6 +83,13 @@ } } +pub trait StructCollect: 'static { + /// Structure name. + fn name() -> String; + /// Structure declaration. + fn declaration() -> String; +} + pub trait SolidityTypeName: 'static { fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; /// "simple" types are stored inline, no `memory` modifier should be used in solidity @@ -145,6 +156,7 @@ impl sealed::CanBePlacedInVec for uint256 {} impl sealed::CanBePlacedInVec for string {} impl sealed::CanBePlacedInVec for address {} +impl sealed::CanBePlacedInVec for EthCrossAccount {} impl SolidityTypeName for Vec { fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { @@ -161,6 +173,60 @@ } } +impl SolidityTupleType for EthCrossAccount { + fn names(tc: &TypeCollector) -> Vec { + let mut collected = Vec::with_capacity(Self::len()); + { + let mut out = string::new(); + address::solidity_name(&mut out, tc).expect("no fmt error"); + collected.push(out); + } + { + let mut out = string::new(); + uint256::solidity_name(&mut out, tc).expect("no fmt error"); + collected.push(out); + } + collected + } + + fn len() -> usize { + 2 + } +} +impl SolidityTypeName for EthCrossAccount { + fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}", tc.collect_struct::()) + } + + fn is_simple() -> bool { + false + } + + fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}(", tc.collect_struct::())?; + address::solidity_default(writer, tc)?; + write!(writer, ",")?; + uint256::solidity_default(writer, tc)?; + write!(writer, ")") + } +} + +impl StructCollect for EthCrossAccount { + fn name() -> String { + "EthCrossAccount".into() + } + + fn declaration() -> String { + let mut str = String::new(); + writeln!(str, "/// @dev Cross account struct").unwrap(); + writeln!(str, "struct {} {{", Self::name()).unwrap(); + writeln!(str, "\taddress eth;").unwrap(); + writeln!(str, "\tuint256 sub;").unwrap(); + writeln!(str, "}}").unwrap(); + str + } +} + pub trait SolidityTupleType { fn names(tc: &TypeCollector) -> Vec; fn len() -> usize; @@ -418,9 +484,9 @@ } pub struct SolidityFunction { pub docs: &'static [&'static str], - pub selector_str: &'static str, pub selector: u32, pub hide: bool, + pub custom_signature: SignatureUnit, pub name: &'static str, pub args: A, pub result: R, @@ -446,7 +512,7 @@ writeln!( writer, "\t{hide_comment}/// or in textual repr: {}", - self.selector_str + self.custom_signature.as_str().expect("bad utf-8") )?; write!(writer, "\t{hide_comment}function {}(", self.name)?; self.args.solidity_name(writer, tc)?; --- /dev/null +++ b/crates/evm-coder/tests/build_failed/custom_signature_over_max_size.rs @@ -0,0 +1,34 @@ +#![allow(dead_code)] +use std::str::from_utf8; + +use evm_coder::{ + make_signature, + custom_signature::{SignatureUnit, SIGNATURE_SIZE_LIMIT}, +}; + +trait Name { + const SIGNATURE: SignatureUnit; + + fn name() -> &'static str { + from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") + } +} + +impl Name for Vec { + const SIGNATURE: SignatureUnit = + evm_coder::make_signature!(new nameof(T::SIGNATURE) fixed("[]")); +} + +struct MaxSize(); +impl Name for MaxSize { + const SIGNATURE: SignatureUnit = SignatureUnit { + data: [b'!'; SIGNATURE_SIZE_LIMIT], + len: SIGNATURE_SIZE_LIMIT, + }; +} + +const NAME: SignatureUnit = >::SIGNATURE; + +fn main() { + assert!(false); +} --- /dev/null +++ b/crates/evm-coder/tests/build_failed/custom_signature_over_max_size.stderr @@ -0,0 +1,29 @@ +warning: unused import: `make_signature` + --> tests/build_failed/custom_signature_over_max_size.rs:5:2 + | +5 | make_signature, + | ^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default + +error: any use of this value will cause an error + --> tests/build_failed/custom_signature_over_max_size.rs:19:3 + | +18 | const SIGNATURE: SignatureUnit = + | ------------------------------ +19 | evm_coder::make_signature!(new nameof(T::SIGNATURE) fixed("[]")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ index out of bounds: the length is 256 but the index is 256 + | + = note: `#[deny(const_err)]` on by default + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 + = note: this error originates in the macro `$crate::make_signature` which comes from the expansion of the macro `evm_coder::make_signature` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: any use of this value will cause an error + --> tests/build_failed/custom_signature_over_max_size.rs:30:29 + | +30 | const NAME: SignatureUnit = >::SIGNATURE; + | ------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #71800 --- a/crates/evm-coder/tests/random.rs +++ b/crates/evm-coder/tests/random.rs @@ -17,6 +17,7 @@ #![allow(dead_code)] // This test only checks that macros is not panicking use evm_coder::{ToLog, execution::Result, solidity_interface, types::*, solidity, weight}; +use evm_coder::{types::Signature}; pub struct Impls; --- a/crates/evm-coder/tests/solidity_generation.rs +++ b/crates/evm-coder/tests/solidity_generation.rs @@ -15,6 +15,7 @@ // along with Unique Network. If not, see . use evm_coder::{execution::Result, generate_stubgen, solidity_interface, types::*}; +use evm_coder::{types::Signature}; pub struct ERC20; --- a/pallets/common/CHANGELOG.md +++ b/pallets/common/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [0.1.10] - 2022-11-02 +### Changed + - Use named structure `EthCrossAccount` in eth functions. + ## [0.1.9] - 2022-10-13 ## Added --- a/pallets/common/Cargo.toml +++ b/pallets/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-common" -version = "0.1.9" +version = "0.1.10" license = "GPLv3" edition = "2021" @@ -45,3 +45,4 @@ "up-data-structs/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] +stubgen = ["evm-coder/stubgen"] --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -33,11 +33,7 @@ use crate::{ Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf, - eth::{ - convert_cross_account_to_uint256, convert_cross_account_to_tuple, - convert_tuple_to_cross_account, - }, - weights::WeightInfo, + eth::convert_cross_account_to_uint256, weights::WeightInfo, }; /// Events for ethereum collection helper. @@ -230,13 +226,13 @@ fn set_collection_sponsor_cross( &mut self, caller: caller, - sponsor: (address, uint256), + sponsor: EthCrossAccount, ) -> Result { self.consume_store_reads_and_writes(1, 1)?; check_is_owner_or_admin(caller, self)?; - let sponsor = convert_tuple_to_cross_account::(sponsor)?; + let sponsor = sponsor.into_sub_cross_account::()?; self.set_sponsor(sponsor.as_sub().clone()) .map_err(dispatch_to_evm::)?; save(self) @@ -387,12 +383,12 @@ fn add_collection_admin_cross( &mut self, caller: caller, - new_admin: (address, uint256), + new_admin: EthCrossAccount, ) -> Result { self.consume_store_writes(2)?; let caller = T::CrossAccountId::from_eth(caller); - let new_admin = convert_tuple_to_cross_account::(new_admin)?; + let new_admin = new_admin.into_sub_cross_account::()?; >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; Ok(()) } @@ -402,12 +398,12 @@ fn remove_collection_admin_cross( &mut self, caller: caller, - admin: (address, uint256), + admin: EthCrossAccount, ) -> Result { self.consume_store_writes(2)?; let caller = T::CrossAccountId::from_eth(caller); - let admin = convert_tuple_to_cross_account::(admin)?; + let admin = admin.into_sub_cross_account::()?; >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; Ok(()) } @@ -565,12 +561,12 @@ fn add_to_collection_allow_list_cross( &mut self, caller: caller, - user: (address, uint256), + user: EthCrossAccount, ) -> Result { self.consume_store_writes(1)?; let caller = T::CrossAccountId::from_eth(caller); - let user = convert_tuple_to_cross_account::(user)?; + let user = user.into_sub_cross_account::()?; Pallet::::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; Ok(()) } @@ -593,12 +589,12 @@ fn remove_from_collection_allow_list_cross( &mut self, caller: caller, - user: (address, uint256), + user: EthCrossAccount, ) -> Result { self.consume_store_writes(1)?; let caller = T::CrossAccountId::from_eth(caller); - let user = convert_tuple_to_cross_account::(user)?; + let user = user.into_sub_cross_account::()?; Pallet::::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::)?; Ok(()) } @@ -638,8 +634,8 @@ /// /// @param user User cross account to verify /// @return "true" if account is the owner or admin - fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result { - let user = convert_tuple_to_cross_account::(user)?; + fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result { + let user = user.into_sub_cross_account::()?; Ok(self.is_owner_or_admin(&user)) } @@ -657,10 +653,10 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. - fn collection_owner(&self) -> Result<(address, uint256)> { - Ok(convert_cross_account_to_tuple::( + fn collection_owner(&self) -> Result { + Ok(EthCrossAccount::from_sub_cross_account::( &T::CrossAccountId::from_sub(self.owner.clone()), )) } @@ -683,9 +679,9 @@ /// /// @return Vector of tuples with admins address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. - fn collection_admins(&self) -> Result> { + fn collection_admins(&self) -> Result> { let result = crate::IsAdmin::::iter_prefix((self.id,)) - .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::(&admin)) + .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::(&admin)) .collect(); Ok(result) } @@ -694,11 +690,11 @@ /// /// @dev Owner can be changed only by current owner /// @param newOwner new owner cross account - fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result { + fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result { self.consume_store_writes(1)?; let caller = T::CrossAccountId::from_eth(caller); - let new_owner = convert_tuple_to_cross_account::(new_owner)?; + let new_owner = new_owner.into_sub_cross_account::()?; self.set_owner_internal(caller, new_owner) .map_err(dispatch_to_evm::) } --- a/pallets/evm-contract-helpers/Cargo.toml +++ b/pallets/evm-contract-helpers/Cargo.toml @@ -27,7 +27,7 @@ evm-coder = { default-features = false, path = '../../crates/evm-coder' } pallet-common = { default-features = false, path = '../../pallets/common' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } -pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' } +pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' } up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [ 'serde1', ] } @@ -52,3 +52,4 @@ "up-sponsorship/std", ] try-runtime = ["frame-support/try-runtime"] +stubgen = ["evm-coder/stubgen"] --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -16,6 +16,7 @@ //! Implementation of magic contract +extern crate alloc; use core::marker::PhantomData; use evm_coder::{ abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog, --- a/pallets/fungible/CHANGELOG.md +++ b/pallets/fungible/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [0.1.6] - 2022-11-02 +### Changed + - Use named structure `EthCrossAccount` in eth functions. ## [0.1.5] - 2022-08-29 --- a/pallets/fungible/Cargo.toml +++ b/pallets/fungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-fungible" -version = "0.1.5" +version = "0.1.6" license = "GPLv3" edition = "2021" @@ -46,3 +46,4 @@ ] runtime-benchmarks = ['frame-benchmarking', 'pallet-common/runtime-benchmarks'] try-runtime = ["frame-support/try-runtime"] +stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"] --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -16,10 +16,10 @@ //! ERC-20 standart support implementation. +extern crate alloc; use core::char::{REPLACEMENT_CHARACTER, decode_utf16}; use core::convert::TryInto; use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; -use pallet_common::eth::convert_tuple_to_cross_account; use up_data_structs::CollectionMode; use pallet_common::erc::{CommonEvmHandler, PrecompileResult}; use sp_std::vec::Vec; @@ -158,11 +158,11 @@ fn approve_cross( &mut self, caller: caller, - spender: (address, uint256), + spender: EthCrossAccount, amount: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let spender = convert_tuple_to_cross_account::(spender)?; + let spender = spender.into_sub_cross_account::()?; let amount = amount.try_into().map_err(|_| "amount overflow")?; >::set_allowance(self, &caller, &spender, amount) @@ -198,11 +198,11 @@ fn burn_from_cross( &mut self, caller: caller, - from: (address, uint256), + from: EthCrossAccount, amount: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; + let from = from.into_sub_cross_account::()?; let amount = amount.try_into().map_err(|_| "amount overflow")?; let budget = self .recorder @@ -240,13 +240,13 @@ fn transfer_from_cross( &mut self, caller: caller, - from: (address, uint256), - to: (address, uint256), + from: EthCrossAccount, + to: EthCrossAccount, amount: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; - let to = convert_tuple_to_cross_account::(to)?; + let from = from.into_sub_cross_account::()?; + let to = to.into_sub_cross_account::()?; let amount = amount.try_into().map_err(|_| "amount overflow")?; let budget = self .recorder --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -38,7 +38,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple10[] memory properties) public { + function setCollectionProperties(Tuple14[] memory properties) public { require(false, stub_error); properties; dummy = 0; @@ -87,11 +87,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple10[] memory) { + function collectionProperties(string[] memory keys) public view returns (Tuple14[] memory) { require(false, stub_error); keys; dummy; - return new Tuple10[](0); + return new Tuple14[](0); } /// Set the sponsor of the collection. @@ -114,7 +114,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) public { + function setCollectionSponsorCross(EthCrossAccount memory sponsor) public { require(false, stub_error); sponsor; dummy = 0; @@ -152,10 +152,10 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() public view returns (Tuple6 memory) { + function collectionSponsor() public view returns (Tuple8 memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return Tuple8(0x0000000000000000000000000000000000000000, 0); } /// Set limits for the collection. @@ -206,7 +206,7 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) public { + function addCollectionAdminCross(EthCrossAccount memory newAdmin) public { require(false, stub_error); newAdmin; dummy = 0; @@ -216,7 +216,7 @@ /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) public { + function removeCollectionAdminCross(EthCrossAccount memory admin) public { require(false, stub_error); admin; dummy = 0; @@ -307,7 +307,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) public { + function addToCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -329,7 +329,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) public { + function removeFromCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -365,7 +365,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) { + function isOwnerOrAdminCross(EthCrossAccount memory user) public view returns (bool) { require(false, stub_error); user; dummy; @@ -385,14 +385,14 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() public view returns (Tuple6 memory) { + function collectionOwner() public view returns (EthCrossAccount memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return EthCrossAccount(0x0000000000000000000000000000000000000000, 0); } /// Changes collection owner to another account @@ -413,10 +413,10 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() public view returns (Tuple6[] memory) { + function collectionAdmins() public view returns (EthCrossAccount[] memory) { require(false, stub_error); dummy; - return new Tuple6[](0); + return new EthCrossAccount[](0); } /// Changes collection owner to another account @@ -425,15 +425,21 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) public { + function setOwnerCross(EthCrossAccount memory newOwner) public { require(false, stub_error); newOwner; dummy = 0; } } +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; +} + /// @dev anonymous struct -struct Tuple10 { +struct Tuple14 { string field_0; bytes field_1; } @@ -442,7 +448,7 @@ contract ERC20UniqueExtensions is Dummy, ERC165 { /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) - function approveCross(Tuple6 memory spender, uint256 amount) public returns (bool) { + function approveCross(EthCrossAccount memory spender, uint256 amount) public returns (bool) { require(false, stub_error); spender; amount; @@ -472,7 +478,7 @@ /// @param amount The amount that will be burnt. /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 amount) public returns (bool) { + function burnFromCross(EthCrossAccount memory from, uint256 amount) public returns (bool) { require(false, stub_error); from; amount; @@ -484,7 +490,7 @@ /// @param amounts array of pairs of account address and amount /// @dev EVM selector for this function is: 0x1acf2d55, /// or in textual repr: mintBulk((address,uint256)[]) - function mintBulk(Tuple6[] memory amounts) public returns (bool) { + function mintBulk(Tuple8[] memory amounts) public returns (bool) { require(false, stub_error); amounts; dummy = 0; @@ -494,8 +500,8 @@ /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 amount ) public returns (bool) { require(false, stub_error); @@ -508,7 +514,7 @@ } /// @dev anonymous struct -struct Tuple6 { +struct Tuple8 { address field_0; uint256 field_1; } --- a/pallets/nonfungible/CHANGELOG.md +++ b/pallets/nonfungible/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. +## [v0.1.7] - 2022-11-02 +### Changed + - Use named structure `EthCrossAccount` in eth functions. ## [v0.1.6] - 2022-20-10 --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nonfungible" -version = "0.1.6" +version = "0.1.7" license = "GPLv3" edition = "2021" @@ -52,3 +52,4 @@ 'up-data-structs/runtime-benchmarks', ] try-runtime = ["frame-support/try-runtime"] +stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"] --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -35,7 +35,6 @@ use pallet_common::{ erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key}, CollectionHandle, CollectionPropertyPermissions, - eth::convert_tuple_to_cross_account, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::call; @@ -675,11 +674,11 @@ fn approve_cross( &mut self, caller: caller, - approved: (address, uint256), + approved: EthCrossAccount, token_id: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let approved = convert_tuple_to_cross_account::(approved)?; + let approved = approved.into_sub_cross_account::()?; let token = token_id.try_into()?; >::set_allowance(self, &caller, token, Some(&approved)) @@ -715,13 +714,13 @@ fn transfer_from_cross( &mut self, caller: caller, - from: (address, uint256), - to: (address, uint256), + from: EthCrossAccount, + to: EthCrossAccount, token_id: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; - let to = convert_tuple_to_cross_account::(to)?; + let from = from.into_sub_cross_account::()?; + let to = to.into_sub_cross_account::()?; let token_id = token_id.try_into()?; let budget = self .recorder @@ -761,11 +760,11 @@ fn burn_from_cross( &mut self, caller: caller, - from: (address, uint256), + from: EthCrossAccount, token_id: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; + let from = from.into_sub_cross_account::()?; let token = token_id.try_into()?; let budget = self .recorder --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -67,7 +67,7 @@ /// @param properties settable properties /// @dev EVM selector for this function is: 0x14ed3a6e, /// or in textual repr: setProperties(uint256,(string,bytes)[]) - function setProperties(uint256 tokenId, Tuple19[] memory properties) public { + function setProperties(uint256 tokenId, Tuple21[] memory properties) public { require(false, stub_error); tokenId; properties; @@ -124,7 +124,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple19[] memory properties) public { + function setCollectionProperties(Tuple21[] memory properties) public { require(false, stub_error); properties; dummy = 0; @@ -173,11 +173,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple19[] memory) { + function collectionProperties(string[] memory keys) public view returns (Tuple21[] memory) { require(false, stub_error); keys; dummy; - return new Tuple19[](0); + return new Tuple21[](0); } /// Set the sponsor of the collection. @@ -200,7 +200,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) public { + function setCollectionSponsorCross(EthCrossAccount memory sponsor) public { require(false, stub_error); sponsor; dummy = 0; @@ -238,10 +238,10 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() public view returns (Tuple6 memory) { + function collectionSponsor() public view returns (Tuple24 memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return Tuple24(0x0000000000000000000000000000000000000000, 0); } /// Set limits for the collection. @@ -292,7 +292,7 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) public { + function addCollectionAdminCross(EthCrossAccount memory newAdmin) public { require(false, stub_error); newAdmin; dummy = 0; @@ -302,7 +302,7 @@ /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) public { + function removeCollectionAdminCross(EthCrossAccount memory admin) public { require(false, stub_error); admin; dummy = 0; @@ -393,7 +393,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) public { + function addToCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -415,7 +415,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) public { + function removeFromCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -451,7 +451,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) { + function isOwnerOrAdminCross(EthCrossAccount memory user) public view returns (bool) { require(false, stub_error); user; dummy; @@ -471,14 +471,14 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() public view returns (Tuple6 memory) { + function collectionOwner() public view returns (EthCrossAccount memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return EthCrossAccount(0x0000000000000000000000000000000000000000, 0); } /// Changes collection owner to another account @@ -499,10 +499,10 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() public view returns (Tuple6[] memory) { + function collectionAdmins() public view returns (EthCrossAccount[] memory) { require(false, stub_error); dummy; - return new Tuple6[](0); + return new EthCrossAccount[](0); } /// Changes collection owner to another account @@ -511,15 +511,27 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) public { + function setOwnerCross(EthCrossAccount memory newOwner) public { require(false, stub_error); newOwner; dummy = 0; } } +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; +} + /// @dev anonymous struct -struct Tuple19 { +struct Tuple24 { + address field_0; + uint256 field_1; +} + +/// @dev anonymous struct +struct Tuple21 { string field_0; bytes field_1; } @@ -696,7 +708,7 @@ /// @param tokenId The NFT to approve /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) - function approveCross(Tuple6 memory approved, uint256 tokenId) public { + function approveCross(EthCrossAccount memory approved, uint256 tokenId) public { require(false, stub_error); approved; tokenId; @@ -726,8 +738,8 @@ /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 tokenId ) public { require(false, stub_error); @@ -760,7 +772,7 @@ /// @param tokenId The NFT to transfer /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 tokenId) public { + function burnFromCross(EthCrossAccount memory from, uint256 tokenId) public { require(false, stub_error); from; tokenId; @@ -797,7 +809,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) { + // function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) public returns (bool) { // require(false, stub_error); // to; // tokens; @@ -808,15 +820,9 @@ } /// @dev anonymous struct -struct Tuple8 { +struct Tuple10 { uint256 field_0; string field_1; -} - -/// @dev anonymous struct -struct Tuple6 { - address field_0; - uint256 field_1; } /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension --- a/pallets/refungible/CHANGELOG.md +++ b/pallets/refungible/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [v0.2.6] - 2022-11-02 +### Changed + - Use named structure `EthCrossAccount` in eth functions. + ## [v0.2.5] - 2022-20-10 ### Change --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-refungible" -version = "0.2.5" +version = "0.2.6" license = "GPLv3" edition = "2021" @@ -26,7 +26,9 @@ struct-versioning = { path = "../../crates/struct-versioning" } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } ethereum = { version = "0.12.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive",] } +scale-info = { version = "2.0.1", default-features = false, features = [ + "derive", +] } derivative = { version = "2.2.0", features = ["use_core"] } [features] @@ -52,3 +54,4 @@ 'up-data-structs/runtime-benchmarks', ] try-runtime = ["frame-support/try-runtime"] +stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"] --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -30,7 +30,6 @@ use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, erc::{CommonEvmHandler, CollectionCall, static_property::key}, - eth::convert_tuple_to_cross_account, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm}; @@ -725,13 +724,13 @@ fn transfer_from_cross( &mut self, caller: caller, - from: (address, uint256), - to: (address, uint256), + from: EthCrossAccount, + to: EthCrossAccount, token_id: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; - let to = convert_tuple_to_cross_account::(to)?; + let from = from.into_sub_cross_account::()?; + let to = to.into_sub_cross_account::()?; let token_id = token_id.try_into()?; let budget = self .recorder @@ -780,11 +779,11 @@ fn burn_from_cross( &mut self, caller: caller, - from: (address, uint256), + from: EthCrossAccount, token_id: uint256, ) -> Result { let caller = T::CrossAccountId::from_eth(caller); - let from = convert_tuple_to_cross_account::(from)?; + let from = from.into_sub_cross_account::()?; let token = token_id.try_into()?; let budget = self .recorder --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -67,7 +67,7 @@ /// @param properties settable properties /// @dev EVM selector for this function is: 0x14ed3a6e, /// or in textual repr: setProperties(uint256,(string,bytes)[]) - function setProperties(uint256 tokenId, Tuple19[] memory properties) public { + function setProperties(uint256 tokenId, Tuple20[] memory properties) public { require(false, stub_error); tokenId; properties; @@ -124,7 +124,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple19[] memory properties) public { + function setCollectionProperties(Tuple20[] memory properties) public { require(false, stub_error); properties; dummy = 0; @@ -173,11 +173,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple19[] memory) { + function collectionProperties(string[] memory keys) public view returns (Tuple20[] memory) { require(false, stub_error); keys; dummy; - return new Tuple19[](0); + return new Tuple20[](0); } /// Set the sponsor of the collection. @@ -200,7 +200,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) public { + function setCollectionSponsorCross(EthCrossAccount memory sponsor) public { require(false, stub_error); sponsor; dummy = 0; @@ -238,10 +238,10 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() public view returns (Tuple6 memory) { + function collectionSponsor() public view returns (Tuple23 memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return Tuple23(0x0000000000000000000000000000000000000000, 0); } /// Set limits for the collection. @@ -292,7 +292,7 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) public { + function addCollectionAdminCross(EthCrossAccount memory newAdmin) public { require(false, stub_error); newAdmin; dummy = 0; @@ -302,7 +302,7 @@ /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) public { + function removeCollectionAdminCross(EthCrossAccount memory admin) public { require(false, stub_error); admin; dummy = 0; @@ -393,7 +393,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) public { + function addToCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -415,7 +415,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) public { + function removeFromCollectionAllowListCross(EthCrossAccount memory user) public { require(false, stub_error); user; dummy = 0; @@ -451,7 +451,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) { + function isOwnerOrAdminCross(EthCrossAccount memory user) public view returns (bool) { require(false, stub_error); user; dummy; @@ -471,14 +471,14 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() public view returns (Tuple6 memory) { + function collectionOwner() public view returns (EthCrossAccount memory) { require(false, stub_error); dummy; - return Tuple6(0x0000000000000000000000000000000000000000, 0); + return EthCrossAccount(0x0000000000000000000000000000000000000000, 0); } /// Changes collection owner to another account @@ -499,10 +499,10 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() public view returns (Tuple6[] memory) { + function collectionAdmins() public view returns (EthCrossAccount[] memory) { require(false, stub_error); dummy; - return new Tuple6[](0); + return new EthCrossAccount[](0); } /// Changes collection owner to another account @@ -511,15 +511,27 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) public { + function setOwnerCross(EthCrossAccount memory newOwner) public { require(false, stub_error); newOwner; dummy = 0; } } +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; +} + /// @dev anonymous struct -struct Tuple19 { +struct Tuple23 { + address field_0; + uint256 field_1; +} + +/// @dev anonymous struct +struct Tuple20 { string field_0; bytes field_1; } @@ -710,8 +722,8 @@ /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 tokenId ) public { require(false, stub_error); @@ -746,7 +758,7 @@ /// @param tokenId The RFT to transfer /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 tokenId) public { + function burnFromCross(EthCrossAccount memory from, uint256 tokenId) public { require(false, stub_error); from; tokenId; @@ -784,7 +796,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) { + // function mintBulkWithTokenURI(address to, Tuple9[] memory tokens) public returns (bool) { // require(false, stub_error); // to; // tokens; @@ -806,15 +818,9 @@ } /// @dev anonymous struct -struct Tuple8 { +struct Tuple9 { uint256 field_0; string field_1; -} - -/// @dev anonymous struct -struct Tuple6 { - address field_0; - uint256 field_1; } /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -20,14 +20,10 @@ use ethereum as _; use evm_coder::{execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; use frame_support::traits::Get; - use crate::Pallet; use pallet_common::{ - CollectionById, - dispatch::CollectionDispatch, - erc::{static_property::key, CollectionHelpersEvents}, - Pallet as PalletCommon, + CollectionById, dispatch::CollectionDispatch, erc::static_property::key, Pallet as PalletCommon, }; use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder}; --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -169,6 +169,7 @@ ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] unique-runtime = [] +stubgen = ["evm-coder/stubgen"] refungible = [] scheduler = [] --- a/tests/src/eth/allowlist.test.ts +++ b/tests/src/eth/allowlist.test.ts @@ -89,21 +89,21 @@ expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; }); - //itEth('Collection allowlist can be added and removed by [cross] address', async ({helper}) => { - // const owner = await helper.eth.createAccountWithBalance(donor); - // const user = donor; - // - // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - // const userCross = helper.ethCrossAccount.fromKeyringPair(user); - // - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - // await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - // - // await collectionEvm.methods.removeFromCollectionAllowListCross(userCross).send({from: owner}); - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - //}); + itEth('Collection allowlist can be added and removed by [cross] address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = donor; + + const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const userCross = helper.ethCrossAccount.fromKeyringPair(user); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + + await collectionEvm.methods.removeFromCollectionAllowListCross(userCross).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + }); itEth('Collection allowlist can not be add and remove [eth] address by not owner', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -122,22 +122,22 @@ expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true; }); - //itEth('Collection allowlist can not be add and remove [cross] address by not owner', async ({helper}) => { - // const owner = await helper.eth.createAccountWithBalance(donor); - // const notOwner = await helper.eth.createAccountWithBalance(donor); - // const user = donor; - // - // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - // - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - // const userCross = helper.ethCrossAccount.fromKeyringPair(user); - // await expect(collectionEvm.methods.addToCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission'); - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - // await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); - // - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - // await expect(collectionEvm.methods.removeFromCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission'); - // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - //}); + itEth('Collection allowlist can not be add and remove [cross] address by not owner', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const user = donor; + + const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + const userCross = helper.ethCrossAccount.fromKeyringPair(user); + await expect(collectionEvm.methods.addToCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); + + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + await expect(collectionEvm.methods.removeFromCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + }); }); --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -28,7 +28,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple10[] memory properties) external; + function setCollectionProperties(Tuple14[] memory properties) external; /// Delete collection property. /// @@ -60,7 +60,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple10[] memory); + function collectionProperties(string[] memory keys) external view returns (Tuple14[] memory); /// Set the sponsor of the collection. /// @@ -78,7 +78,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) external; + function setCollectionSponsorCross(EthCrossAccount memory sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -102,7 +102,7 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() external view returns (Tuple6 memory); + function collectionSponsor() external view returns (Tuple8 memory); /// Set limits for the collection. /// @dev Throws error if limit not found. @@ -138,13 +138,13 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) external; + function addCollectionAdminCross(EthCrossAccount memory newAdmin) external; /// Remove collection admin. /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) external; + function removeCollectionAdminCross(EthCrossAccount memory admin) external; /// Add collection admin. /// @param newAdmin Address of the added administrator. @@ -201,7 +201,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) external; + function addToCollectionAllowListCross(EthCrossAccount memory user) external; /// Remove the user from the allowed list. /// @@ -215,7 +215,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) external; + function removeFromCollectionAllowListCross(EthCrossAccount memory user) external; /// Switch permission for minting. /// @@ -238,7 +238,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool); + function isOwnerOrAdminCross(EthCrossAccount memory user) external view returns (bool); /// Returns collection type /// @@ -249,11 +249,11 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() external view returns (Tuple6 memory); + function collectionOwner() external view returns (EthCrossAccount memory); /// Changes collection owner to another account /// @@ -269,7 +269,7 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() external view returns (Tuple6[] memory); + function collectionAdmins() external view returns (EthCrossAccount[] memory); /// Changes collection owner to another account /// @@ -277,11 +277,17 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) external; + function setOwnerCross(EthCrossAccount memory newOwner) external; +} + +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; } /// @dev anonymous struct -struct Tuple10 { +struct Tuple14 { string field_0; bytes field_1; } @@ -290,7 +296,7 @@ interface ERC20UniqueExtensions is Dummy, ERC165 { /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) - function approveCross(Tuple6 memory spender, uint256 amount) external returns (bool); + function approveCross(EthCrossAccount memory spender, uint256 amount) external returns (bool); /// Burn tokens from account /// @dev Function that burns an `amount` of the tokens of a given account, @@ -308,25 +314,25 @@ /// @param amount The amount that will be burnt. /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 amount) external returns (bool); + function burnFromCross(EthCrossAccount memory from, uint256 amount) external returns (bool); /// Mint tokens for multiple accounts. /// @param amounts array of pairs of account address and amount /// @dev EVM selector for this function is: 0x1acf2d55, /// or in textual repr: mintBulk((address,uint256)[]) - function mintBulk(Tuple6[] memory amounts) external returns (bool); + function mintBulk(Tuple8[] memory amounts) external returns (bool); /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 amount ) external returns (bool); } /// @dev anonymous struct -struct Tuple6 { +struct Tuple8 { address field_0; uint256 field_1; } --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -49,7 +49,7 @@ /// @param properties settable properties /// @dev EVM selector for this function is: 0x14ed3a6e, /// or in textual repr: setProperties(uint256,(string,bytes)[]) - function setProperties(uint256 tokenId, Tuple19[] memory properties) external; + function setProperties(uint256 tokenId, Tuple21[] memory properties) external; /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. @@ -85,7 +85,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple19[] memory properties) external; + function setCollectionProperties(Tuple21[] memory properties) external; /// Delete collection property. /// @@ -117,7 +117,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple19[] memory); + function collectionProperties(string[] memory keys) external view returns (Tuple21[] memory); /// Set the sponsor of the collection. /// @@ -135,7 +135,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) external; + function setCollectionSponsorCross(EthCrossAccount memory sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -159,7 +159,7 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() external view returns (Tuple6 memory); + function collectionSponsor() external view returns (Tuple24 memory); /// Set limits for the collection. /// @dev Throws error if limit not found. @@ -195,13 +195,13 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) external; + function addCollectionAdminCross(EthCrossAccount memory newAdmin) external; /// Remove collection admin. /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) external; + function removeCollectionAdminCross(EthCrossAccount memory admin) external; /// Add collection admin. /// @param newAdmin Address of the added administrator. @@ -258,7 +258,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) external; + function addToCollectionAllowListCross(EthCrossAccount memory user) external; /// Remove the user from the allowed list. /// @@ -272,7 +272,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) external; + function removeFromCollectionAllowListCross(EthCrossAccount memory user) external; /// Switch permission for minting. /// @@ -295,7 +295,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool); + function isOwnerOrAdminCross(EthCrossAccount memory user) external view returns (bool); /// Returns collection type /// @@ -306,11 +306,11 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() external view returns (Tuple6 memory); + function collectionOwner() external view returns (EthCrossAccount memory); /// Changes collection owner to another account /// @@ -326,7 +326,7 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() external view returns (Tuple6[] memory); + function collectionAdmins() external view returns (EthCrossAccount[] memory); /// Changes collection owner to another account /// @@ -334,11 +334,23 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) external; + function setOwnerCross(EthCrossAccount memory newOwner) external; +} + +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; } /// @dev anonymous struct -struct Tuple19 { +struct Tuple24 { + address field_0; + uint256 field_1; +} + +/// @dev anonymous struct +struct Tuple21 { string field_0; bytes field_1; } @@ -458,7 +470,7 @@ /// @param tokenId The NFT to approve /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) - function approveCross(Tuple6 memory approved, uint256 tokenId) external; + function approveCross(EthCrossAccount memory approved, uint256 tokenId) external; /// @notice Transfer ownership of an NFT /// @dev Throws unless `msg.sender` is the current owner. Throws if `to` @@ -478,8 +490,8 @@ /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 tokenId ) external; @@ -501,7 +513,7 @@ /// @param tokenId The NFT to transfer /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 tokenId) external; + function burnFromCross(EthCrossAccount memory from, uint256 tokenId) external; /// @notice Returns next free NFT ID. /// @dev EVM selector for this function is: 0x75794a3c, @@ -523,20 +535,14 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool); + // function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) external returns (bool); } /// @dev anonymous struct -struct Tuple8 { +struct Tuple10 { uint256 field_0; string field_1; -} - -/// @dev anonymous struct -struct Tuple6 { - address field_0; - uint256 field_1; } /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -49,7 +49,7 @@ /// @param properties settable properties /// @dev EVM selector for this function is: 0x14ed3a6e, /// or in textual repr: setProperties(uint256,(string,bytes)[]) - function setProperties(uint256 tokenId, Tuple19[] memory properties) external; + function setProperties(uint256 tokenId, Tuple20[] memory properties) external; /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. @@ -85,7 +85,7 @@ /// @param properties Vector of properties key/value pair. /// @dev EVM selector for this function is: 0x50b26b2a, /// or in textual repr: setCollectionProperties((string,bytes)[]) - function setCollectionProperties(Tuple19[] memory properties) external; + function setCollectionProperties(Tuple20[] memory properties) external; /// Delete collection property. /// @@ -117,7 +117,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple19[] memory); + function collectionProperties(string[] memory keys) external view returns (Tuple20[] memory); /// Set the sponsor of the collection. /// @@ -135,7 +135,7 @@ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. /// @dev EVM selector for this function is: 0x84a1d5a8, /// or in textual repr: setCollectionSponsorCross((address,uint256)) - function setCollectionSponsorCross(Tuple6 memory sponsor) external; + function setCollectionSponsorCross(EthCrossAccount memory sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -159,7 +159,7 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() external view returns (Tuple6 memory); + function collectionSponsor() external view returns (Tuple23 memory); /// Set limits for the collection. /// @dev Throws error if limit not found. @@ -195,13 +195,13 @@ /// @param newAdmin Cross account administrator address. /// @dev EVM selector for this function is: 0x859aa7d6, /// or in textual repr: addCollectionAdminCross((address,uint256)) - function addCollectionAdminCross(Tuple6 memory newAdmin) external; + function addCollectionAdminCross(EthCrossAccount memory newAdmin) external; /// Remove collection admin. /// @param admin Cross account administrator address. /// @dev EVM selector for this function is: 0x6c0cd173, /// or in textual repr: removeCollectionAdminCross((address,uint256)) - function removeCollectionAdminCross(Tuple6 memory admin) external; + function removeCollectionAdminCross(EthCrossAccount memory admin) external; /// Add collection admin. /// @param newAdmin Address of the added administrator. @@ -258,7 +258,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0xa0184a3a, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) - function addToCollectionAllowListCross(Tuple6 memory user) external; + function addToCollectionAllowListCross(EthCrossAccount memory user) external; /// Remove the user from the allowed list. /// @@ -272,7 +272,7 @@ /// @param user User cross account address. /// @dev EVM selector for this function is: 0x09ba452a, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) - function removeFromCollectionAllowListCross(Tuple6 memory user) external; + function removeFromCollectionAllowListCross(EthCrossAccount memory user) external; /// Switch permission for minting. /// @@ -295,7 +295,7 @@ /// @return "true" if account is the owner or admin /// @dev EVM selector for this function is: 0x3e75a905, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) - function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool); + function isOwnerOrAdminCross(EthCrossAccount memory user) external view returns (bool); /// Returns collection type /// @@ -306,11 +306,11 @@ /// Get collection owner. /// - /// @return Tuple with sponsor address and his substrate mirror. + /// @return Tuble with sponsor address and his substrate mirror. /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() - function collectionOwner() external view returns (Tuple6 memory); + function collectionOwner() external view returns (EthCrossAccount memory); /// Changes collection owner to another account /// @@ -326,7 +326,7 @@ /// If address is canonical then substrate mirror is zero and vice versa. /// @dev EVM selector for this function is: 0x5813216b, /// or in textual repr: collectionAdmins() - function collectionAdmins() external view returns (Tuple6[] memory); + function collectionAdmins() external view returns (EthCrossAccount[] memory); /// Changes collection owner to another account /// @@ -334,11 +334,23 @@ /// @param newOwner new owner cross account /// @dev EVM selector for this function is: 0xe5c9913f, /// or in textual repr: setOwnerCross((address,uint256)) - function setOwnerCross(Tuple6 memory newOwner) external; + function setOwnerCross(EthCrossAccount memory newOwner) external; +} + +/// @dev Cross account struct +struct EthCrossAccount { + address eth; + uint256 sub; } /// @dev anonymous struct -struct Tuple19 { +struct Tuple23 { + address field_0; + uint256 field_1; +} + +/// @dev anonymous struct +struct Tuple20 { string field_0; bytes field_1; } @@ -467,8 +479,8 @@ /// @dev EVM selector for this function is: 0xd5cf430b, /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) function transferFromCross( - Tuple6 memory from, - Tuple6 memory to, + EthCrossAccount memory from, + EthCrossAccount memory to, uint256 tokenId ) external; @@ -492,7 +504,7 @@ /// @param tokenId The RFT to transfer /// @dev EVM selector for this function is: 0xbb2f5a58, /// or in textual repr: burnFromCross((address,uint256),uint256) - function burnFromCross(Tuple6 memory from, uint256 tokenId) external; + function burnFromCross(EthCrossAccount memory from, uint256 tokenId) external; /// @notice Returns next free RFT ID. /// @dev EVM selector for this function is: 0x75794a3c, @@ -515,7 +527,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool); + // function mintBulkWithTokenURI(address to, Tuple9[] memory tokens) external returns (bool); /// Returns EVM address for refungible token /// @@ -526,15 +538,9 @@ } /// @dev anonymous struct -struct Tuple8 { +struct Tuple9 { uint256 field_0; string field_1; -} - -/// @dev anonymous struct -struct Tuple6 { - address field_0; - uint256 field_1; } /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension --- a/tests/src/eth/base.test.ts +++ b/tests/src/eth/base.test.ts @@ -116,7 +116,7 @@ await checkInterface(helper, '0x780e9d63', true, true); }); - itEth('ERC721UniqueExtensions - 0x244543ee - support', async ({helper}) => { + itEth('ERC721UniqueExtensions support', async ({helper}) => { await checkInterface(helper, '0x244543ee', true, true); }); --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -14,7 +14,10 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {usingEthPlaygrounds, itEth, expect, EthUniqueHelper} from './util'; +import {expect} from 'chai'; +import {IEthCrossAccountId} from '../util/playgrounds/types'; +import {usingEthPlaygrounds, itEth} from './util'; +import {EthUniqueHelper} from './util/playgrounds/unique.dev'; async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise) { const before = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress)); @@ -63,24 +66,25 @@ expect(adminList).to.be.like([{Substrate: newAdmin.address}]); }); - // itEth('Check adminlist', async ({helper, privateKey}) => { - // const owner = await helper.eth.createAccountWithBalance(donor); + itEth('Check adminlist', async ({helper, privateKey}) => { + const owner = await helper.eth.createAccountWithBalance(donor); - // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - // const admin1 = helper.eth.createAccount(); - // const admin2 = await privateKey('admin'); - // await collectionEvm.methods.addCollectionAdmin(admin1).send(); - // await collectionEvm.methods.addCollectionAdminCross(helper.ethCrossAccount.fromKeyringPair(admin2)).send(); + const admin1 = helper.eth.createAccount(); + const admin2 = await privateKey('admin'); + const admin2Cross = helper.ethCrossAccount.fromKeyringPair(admin2); + await collectionEvm.methods.addCollectionAdmin(admin1).send(); + await collectionEvm.methods.addCollectionAdminCross(admin2Cross).send(); - // const adminListRpc = await helper.collection.getAdmins(collectionId); - // let adminListEth = await collectionEvm.methods.collectionAdmins().call(); - // adminListEth = adminListEth.map((element: IEthCrossAccountId) => { - // return helper.address.convertCrossAccountFromEthCrossAcoount(element); - // }); - // expect(adminListRpc).to.be.like(adminListEth); - // }); + const adminListRpc = await helper.collection.getAdmins(collectionId); + let adminListEth = await collectionEvm.methods.collectionAdmins().call(); + adminListEth = adminListEth.map((element: IEthCrossAccountId) => { + return helper.address.convertCrossAccountFromEthCrossAcoount(element); + }); + expect(adminListRpc).to.be.like(adminListEth); + }); itEth('Verify owner or admin', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -93,25 +97,6 @@ await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true; }); - - // itEth.skip('Check adminlist', async ({helper, privateKey}) => { - // const owner = await helper.eth.createAccountWithBalance(donor); - - // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - - // const admin1 = helper.eth.createAccount(); - // const admin2 = await privateKey('admin'); - // await collectionEvm.methods.addCollectionAdmin(admin1).send(); - // await collectionEvm.methods.addCollectionAdminSubstrate(admin2.addressRaw).send(); - - // const adminListRpc = await helper.collection.getAdmins(collectionId); - // let adminListEth = await collectionEvm.methods.collectionAdmins().call(); - // adminListEth = adminListEth.map((element: IEthCrossAccountId) => { - // return helper.address.convertCrossAccountFromEthCrossAcoount(element); - // }); - // expect(adminListRpc).to.be.like(adminListEth); - // }); itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -146,7 +131,7 @@ expect(adminList.length).to.be.eq(0); }); - itEth.skip('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({helper}) => { + itEth('(!negative tests!) Add [cross] admin by ADMIN is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); @@ -155,7 +140,8 @@ await collectionEvm.methods.addCollectionAdmin(admin).send(); const [notAdmin] = await helper.arrange.createAccounts([10n], donor); - await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin.addressRaw).call({from: admin})) + const notAdminCross = helper.ethCrossAccount.fromKeyringPair(notAdmin); + await expect(collectionEvm.methods.addCollectionAdminCross(notAdminCross).call({from: admin})) .to.be.rejectedWith('NoPermission'); const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); @@ -164,14 +150,15 @@ .to.be.eq(admin.toLocaleLowerCase()); }); - itEth.skip('(!negative tests!) Add substrate admin by USER is not allowed', async ({helper}) => { + itEth('(!negative tests!) Add [cross] admin by USER is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const notAdmin0 = await helper.eth.createAccountWithBalance(donor); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const [notAdmin1] = await helper.arrange.createAccounts([10n], donor); - await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin1.addressRaw).call({from: notAdmin0})) + const notAdmin1Cross = helper.ethCrossAccount.fromKeyringPair(notAdmin1); + await expect(collectionEvm.methods.addCollectionAdminCross(notAdmin1Cross).call({from: notAdmin0})) .to.be.rejectedWith('NoPermission'); const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); @@ -208,20 +195,21 @@ expect(adminList.length).to.be.eq(0); }); - itEth.skip('Remove substrate admin by owner', async ({helper}) => { + itEth('Remove [cross] admin by owner', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const [newAdmin] = await helper.arrange.createAccounts([10n], donor); + const newAdminCross = helper.ethCrossAccount.fromKeyringPair(newAdmin); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send(); + await collectionEvm.methods.addCollectionAdminCross(newAdminCross).send(); { const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); expect(adminList[0].asSubstrate.toString().toLocaleLowerCase()) .to.be.eq(newAdmin.address.toLocaleLowerCase()); } - await collectionEvm.methods.removeCollectionAdminSubstrate(newAdmin.addressRaw).send(); + await collectionEvm.methods.removeCollectionAdminCross(newAdminCross).send(); const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); expect(adminList.length).to.be.eq(0); }); @@ -268,17 +256,18 @@ } }); - itEth.skip('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({helper}) => { + itEth('(!negative tests!) Remove [cross] admin by ADMIN is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const [adminSub] = await helper.arrange.createAccounts([10n], donor); + const adminSubCross = helper.ethCrossAccount.fromKeyringPair(adminSub); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send(); + await collectionEvm.methods.addCollectionAdminCross(adminSubCross).send(); const adminEth = await helper.eth.createAccountWithBalance(donor); await collectionEvm.methods.addCollectionAdmin(adminEth).send(); - await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: adminEth})) + await expect(collectionEvm.methods.removeCollectionAdminCross(adminSubCross).call({from: adminEth})) .to.be.rejectedWith('NoPermission'); const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); @@ -288,16 +277,17 @@ .to.be.deep.contains(adminEth.toLocaleLowerCase()); }); - itEth.skip('(!negative tests!) Remove substrate admin by USER is not allowed', async ({helper}) => { + itEth('(!negative tests!) Remove [cross] admin by USER is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const [adminSub] = await helper.arrange.createAccounts([10n], donor); + const adminSubCross = helper.ethCrossAccount.fromKeyringPair(adminSub); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send(); + await collectionEvm.methods.addCollectionAdminCross(adminSubCross).send(); const notAdminEth = await helper.eth.createAccountWithBalance(donor); - await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: notAdminEth})) + await expect(collectionEvm.methods.removeCollectionAdminCross(adminSubCross).call({from: notAdminEth})) .to.be.rejectedWith('NoPermission'); const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); @@ -358,19 +348,20 @@ }); }); - itEth.skip('Change owner', async ({helper}) => { + itEth('Change owner [cross]', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const [newOwner] = await helper.arrange.createAccounts([10n], donor); + const newOwnerCross = helper.ethCrossAccount.fromKeyringPair(newOwner); const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.true; - expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false; + expect(await collectionEvm.methods.isOwnerOrAdminCross(newOwnerCross).call()).to.be.false; - await collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send(); + await collectionEvm.methods.setOwnerCross(newOwnerCross).send(); expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false; - expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.true; + expect(await collectionEvm.methods.isOwnerOrAdminCross(newOwnerCross).call()).to.be.true; }); itEth.skip('change owner call fee', async ({helper}) => { @@ -384,14 +375,15 @@ expect(cost > 0); }); - itEth.skip('(!negative tests!) call setOwner by non owner', async ({helper}) => { + itEth('(!negative tests!) call setOwner by non owner [cross]', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const otherReceiver = await helper.eth.createAccountWithBalance(donor); const [newOwner] = await helper.arrange.createAccounts([10n], donor); + const newOwnerCross = helper.ethCrossAccount.fromKeyringPair(newOwner); const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - await expect(collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send({from: otherReceiver})).to.be.rejected; - expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false; + await expect(collectionEvm.methods.setOwnerCross(newOwnerCross).send({from: otherReceiver})).to.be.rejected; + expect(await collectionEvm.methods.isOwnerOrAdminCross(newOwnerCross).call()).to.be.false; }); }); --- /dev/null +++ b/tests/src/eth/createFTCollection.seqtest.ts @@ -0,0 +1,76 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. +// +// Unique Network 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 Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {Pallets, requirePalletsOrSkip} from '../util'; +import {expect, itEth, usingEthPlaygrounds} from './util'; + +const DECIMALS = 18; + +describe('Create FT collection from EVM', () => { + let donor: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.Fungible]); + donor = await privateKey({filename: __filename}); + }); + }); + + itEth('Create collection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const name = 'CollectionEVM'; + const description = 'Some description'; + const prefix = 'token prefix'; + + // todo:playgrounds this might fail when in async environment. + const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + + const {collectionId} = await helper.eth.createFungibleCollection(owner, name, DECIMALS, description, prefix); + + const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + const data = (await helper.ft.getData(collectionId))!; + + expect(collectionCountAfter - collectionCountBefore).to.be.eq(1); + expect(collectionId).to.be.eq(collectionCountAfter); + expect(data.name).to.be.eq(name); + expect(data.description).to.be.eq(description); + expect(data.raw.tokenPrefix).to.be.eq(prefix); + expect(data.raw.mode).to.be.deep.eq({Fungible: DECIMALS.toString()}); + }); + + // todo:playgrounds this test will fail when in async environment. + itEth('Check collection address exist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; + const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); + const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.false; + + + await helper.eth.createFungibleCollection(owner, 'A', DECIMALS, 'A', 'A'); + + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.true; + }); +}); --- a/tests/src/eth/createFTCollection.test.ts +++ b/tests/src/eth/createFTCollection.test.ts @@ -30,50 +30,6 @@ donor = await privateKey({filename: __filename}); }); }); - - itEth('Create collection', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - - const name = 'CollectionEVM'; - const description = 'Some description'; - const prefix = 'token prefix'; - - // todo:playgrounds this might fail when in async environment. - const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; - - const {collectionId} = await helper.eth.createFungibleCollection(owner, name, DECIMALS, description, prefix); - - const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; - const data = (await helper.ft.getData(collectionId))!; - - expect(collectionCountAfter - collectionCountBefore).to.be.eq(1); - expect(collectionId).to.be.eq(collectionCountAfter); - expect(data.name).to.be.eq(name); - expect(data.description).to.be.eq(description); - expect(data.raw.tokenPrefix).to.be.eq(prefix); - expect(data.raw.mode).to.be.deep.eq({Fungible: DECIMALS.toString()}); - }); - - // todo:playgrounds this test will fail when in async environment. - itEth('Check collection address exist', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - - const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; - const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); - const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.false; - - - await helper.eth.createFungibleCollection(owner, 'A', DECIMALS, 'A', 'A'); - - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.true; - }); itEth('Set sponsorship', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); --- /dev/null +++ b/tests/src/eth/createNFTCollection.seqtest.ts @@ -0,0 +1,89 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. +// +// Unique Network 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 Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itEth, usingEthPlaygrounds} from './util'; + + +describe('Create NFT collection from EVM', () => { + let donor: IKeyringPair; + + before(async function () { + await usingEthPlaygrounds(async (_helper, privateKey) => { + donor = await privateKey({filename: __filename}); + }); + }); + + itEth('Create collection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const name = 'CollectionEVM'; + const description = 'Some description'; + const prefix = 'token prefix'; + + // todo:playgrounds this might fail when in async environment. + const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + const {collectionId, collectionAddress, events} = await helper.eth.createNFTCollection(owner, name, description, prefix); + + expect(events).to.be.deep.equal([ + { + address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F', + event: 'CollectionCreated', + args: { + owner: owner, + collectionId: collectionAddress, + }, + }, + ]); + + const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + + const collection = helper.nft.getCollectionObject(collectionId); + const data = (await collection.getData())!; + + expect(collectionCountAfter - collectionCountBefore).to.be.eq(1); + expect(collectionId).to.be.eq(collectionCountAfter); + expect(data.name).to.be.eq(name); + expect(data.description).to.be.eq(description); + expect(data.raw.tokenPrefix).to.be.eq(prefix); + expect(data.raw.mode).to.be.eq('NFT'); + + const options = await collection.getOptions(); + + expect(options.tokenPropertyPermissions).to.be.empty; + }); + + // this test will occasionally fail when in async environment. + itEth('Check collection address exist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; + const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); + const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.false; + + await collectionHelpers.methods + .createNFTCollection('A', 'A', 'A') + .send({value: Number(2n * helper.balance.getOneTokenNominal())}); + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.true; + }); +}); --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -28,45 +28,6 @@ }); }); - itEth('Create collection', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - - const name = 'CollectionEVM'; - const description = 'Some description'; - const prefix = 'token prefix'; - - // todo:playgrounds this might fail when in async environment. - const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; - const {collectionId, collectionAddress, events} = await helper.eth.createNFTCollection(owner, name, description, prefix); - - expect(events).to.be.deep.equal([ - { - address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F', - event: 'CollectionCreated', - args: { - owner: owner, - collectionId: collectionAddress, - }, - }, - ]); - - const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; - - const collection = helper.nft.getCollectionObject(collectionId); - const data = (await collection.getData())!; - - expect(collectionCountAfter - collectionCountBefore).to.be.eq(1); - expect(collectionId).to.be.eq(collectionCountAfter); - expect(data.name).to.be.eq(name); - expect(data.description).to.be.eq(description); - expect(data.raw.tokenPrefix).to.be.eq(prefix); - expect(data.raw.mode).to.be.eq('NFT'); - - const options = await collection.getOptions(); - - expect(options.tokenPropertyPermissions).to.be.empty; - }); - itEth('Create collection with properties', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -107,27 +68,6 @@ permission: {mutable: true, collectionAdmin: true, tokenOwner: false}, }, ]); - }); - - // this test will occasionally fail when in async environment. - itEth.skip('Check collection address exist', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - - const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; - const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); - const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.false; - - await collectionHelpers.methods - .createNFTCollection('A', 'A', 'A') - .send({value: Number(2n * helper.balance.getOneTokenNominal())}); - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.true; }); itEth('Set sponsorship', async ({helper}) => { --- a/tests/src/eth/fungible.test.ts +++ b/tests/src/eth/fungible.test.ts @@ -52,11 +52,12 @@ describe('Fungible: Plain calls', () => { let donor: IKeyringPair; let alice: IKeyringPair; + let owner: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); - [alice] = await helper.arrange.createAccounts([20n], donor); + [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor); }); }); @@ -148,21 +149,20 @@ } }); - itEth('Can perform burnFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const sender = await helper.eth.createAccountWithBalance(alice, 100n); + itEth('Can perform burnFromCross()', async ({helper}) => { + const sender = await helper.eth.createAccountWithBalance(donor, 100n); - const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); + const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); - await collection.mint(alice, 200n, {Substrate: alice.address}); - await collection.approveTokens(alice, {Ethereum: sender}, 100n); + await collection.mint(owner, 200n, {Substrate: owner.address}); + await collection.approveTokens(owner, {Ethereum: sender}, 100n); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'ft'); - const fromBalanceBefore = await collection.getBalance({Substrate: alice.address}); + const fromBalanceBefore = await collection.getBalance({Substrate: owner.address}); - const ownerCross = helper.ethCrossAccount.fromKeyringPair(alice); + const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner); const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender}); const events = result.events; @@ -171,7 +171,7 @@ address: helper.ethAddress.fromCollectionId(collection.collectionId), event: 'Transfer', returnValues: { - from: helper.address.substrateToEth(alice.address), + from: helper.address.substrateToEth(owner.address), to: '0x0000000000000000000000000000000000000000', value: '49', }, @@ -179,7 +179,7 @@ Approval: { address: helper.ethAddress.fromCollectionId(collection.collectionId), returnValues: { - owner: helper.address.substrateToEth(alice.address), + owner: helper.address.substrateToEth(owner.address), spender: sender, value: '51', }, @@ -187,7 +187,7 @@ }, }); - const fromBalanceAfter = await collection.getBalance({Substrate: alice.address}); + const fromBalanceAfter = await collection.getBalance({Substrate: owner.address}); expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n); }); @@ -261,23 +261,22 @@ }); itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const sender = await helper.eth.createAccountWithBalance(alice, 100n); + const sender = await helper.eth.createAccountWithBalance(donor, 100n); - const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); + const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); const receiver = helper.eth.createAccount(); - await collection.mint(alice, 200n, {Substrate: alice.address}); - await collection.approveTokens(alice, {Ethereum: sender}, 100n); + await collection.mint(owner, 200n, {Substrate: owner.address}); + await collection.approveTokens(owner, {Ethereum: sender}, 100n); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'ft'); - const from = helper.ethCrossAccount.fromKeyringPair(alice); + const from = helper.ethCrossAccount.fromKeyringPair(owner); const to = helper.ethCrossAccount.fromAddress(receiver); - const fromBalanceBefore = await collection.getBalance({Substrate: alice.address}); + const fromBalanceBefore = await collection.getBalance({Substrate: owner.address}); const toBalanceBefore = await collection.getBalance({Ethereum: receiver}); const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender}); @@ -287,7 +286,7 @@ address, event: 'Transfer', returnValues: { - from: helper.address.substrateToEth(alice.address), + from: helper.address.substrateToEth(owner.address), to: receiver, value: '51', }, @@ -296,13 +295,13 @@ address, event: 'Approval', returnValues: { - owner: helper.address.substrateToEth(alice.address), + owner: helper.address.substrateToEth(owner.address), spender: sender, value: '49', }, }}); - const fromBalanceAfter = await collection.getBalance({Substrate: alice.address}); + const fromBalanceAfter = await collection.getBalance({Substrate: owner.address}); expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n); const toBalanceAfter = await collection.getBalance({Ethereum: receiver}); expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n); @@ -365,11 +364,12 @@ describe('Fungible: Substrate calls', () => { let donor: IKeyringPair; let alice: IKeyringPair; + let owner: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); - [alice] = await helper.arrange.createAccounts([20n], donor); + [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor); }); }); @@ -455,20 +455,19 @@ }); itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const sender = await helper.eth.createAccountWithBalance(alice, 100n); + const sender = await helper.eth.createAccountWithBalance(donor, 100n); - const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); + const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); const receiver = helper.eth.createAccount(); - await collection.mint(alice, 200n, {Substrate: alice.address}); - await collection.approveTokens(alice, {Ethereum: sender}, 100n); + await collection.mint(owner, 200n, {Substrate: owner.address}); + await collection.approveTokens(owner, {Ethereum: sender}, 100n); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'ft'); - const from = helper.ethCrossAccount.fromKeyringPair(alice); + const from = helper.ethCrossAccount.fromKeyringPair(owner); const to = helper.ethCrossAccount.fromAddress(receiver); const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender}); @@ -478,7 +477,7 @@ address, event: 'Transfer', returnValues: { - from: helper.address.substrateToEth(alice.address), + from: helper.address.substrateToEth(owner.address), to: receiver, value: '51', }, @@ -487,7 +486,7 @@ address, event: 'Approval', returnValues: { - owner: helper.address.substrateToEth(alice.address), + owner: helper.address.substrateToEth(owner.address), spender: sender, value: '49', }, --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -62,10 +62,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newAdmin", "type": "tuple" } @@ -88,10 +88,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -134,10 +134,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "spender", "type": "tuple" }, @@ -171,10 +171,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, @@ -200,10 +200,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6[]", + "internalType": "struct EthCrossAccount[]", "name": "", "type": "tuple[]" } @@ -217,10 +217,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "", "type": "tuple" } @@ -239,7 +239,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple10[]", + "internalType": "struct Tuple14[]", "name": "", "type": "tuple[]" } @@ -263,7 +263,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct Tuple8", "name": "", "type": "tuple" } @@ -328,10 +328,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -358,7 +358,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple6[]", + "internalType": "struct Tuple8[]", "name": "amounts", "type": "tuple[]" } @@ -388,10 +388,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "admin", "type": "tuple" } @@ -421,10 +421,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -496,7 +496,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple10[]", + "internalType": "struct Tuple14[]", "name": "properties", "type": "tuple[]" } @@ -529,10 +529,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "sponsor", "type": "tuple" } @@ -546,10 +546,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newOwner", "type": "tuple" } @@ -607,19 +607,19 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "to", "type": "tuple" }, --- /dev/null +++ b/tests/src/eth/migration.seqtest.ts @@ -0,0 +1,108 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +import {expect, itEth, usingEthPlaygrounds} from './util'; +import {IKeyringPair} from '@polkadot/types/types'; + +describe('EVM Migrations', () => { + let superuser: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (_helper, privateKey) => { + superuser = await privateKey('//Alice'); + }); + }); + + // todo:playgrounds requires sudo, look into later + itEth('Deploy contract saved state', async ({helper}) => { + /* + contract StatefulContract { + uint counter; + mapping (uint => uint) kv; + + function inc() public { + counter = counter + 1; + } + function counterValue() public view returns (uint) { + return counter; + } + + function set(uint key, uint value) public { + kv[key] = value; + } + + function get(uint key) public view returns (uint) { + return kv[key]; + } + } + */ + const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6'; + const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033'; + const DATA = [ + // counter = 10 + ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'], + // kv = {1: 1, 2: 2, 3: 3, 4: 4}, + ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'], + ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'], + ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'], + ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'], + ]; + + const caller = await helper.eth.createAccountWithBalance(superuser); + + const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]); + const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]); + const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]); + await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled; + await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled; + await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled; + + const web3 = helper.getWeb3(); + const contract = new web3.eth.Contract([ + { + inputs: [], + name: 'counterValue', + outputs: [{ + internalType: 'uint256', + name: '', + type: 'uint256', + }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ + internalType: 'uint256', + name: 'key', + type: 'uint256', + }], + name: 'get', + outputs: [{ + internalType: 'uint256', + name: '', + type: 'uint256', + }], + stateMutability: 'view', + type: 'function', + }, + ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS}); + + expect(await contract.methods.counterValue().call()).to.be.equal('10'); + for (let i = 1; i <= 4; i++) { + expect(await contract.methods.get(i).call()).to.be.equal(i.toString()); + } + }); +}); --- a/tests/src/eth/migration.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network 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. - -// Unique Network 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 Unique Network. If not, see . - -import {expect, itEth, usingEthPlaygrounds} from './util'; -import {IKeyringPair} from '@polkadot/types/types'; - -describe('EVM Migrations', () => { - let superuser: IKeyringPair; - - before(async function() { - await usingEthPlaygrounds(async (_helper, privateKey) => { - superuser = await privateKey('//Alice'); - }); - }); - - // todo:playgrounds requires sudo, look into later - itEth('Deploy contract saved state', async ({helper}) => { - /* - contract StatefulContract { - uint counter; - mapping (uint => uint) kv; - - function inc() public { - counter = counter + 1; - } - function counterValue() public view returns (uint) { - return counter; - } - - function set(uint key, uint value) public { - kv[key] = value; - } - - function get(uint key) public view returns (uint) { - return kv[key]; - } - } - */ - const ADDRESS = '0x4956bf52ef9ed8789f21bc600e915e0d961079f6'; - const CODE = '0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80631ab06ee514610051578063371303c01461006d5780637bfdec3b146100775780639507d39a14610095575b600080fd5b61006b60048036038101906100669190610160565b6100c5565b005b6100756100e1565b005b61007f6100f8565b60405161008c91906101af565b60405180910390f35b6100af60048036038101906100aa9190610133565b610101565b6040516100bc91906101af565b60405180910390f35b8060016000848152602001908152602001600020819055505050565b60016000546100f091906101ca565b600081905550565b60008054905090565b600060016000838152602001908152602001600020549050919050565b60008135905061012d8161025e565b92915050565b60006020828403121561014957610148610259565b5b60006101578482850161011e565b91505092915050565b6000806040838503121561017757610176610259565b5b60006101858582860161011e565b92505060206101968582860161011e565b9150509250929050565b6101a981610220565b82525050565b60006020820190506101c460008301846101a0565b92915050565b60006101d582610220565b91506101e083610220565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102155761021461022a565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b61026781610220565b811461027257600080fd5b5056fea26469706673582212206a02d2fb5c244105ab884961479c1aee3b4c1011e4b5530ab483eb22344a865664736f6c63430008060033'; - const DATA = [ - // counter = 10 - ['0x0000000000000000000000000000000000000000000000000000000000000000', '0x000000000000000000000000000000000000000000000000000000000000000a'], - // kv = {1: 1, 2: 2, 3: 3, 4: 4}, - ['0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f', '0x0000000000000000000000000000000000000000000000000000000000000001'], - ['0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f', '0x0000000000000000000000000000000000000000000000000000000000000002'], - ['0x7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b', '0x0000000000000000000000000000000000000000000000000000000000000003'], - ['0xedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643', '0x0000000000000000000000000000000000000000000000000000000000000004'], - ]; - - const caller = await helper.eth.createAccountWithBalance(superuser); - - const txBegin = helper.constructApiCall('api.tx.evmMigration.begin', [ADDRESS]); - const txSetData = helper.constructApiCall('api.tx.evmMigration.setData', [ADDRESS, DATA]); - const txFinish = helper.constructApiCall('api.tx.evmMigration.finish', [ADDRESS, CODE]); - await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txBegin])).to.be.fulfilled; - await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txSetData])).to.be.fulfilled; - await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [txFinish])).to.be.fulfilled; - - const web3 = helper.getWeb3(); - const contract = new web3.eth.Contract([ - { - inputs: [], - name: 'counterValue', - outputs: [{ - internalType: 'uint256', - name: '', - type: 'uint256', - }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [{ - internalType: 'uint256', - name: 'key', - type: 'uint256', - }], - name: 'get', - outputs: [{ - internalType: 'uint256', - name: '', - type: 'uint256', - }], - stateMutability: 'view', - type: 'function', - }, - ], ADDRESS, {from: caller, gas: helper.eth.DEFAULT_GAS}); - - expect(await contract.methods.counterValue().call()).to.be.equal('10'); - for (let i = 1; i <= 4; i++) { - expect(await contract.methods.get(i).call()).to.be.equal(i.toString()); - } - }); -}); --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -138,12 +138,14 @@ describe('NFT: Plain calls', () => { let donor: IKeyringPair; - let alice: IKeyringPair; + let minter: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); - [alice] = await helper.arrange.createAccounts([10n], donor); + [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor); }); }); @@ -176,8 +178,8 @@ const caller = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); - const collection = await helper.nft.mintCollection(alice); - await collection.addAdmin(alice, {Ethereum: caller}); + const collection = await helper.nft.mintCollection(minter); + await collection.addAdmin(minter, {Ethereum: caller}); const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller); @@ -208,8 +210,8 @@ itEth('Can perform burn()', async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const collection = await helper.nft.mintCollection(alice, {}); - const {tokenId} = await collection.mintToken(alice, {Ethereum: caller}); + const collection = await helper.nft.mintCollection(minter, {}); + const {tokenId} = await collection.mintToken(minter, {Ethereum: caller}); const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller); @@ -229,8 +231,8 @@ const owner = await helper.eth.createAccountWithBalance(donor); const spender = helper.eth.createAccount(); - const collection = await helper.nft.mintCollection(alice, {}); - const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); + const collection = await helper.nft.mintCollection(minter, {}); + const {tokenId} = await collection.mintToken(minter, {Ethereum: owner}); const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); @@ -246,14 +248,13 @@ } }); - itEth('Can perform burnFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); + itEth('Can perform burnFromCross()', async ({helper}) => { + const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await privateKey('//Bob'); - const spender = await helper.eth.createAccountWithBalance(alice, 100n); + const owner = bob; + const spender = await helper.eth.createAccountWithBalance(donor, 100n); - const token = await collection.mintToken(alice, {Substrate: owner.address}); + const token = await collection.mintToken(minter, {Substrate: owner.address}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'nft'); @@ -276,14 +277,13 @@ } }); - itEth('Can perform approveCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); + itEth('Can perform approveCross()', async ({helper}) => { + const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await helper.eth.createAccountWithBalance(alice, 100n); - const receiver = await privateKey('//Charlie'); + const owner = await helper.eth.createAccountWithBalance(donor, 100n); + const receiver = charlie; - const token = await collection.mintToken(alice, {Ethereum: owner}); + const token = await collection.mintToken(minter, {Ethereum: owner}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'nft'); @@ -309,8 +309,8 @@ const spender = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); - const collection = await helper.nft.mintCollection(alice, {}); - const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); + const collection = await helper.nft.mintCollection(minter, {}); + const {tokenId} = await collection.mintToken(minter, {Ethereum: owner}); const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); @@ -338,12 +338,46 @@ } }); + itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { + const minter = await privateKey('//Alice'); + const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); + + const owner = await privateKey('//Bob'); + const spender = await helper.eth.createAccountWithBalance(donor); + const receiver = await privateKey('//Charlie'); + + const token = await collection.mintToken(minter, {Substrate: owner.address}); + + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, 'nft'); + + await token.approve(owner, {Ethereum: spender}); + + { + const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner); + const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver); + const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender}); + const event = result.events.Transfer; + expect(event).to.be.like({ + address: helper.ethAddress.fromCollectionId(collection.collectionId), + event: 'Transfer', + returnValues: { + from: helper.address.substrateToEth(owner.address), + to: helper.address.substrateToEth(receiver.address), + tokenId: token.tokenId.toString(), + }, + }); + } + + expect(await token.getOwner()).to.be.like({Substrate: receiver.address}); + }); + itEth('Can perform transfer()', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {}); + const collection = await helper.nft.mintCollection(minter, {}); const owner = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); - const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); + const {tokenId} = await collection.mintToken(minter, {Ethereum: owner}); const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); @@ -373,11 +407,13 @@ describe('NFT: Fees', () => { let donor: IKeyringPair; let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); - [alice] = await helper.arrange.createAccounts([10n], donor); + [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor); }); }); @@ -410,14 +446,14 @@ }); itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); + const collectionMinter = alice; + const owner = bob; + const receiver = charlie; + const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await privateKey('//Bob'); - const spender = await helper.eth.createAccountWithBalance(alice, 100n); - const receiver = await privateKey('//Charlie'); + const spender = await helper.eth.createAccountWithBalance(donor, 100n); - const token = await collection.mintToken(alice, {Substrate: owner.address}); + const token = await collection.mintToken(collectionMinter, {Substrate: owner.address}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'nft'); --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -93,10 +93,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newAdmin", "type": "tuple" } @@ -119,10 +119,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -155,10 +155,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "approved", "type": "tuple" }, @@ -201,10 +201,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, @@ -230,10 +230,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6[]", + "internalType": "struct EthCrossAccount[]", "name": "", "type": "tuple[]" } @@ -247,10 +247,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "", "type": "tuple" } @@ -269,7 +269,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple21[]", "name": "", "type": "tuple[]" } @@ -293,7 +293,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct Tuple24", "name": "", "type": "tuple" } @@ -387,10 +387,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -470,10 +470,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "admin", "type": "tuple" } @@ -503,10 +503,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -611,7 +611,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple21[]", "name": "properties", "type": "tuple[]" } @@ -644,10 +644,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "sponsor", "type": "tuple" } @@ -661,10 +661,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newOwner", "type": "tuple" } @@ -682,7 +682,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple21[]", "name": "properties", "type": "tuple[]" } @@ -791,19 +791,19 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "to", "type": "tuple" }, --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -104,12 +104,16 @@ describe('Refungible: Plain calls', () => { let donor: IKeyringPair; + let minter: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); donor = await privateKey({filename: __filename}); + [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor); }); }); @@ -227,14 +231,13 @@ } }); - itEth('Can perform burnFrom()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); + itEth('Can perform burnFrom()', async ({helper}) => { + const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await helper.eth.createAccountWithBalance(alice, 100n); - const spender = await helper.eth.createAccountWithBalance(alice, 100n); + const owner = await helper.eth.createAccountWithBalance(donor, 100n); + const spender = await helper.eth.createAccountWithBalance(donor, 100n); - const token = await collection.mintToken(alice, 100n, {Ethereum: owner}); + const token = await collection.mintToken(minter, 100n, {Ethereum: owner}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'rft'); @@ -260,15 +263,14 @@ expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n); }); - - itEth('Can perform burnFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await privateKey('//Bob'); - const spender = await helper.eth.createAccountWithBalance(alice, 100n); + itEth('Can perform burnFromCross()', async ({helper}) => { + const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); + + const owner = bob; + const spender = await helper.eth.createAccountWithBalance(donor, 100n); - const token = await collection.mintToken(alice, 100n, {Substrate: owner.address}); + const token = await collection.mintToken(minter, 100n, {Substrate: owner.address}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'rft'); @@ -294,15 +296,14 @@ expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n); }); - itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); - const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); + itEth('Can perform transferFromCross()', async ({helper}) => { + const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await privateKey('//Bob'); - const spender = await helper.eth.createAccountWithBalance(alice, 100n); - const receiver = await privateKey('//Charlie'); + const owner = bob; + const spender = await helper.eth.createAccountWithBalance(donor, 100n); + const receiver = charlie; - const token = await collection.mintToken(alice, 100n, {Substrate: owner.address}); + const token = await collection.mintToken(minter, 100n, {Substrate: owner.address}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'rft'); --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -93,10 +93,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newAdmin", "type": "tuple" } @@ -119,10 +119,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -183,10 +183,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, @@ -212,10 +212,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6[]", + "internalType": "struct EthCrossAccount[]", "name": "", "type": "tuple[]" } @@ -229,10 +229,10 @@ "outputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "", "type": "tuple" } @@ -251,7 +251,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple20[]", "name": "", "type": "tuple[]" } @@ -275,7 +275,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct Tuple23", "name": "", "type": "tuple" } @@ -369,10 +369,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -452,10 +452,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "admin", "type": "tuple" } @@ -485,10 +485,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "user", "type": "tuple" } @@ -593,7 +593,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple20[]", "name": "properties", "type": "tuple[]" } @@ -626,10 +626,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "sponsor", "type": "tuple" } @@ -643,10 +643,10 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "newOwner", "type": "tuple" } @@ -664,7 +664,7 @@ { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } ], - "internalType": "struct Tuple19[]", + "internalType": "struct Tuple20[]", "name": "properties", "type": "tuple[]" } @@ -782,19 +782,19 @@ "inputs": [ { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "from", "type": "tuple" }, { "components": [ - { "internalType": "address", "name": "field_0", "type": "address" }, - { "internalType": "uint256", "name": "field_1", "type": "uint256" } + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } ], - "internalType": "struct Tuple6", + "internalType": "struct EthCrossAccount", "name": "to", "type": "tuple" }, --- a/tests/src/eth/scheduling.test.ts +++ b/tests/src/eth/scheduling.test.ts @@ -27,7 +27,8 @@ }); itEth.ifWithPallets('Successfully schedules and periodically executes an EVM contract', [Pallets.Scheduler], async ({helper, privateKey}) => { - const alice = await privateKey('//Alice'); + const donor = await privateKey({filename: __filename}); + const [alice] = await helper.arrange.createAccounts([1000n], donor); const scheduledId = await helper.arrange.makeScheduledId(); --- a/tests/src/eth/util/playgrounds/types.ts +++ b/tests/src/eth/util/playgrounds/types.ts @@ -14,10 +14,8 @@ args: { [key: string]: string } }; export interface TEthCrossAccount { - readonly 0: string, - readonly 1: string | Uint8Array, - readonly field_0: string, - readonly field_1: string | Uint8Array, + readonly eth: string, + readonly sub: string | Uint8Array, } export type EthProperty = string[]; --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -364,19 +364,15 @@ export class EthCrossAccountGroup extends EthGroupBase { fromAddress(address: TEthereumAccount): TEthCrossAccount { return { - 0: address, - 1: '0', - field_0: address, - field_1: '0', + eth: address, + sub: '0', }; } fromKeyringPair(keyring: IKeyringPair): TEthCrossAccount { return { - 0: '0x0000000000000000000000000000000000000000', - 1: keyring.addressRaw, - field_0: '0x0000000000000000000000000000000000000000', - field_1: keyring.addressRaw, + eth: '0x0000000000000000000000000000000000000000', + sub: keyring.addressRaw, }; } } @@ -387,9 +383,9 @@ eth: EthGroup; ethAddress: EthAddressGroup; + ethCrossAccount: EthCrossAccountGroup; ethNativeContract: NativeContractGroup; ethContract: ContractGroup; - ethCrossAccount: EthCrossAccountGroup; ethProperty: EthPropertyGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { --- /dev/null +++ b/tests/src/inflation.seqtest.ts @@ -0,0 +1,58 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, usingPlaygrounds} from './util'; + +// todo:playgrounds requires sudo, look into on the later stage +describe('integration test: Inflation', () => { + let superuser: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (_, privateKey) => { + superuser = await privateKey('//Alice'); + }); + }); + + itSub('First year inflation is 10%', async ({helper}) => { + // Make sure non-sudo can't start inflation + const [bob] = await helper.arrange.createAccounts([10n], superuser); + + await expect(helper.executeExtrinsic(bob, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/); + + // Make sure superuser can't start inflation without explicit sudo + await expect(helper.executeExtrinsic(superuser, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/); + + // Start inflation on relay block 1 (Alice is sudo) + const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]); + await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected; + + const blockInterval = (helper.getApi().consts.inflation.inflationBlockInterval as any).toBigInt(); + const totalIssuanceStart = ((await helper.callRpc('api.query.inflation.startingYearTotalIssuance', [])) as any).toBigInt(); + const blockInflation = (await helper.callRpc('api.query.inflation.blockInflation', []) as any).toBigInt(); + + const YEAR = 5259600n; // 6-second block. Blocks in one year + // const YEAR = 2629800n; // 12-second block. Blocks in one year + + const totalExpectedInflation = totalIssuanceStart / 10n; + const totalActualInflation = blockInflation * YEAR / blockInterval; + + const tolerance = 0.00001; // Relative difference per year between theoretical and actual inflation + const expectedInflation = totalExpectedInflation / totalActualInflation - 1n; + + expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance); + }); +}); --- a/tests/src/inflation.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network 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. - -// Unique Network 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 Unique Network. If not, see . - -import {IKeyringPair} from '@polkadot/types/types'; -import {expect, itSub, usingPlaygrounds} from './util'; - -// todo:playgrounds requires sudo, look into on the later stage -describe('integration test: Inflation', () => { - let superuser: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (_, privateKey) => { - superuser = await privateKey('//Alice'); - }); - }); - - itSub('First year inflation is 10%', async ({helper}) => { - // Make sure non-sudo can't start inflation - const [bob] = await helper.arrange.createAccounts([10n], superuser); - - await expect(helper.executeExtrinsic(bob, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/); - - // Make sure superuser can't start inflation without explicit sudo - await expect(helper.executeExtrinsic(superuser, 'api.tx.inflation.startInflation', [1])).to.be.rejectedWith(/BadOrigin/); - - // Start inflation on relay block 1 (Alice is sudo) - const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]); - await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected; - - const blockInterval = (helper.getApi().consts.inflation.inflationBlockInterval as any).toBigInt(); - const totalIssuanceStart = ((await helper.callRpc('api.query.inflation.startingYearTotalIssuance', [])) as any).toBigInt(); - const blockInflation = (await helper.callRpc('api.query.inflation.blockInflation', []) as any).toBigInt(); - - const YEAR = 5259600n; // 6-second block. Blocks in one year - // const YEAR = 2629800n; // 12-second block. Blocks in one year - - const totalExpectedInflation = totalIssuanceStart / 10n; - const totalActualInflation = blockInflation * YEAR / blockInterval; - - const tolerance = 0.00001; // Relative difference per year between theoretical and actual inflation - const expectedInflation = totalExpectedInflation / totalActualInflation - 1n; - - expect(Math.abs(Number(expectedInflation))).to.be.lessThanOrEqual(tolerance); - }); -}); --- /dev/null +++ b/tests/src/scheduler.seqtest.ts @@ -0,0 +1,647 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +import {expect, itSub, Pallets, usingPlaygrounds} from './util'; +import {IKeyringPair} from '@polkadot/types/types'; +import {DevUniqueHelper} from './util/playgrounds/unique.dev'; + +describe('Scheduling token and balance transfers', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKeyWrapper) => { + alice = await privateKeyWrapper('//Alice'); + bob = await privateKeyWrapper('//Bob'); + charlie = await privateKeyWrapper('//Charlie'); + + await helper.testUtils.enable(); + }); + }); + + itSub.ifWithPallets('Can delay a transfer of an owned token', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + const schedulerId = await helper.arrange.makeScheduledId(); + const blocksBeforeExecution = 4; + + await token.scheduleAfter(schedulerId, blocksBeforeExecution) + .transfer(alice, {Substrate: bob.address}); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + + await helper.wait.newBlocks(blocksBeforeExecution + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets('Can transfer funds periodically', [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 1; + + const amount = 1n * helper.balance.getOneTokenNominal(); + const periodic = { + period: 2, + repetitions: 2, + }; + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .balance.transferToSubstrate(alice, bob.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterFirst) + .to.be.equal( + bobsBalanceBefore + 1n * amount, + '#1 Balance of the recipient should be increased by 1 * amount', + ); + + await helper.wait.newBlocks(periodic.period); + + const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterSecond) + .to.be.equal( + bobsBalanceBefore + 2n * amount, + '#2 Balance of the recipient should be increased by 2 * amount', + ); + }); + + itSub.ifWithPallets('Can cancel a scheduled operation which has not yet taken effect', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + await helper.scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); + }); + + itSub.ifWithPallets('Can cancel a periodic operation (transfer of funds)', [Pallets.Scheduler], async ({helper}) => { + const waitForBlocks = 1; + const periodic = { + period: 3, + repetitions: 2, + }; + + const scheduledId = await helper.arrange.makeScheduledId(); + + const amount = 1n * helper.balance.getOneTokenNominal(); + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .balance.transferToSubstrate(alice, bob.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); + + expect(bobsBalanceAfterFirst) + .to.be.equal( + bobsBalanceBefore + 1n * amount, + '#1 Balance of the recipient should be increased by 1 * amount', + ); + + await helper.scheduler.cancelScheduled(alice, scheduledId); + await helper.wait.newBlocks(periodic.period); + + const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); + expect(bobsBalanceAfterSecond) + .to.be.equal( + bobsBalanceAfterFirst, + '#2 Balance of the recipient should not be changed', + ); + }); + + itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const initTestVal = 42; + const changedTestVal = 111; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks) + .testUtils.setTestValueAndRollback(alice, changedTestVal); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const testVal = await helper.testUtils.testValue(); + expect(testVal, 'The test value should NOT be commited') + .to.be.equal(initTestVal); + }); + + itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.Scheduler, Pallets.TestUtils], async function({helper}) { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + const periodic = { + period: 2, + repetitions: 2, + }; + + const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []); + const scheduledLen = dummyTx.callIndex.length; + + const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen)) + .partialFee.toBigInt(); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .testUtils.justTakeFee(alice); + + await helper.wait.newBlocks(1); + + const aliceInitBalance = await helper.balance.getSubstrate(alice.address); + let diff; + + await helper.wait.newBlocks(waitForBlocks); + + const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address); + expect( + aliceBalanceAfterFirst < aliceInitBalance, + '[after execution #1] Scheduled task should take a fee', + ).to.be.true; + + diff = aliceInitBalance - aliceBalanceAfterFirst; + expect(diff).to.be.equal( + expectedScheduledFee, + 'Scheduled task should take the right amount of fees', + ); + + await helper.wait.newBlocks(periodic.period); + + const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address); + expect( + aliceBalanceAfterSecond < aliceBalanceAfterFirst, + '[after execution #2] Scheduled task should take a fee', + ).to.be.true; + + diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond; + expect(diff).to.be.equal( + expectedScheduledFee, + 'Scheduled task should take the right amount of fees', + ); + }); + + // Check if we can cancel a scheduled periodic operation + // in the same block in which it is running + itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const currentBlockNumber = await helper.chain.getLatestBlockNumber(); + const blocksBeforeExecution = 10; + const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; + + const [ + scheduledId, + scheduledCancelId, + ] = await helper.arrange.makeScheduledIds(2); + + const periodic = { + period: 5, + repetitions: 5, + }; + + const initTestVal = 0; + const incTestVal = initTestVal + 1; + const finalTestVal = initTestVal + 2; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAt(scheduledId, firstExecutionBlockNumber, {periodic}) + .testUtils.incTestValue(alice); + + // Cancel the inc tx after 2 executions + // *in the same block* in which the second execution is scheduled + await helper.scheduler.scheduleAt( + scheduledCancelId, + firstExecutionBlockNumber + periodic.period, + ).scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber); + + // execution #0 + expect(await helper.testUtils.testValue()) + .to.be.equal(incTestVal); + + await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period); + + // execution #1 + expect(await helper.testUtils.testValue()) + .to.be.equal(finalTestVal); + + for (let i = 1; i < periodic.repetitions; i++) { + await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period * (i + 1)); + expect(await helper.testUtils.testValue()) + .to.be.equal(finalTestVal); + } + }); + + itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + const periodic = { + period: 2, + repetitions: 5, + }; + + const initTestVal = 0; + const maxTestVal = 2; + + await helper.testUtils.setTestValue(alice, initTestVal); + + await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) + .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal); + + await helper.wait.newBlocks(waitForBlocks + 1); + + // execution #0 + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 1); + + await helper.wait.newBlocks(periodic.period); + + // execution #1 + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 2); + + await helper.wait.newBlocks(periodic.period); + + // + expect(await helper.testUtils.testValue()) + .to.be.equal(initTestVal + 2); + }); + + itSub.ifWithPallets('Root can cancel any scheduled operation', [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets('Root can set prioritized scheduled operation', [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const amount = 42n * helper.balance.getOneTokenNominal(); + + const balanceBefore = await helper.balance.getSubstrate(charlie.address); + + await helper.getSudo() + .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}) + .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const balanceAfter = await helper.balance.getSubstrate(charlie.address); + + expect(balanceAfter > balanceBefore).to.be.true; + + const diff = balanceAfter - balanceBefore; + expect(diff).to.be.equal(amount); + }); + + itSub.ifWithPallets("Root can change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 6; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + const priority = 112; + await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority); + + const priorityChanged = await helper.wait.event( + waitForBlocks, + 'scheduler', + 'PriorityChanged', + ); + + expect(priorityChanged !== null).to.be.true; + expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString()); + }); + + itSub.ifWithPallets('Prioritized operations executes in valid order', [Pallets.Scheduler], async ({helper}) => { + const [ + scheduledFirstId, + scheduledSecondId, + ] = await helper.arrange.makeScheduledIds(2); + + const currentBlockNumber = await helper.chain.getLatestBlockNumber(); + const blocksBeforeExecution = 6; + const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; + + const prioHigh = 0; + const prioLow = 255; + + const periodic = { + period: 6, + repetitions: 2, + }; + + const amount = 1n * helper.balance.getOneTokenNominal(); + + // Scheduler a task with a lower priority first, then with a higher priority + await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic}) + .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); + + await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic}) + .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); + + const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched'); + + await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber); + + // Flip priorities + await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh); + await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow); + + await helper.wait.forParachainBlockNumber(firstExecutionBlockNumber + periodic.period); + + const dispatchEvents = capture.extractCapturedEvents(); + expect(dispatchEvents.length).to.be.equal(4); + + const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString()); + + const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]]; + const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]]; + + expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId); + expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId); + + expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId); + expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId); + }); +}); + +describe('Negative Test: Scheduling', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (helper, privateKeyWrapper) => { + alice = await privateKeyWrapper('//Alice'); + bob = await privateKeyWrapper('//Bob'); + + await helper.testUtils.enable(); + }); + }); + + itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks); + await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal())) + .to.be.rejectedWith(/scheduler\.FailedToSchedule/); + + const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter); + }); + + itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + await expect(helper.scheduler.cancelScheduled(alice, scheduledId)) + .to.be.rejectedWith(/scheduler\.NotFound/); + }); + + itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(alice); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(alice, {Substrate: bob.address}); + + await expect(helper.scheduler.cancelScheduled(bob, scheduledId)) + .to.be.rejectedWith(/BadOrigin/); + + await helper.wait.newBlocks(waitForBlocks + 1); + + expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); + }); + + itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => { + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + const amount = 42n * helper.balance.getOneTokenNominal(); + + const balanceBefore = await helper.balance.getSubstrate(bob.address); + + const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}); + + await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount)) + .to.be.rejectedWith(/BadOrigin/); + + await helper.wait.newBlocks(waitForBlocks + 1); + + const balanceAfter = await helper.balance.getSubstrate(bob.address); + + expect(balanceAfter).to.be.equal(balanceBefore); + }); + + itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { + const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); + const token = await collection.mintToken(bob); + + const scheduledId = await helper.arrange.makeScheduledId(); + const waitForBlocks = 4; + + await token.scheduleAfter(scheduledId, waitForBlocks) + .transfer(bob, {Substrate: alice.address}); + + const priority = 112; + await expect(helper.scheduler.changePriority(alice, scheduledId, priority)) + .to.be.rejectedWith(/BadOrigin/); + + const priorityChanged = await helper.wait.event( + waitForBlocks, + 'scheduler', + 'PriorityChanged', + ); + + expect(priorityChanged === null).to.be.true; + }); +}); + +// Implementation of the functionality tested here was postponed/shelved +describe.skip('Sponsoring scheduling', () => { + // let alice: IKeyringPair; + // let bob: IKeyringPair; + + // before(async() => { + // await usingApi(async (_, privateKeyWrapper) => { + // alice = privateKeyWrapper('//Alice'); + // bob = privateKeyWrapper('//Bob'); + // }); + // }); + + it('Can sponsor scheduling a transaction', async () => { + // const collectionId = await createCollectionExpectSuccess(); + // await setCollectionSponsorExpectSuccess(collectionId, bob.address); + // await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); + + // await usingApi(async api => { + // const scheduledId = await makeScheduledId(); + // const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + // const bobBalanceBefore = await getFreeBalance(bob); + // const waitForBlocks = 4; + // // no need to wait to check, fees must be deducted on scheduling, immediately + // await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId); + // const bobBalanceAfter = await getFreeBalance(bob); + // // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true; + // expect(bobBalanceAfter < bobBalanceBefore).to.be.true; + // // wait for sequentiality matters + // await waitNewBlocks(waitForBlocks - 1); + // }); + }); + + it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => { + // await usingApi(async (api, privateKeyWrapper) => { + // // Find an empty, unused account + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + // const collectionId = await createCollectionExpectSuccess(); + + // // Add zeroBalance address to allow list + // await enablePublicMintingExpectSuccess(alice, collectionId); + // await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); + + // // Grace zeroBalance with money, enough to cover future transactions + // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); + // await submitTransactionAsync(alice, balanceTx); + + // // Mint a fresh NFT + // const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT'); + // const scheduledId = await makeScheduledId(); + + // // Schedule transfer of the NFT a few blocks ahead + // const waitForBlocks = 5; + // await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId); + + // // Get rid of the account's funds before the scheduled transaction takes place + // const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n); + // const events = await submitTransactionAsync(zeroBalance, balanceTx2); + // expect(getGenericResult(events).success).to.be.true; + // /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved? + // const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any); + // const events = await submitTransactionAsync(alice, sudoTx); + // expect(getGenericResult(events).success).to.be.true;*/ + + // // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions + // await waitNewBlocks(waitForBlocks - 3); + + // expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); + // }); + }); + + it('Sponsor going bankrupt does not impact a scheduled transaction', async () => { + // const collectionId = await createCollectionExpectSuccess(); + + // await usingApi(async (api, privateKeyWrapper) => { + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); + // await submitTransactionAsync(alice, balanceTx); + + // await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address); + // await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance); + + // const scheduledId = await makeScheduledId(); + // const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + // const waitForBlocks = 5; + // await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId); + + // const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); + // const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any); + // const events = await submitTransactionAsync(alice, sudoTx); + // expect(getGenericResult(events).success).to.be.true; + + // // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions + // await waitNewBlocks(waitForBlocks - 3); + + // expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address)); + // }); + }); + + it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => { + // const collectionId = await createCollectionExpectSuccess(); + // await setCollectionSponsorExpectSuccess(collectionId, bob.address); + // await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); + + // await usingApi(async (api, privateKeyWrapper) => { + // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + // await enablePublicMintingExpectSuccess(alice, collectionId); + // await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); + + // const bobBalanceBefore = await getFreeBalance(bob); + + // const createData = {nft: {const_data: [], variable_data: []}}; + // const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any); + // const scheduledId = await makeScheduledId(); + + // /*const badTransaction = async function () { + // await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); + // }; + // await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/ + + // await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/); + + // expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore); + // }); + }); +}); --- a/tests/src/scheduler.test.ts +++ /dev/null @@ -1,647 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network 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. - -// Unique Network 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 Unique Network. If not, see . - -import {expect, itSub, Pallets, usingPlaygrounds} from './util'; -import {IKeyringPair} from '@polkadot/types/types'; -import {DevUniqueHelper} from './util/playgrounds/unique.dev'; - -describe('Scheduling token and balance transfers', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let charlie: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKeyWrapper) => { - alice = await privateKeyWrapper('//Alice'); - bob = await privateKeyWrapper('//Bob'); - charlie = await privateKeyWrapper('//Charlie'); - - await helper.testUtils.enable(); - }); - }); - - itSub.ifWithPallets('Can delay a transfer of an owned token', [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(alice); - const schedulerId = await helper.arrange.makeScheduledId(); - const blocksBeforeExecution = 4; - - await token.scheduleAfter(schedulerId, blocksBeforeExecution) - .transfer(alice, {Substrate: bob.address}); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); - - await helper.wait.newBlocks(blocksBeforeExecution + 1); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); - - itSub.ifWithPallets('Can transfer funds periodically', [Pallets.Scheduler], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 1; - - const amount = 1n * helper.balance.getOneTokenNominal(); - const periodic = { - period: 2, - repetitions: 2, - }; - - const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); - - await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) - .balance.transferToSubstrate(alice, bob.address, amount); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); - expect(bobsBalanceAfterFirst) - .to.be.equal( - bobsBalanceBefore + 1n * amount, - '#1 Balance of the recipient should be increased by 1 * amount', - ); - - await helper.wait.newBlocks(periodic.period); - - const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); - expect(bobsBalanceAfterSecond) - .to.be.equal( - bobsBalanceBefore + 2n * amount, - '#2 Balance of the recipient should be increased by 2 * amount', - ); - }); - - itSub.ifWithPallets('Can cancel a scheduled operation which has not yet taken effect', [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(alice); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(alice, {Substrate: bob.address}); - - await helper.scheduler.cancelScheduled(alice, scheduledId); - - await helper.wait.newBlocks(waitForBlocks + 1); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); - }); - - itSub.ifWithPallets('Can cancel a periodic operation (transfer of funds)', [Pallets.Scheduler], async ({helper}) => { - const waitForBlocks = 1; - const periodic = { - period: 3, - repetitions: 2, - }; - - const scheduledId = await helper.arrange.makeScheduledId(); - - const amount = 1n * helper.balance.getOneTokenNominal(); - - const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); - - await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) - .balance.transferToSubstrate(alice, bob.address, amount); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const bobsBalanceAfterFirst = await helper.balance.getSubstrate(bob.address); - - expect(bobsBalanceAfterFirst) - .to.be.equal( - bobsBalanceBefore + 1n * amount, - '#1 Balance of the recipient should be increased by 1 * amount', - ); - - await helper.scheduler.cancelScheduled(alice, scheduledId); - await helper.wait.newBlocks(periodic.period); - - const bobsBalanceAfterSecond = await helper.balance.getSubstrate(bob.address); - expect(bobsBalanceAfterSecond) - .to.be.equal( - bobsBalanceAfterFirst, - '#2 Balance of the recipient should not be changed', - ); - }); - - itSub.ifWithPallets('Scheduled tasks are transactional', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - const initTestVal = 42; - const changedTestVal = 111; - - await helper.testUtils.setTestValue(alice, initTestVal); - - await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks) - .testUtils.setTestValueAndRollback(alice, changedTestVal); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const testVal = await helper.testUtils.testValue(); - expect(testVal, 'The test value should NOT be commited') - .to.be.equal(initTestVal); - }); - - itSub.ifWithPallets('Scheduled tasks should take correct fees', [Pallets.Scheduler, Pallets.TestUtils], async function({helper}) { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - const periodic = { - period: 2, - repetitions: 2, - }; - - const dummyTx = helper.constructApiCall('api.tx.testUtils.justTakeFee', []); - const scheduledLen = dummyTx.callIndex.length; - - const expectedScheduledFee = (await helper.getPaymentInfo(alice, dummyTx, scheduledLen)) - .partialFee.toBigInt(); - - await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) - .testUtils.justTakeFee(alice); - - await helper.wait.newBlocks(1); - - const aliceInitBalance = await helper.balance.getSubstrate(alice.address); - let diff; - - await helper.wait.newBlocks(waitForBlocks); - - const aliceBalanceAfterFirst = await helper.balance.getSubstrate(alice.address); - expect( - aliceBalanceAfterFirst < aliceInitBalance, - '[after execution #1] Scheduled task should take a fee', - ).to.be.true; - - diff = aliceInitBalance - aliceBalanceAfterFirst; - expect(diff).to.be.equal( - expectedScheduledFee, - 'Scheduled task should take the right amount of fees', - ); - - await helper.wait.newBlocks(periodic.period); - - const aliceBalanceAfterSecond = await helper.balance.getSubstrate(alice.address); - expect( - aliceBalanceAfterSecond < aliceBalanceAfterFirst, - '[after execution #2] Scheduled task should take a fee', - ).to.be.true; - - diff = aliceBalanceAfterFirst - aliceBalanceAfterSecond; - expect(diff).to.be.equal( - expectedScheduledFee, - 'Scheduled task should take the right amount of fees', - ); - }); - - // Check if we can cancel a scheduled periodic operation - // in the same block in which it is running - itSub.ifWithPallets('Can cancel the periodic sheduled tx when the tx is running', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { - const currentBlockNumber = await helper.chain.getLatestBlockNumber(); - const blocksBeforeExecution = 10; - const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; - - const [ - scheduledId, - scheduledCancelId, - ] = await helper.arrange.makeScheduledIds(2); - - const periodic = { - period: 5, - repetitions: 5, - }; - - const initTestVal = 0; - const incTestVal = initTestVal + 1; - const finalTestVal = initTestVal + 2; - - await helper.testUtils.setTestValue(alice, initTestVal); - - await helper.scheduler.scheduleAt(scheduledId, firstExecutionBlockNumber, {periodic}) - .testUtils.incTestValue(alice); - - // Cancel the inc tx after 2 executions - // *in the same block* in which the second execution is scheduled - await helper.scheduler.scheduleAt( - scheduledCancelId, - firstExecutionBlockNumber + periodic.period, - ).scheduler.cancelScheduled(alice, scheduledId); - - await helper.wait.newBlocks(blocksBeforeExecution); - - // execution #0 - expect(await helper.testUtils.testValue()) - .to.be.equal(incTestVal); - - await helper.wait.newBlocks(periodic.period); - - // execution #1 - expect(await helper.testUtils.testValue()) - .to.be.equal(finalTestVal); - - for (let i = 1; i < periodic.repetitions; i++) { - await helper.wait.newBlocks(periodic.period); - expect(await helper.testUtils.testValue()) - .to.be.equal(finalTestVal); - } - }); - - itSub.ifWithPallets('A scheduled operation can cancel itself', [Pallets.Scheduler, Pallets.TestUtils], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - const periodic = { - period: 2, - repetitions: 5, - }; - - const initTestVal = 0; - const maxTestVal = 2; - - await helper.testUtils.setTestValue(alice, initTestVal); - - await helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {periodic}) - .testUtils.selfCancelingInc(alice, scheduledId, maxTestVal); - - await helper.wait.newBlocks(waitForBlocks + 1); - - // execution #0 - expect(await helper.testUtils.testValue()) - .to.be.equal(initTestVal + 1); - - await helper.wait.newBlocks(periodic.period); - - // execution #1 - expect(await helper.testUtils.testValue()) - .to.be.equal(initTestVal + 2); - - await helper.wait.newBlocks(periodic.period); - - // - expect(await helper.testUtils.testValue()) - .to.be.equal(initTestVal + 2); - }); - - itSub.ifWithPallets('Root can cancel any scheduled operation', [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(bob); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(bob, {Substrate: alice.address}); - - await helper.getSudo().scheduler.cancelScheduled(alice, scheduledId); - - await helper.wait.newBlocks(waitForBlocks + 1); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); - - itSub.ifWithPallets('Root can set prioritized scheduled operation', [Pallets.Scheduler], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - const amount = 42n * helper.balance.getOneTokenNominal(); - - const balanceBefore = await helper.balance.getSubstrate(charlie.address); - - await helper.getSudo() - .scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}) - .balance.forceTransferToSubstrate(alice, bob.address, charlie.address, amount); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const balanceAfter = await helper.balance.getSubstrate(charlie.address); - - expect(balanceAfter > balanceBefore).to.be.true; - - const diff = balanceAfter - balanceBefore; - expect(diff).to.be.equal(amount); - }); - - itSub.ifWithPallets("Root can change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(bob); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 6; - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(bob, {Substrate: alice.address}); - - const priority = 112; - await helper.getSudo().scheduler.changePriority(alice, scheduledId, priority); - - const priorityChanged = await helper.wait.event( - waitForBlocks, - 'scheduler', - 'PriorityChanged', - ); - - expect(priorityChanged !== null).to.be.true; - expect(priorityChanged!.event.data[2].toString()).to.be.equal(priority.toString()); - }); - - itSub.ifWithPallets('Prioritized operations executes in valid order', [Pallets.Scheduler], async ({helper}) => { - const [ - scheduledFirstId, - scheduledSecondId, - ] = await helper.arrange.makeScheduledIds(2); - - const currentBlockNumber = await helper.chain.getLatestBlockNumber(); - const blocksBeforeExecution = 4; - const firstExecutionBlockNumber = currentBlockNumber + blocksBeforeExecution; - - const prioHigh = 0; - const prioLow = 255; - - const periodic = { - period: 6, - repetitions: 2, - }; - - const amount = 1n * helper.balance.getOneTokenNominal(); - - // Scheduler a task with a lower priority first, then with a higher priority - await helper.getSudo().scheduler.scheduleAt(scheduledFirstId, firstExecutionBlockNumber, {priority: prioLow, periodic}) - .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); - - await helper.getSudo().scheduler.scheduleAt(scheduledSecondId, firstExecutionBlockNumber, {priority: prioHigh, periodic}) - .balance.forceTransferToSubstrate(alice, alice.address, bob.address, amount); - - const capture = await helper.arrange.captureEvents('scheduler', 'Dispatched'); - - await helper.wait.newBlocks(blocksBeforeExecution); - - // Flip priorities - await helper.getSudo().scheduler.changePriority(alice, scheduledFirstId, prioHigh); - await helper.getSudo().scheduler.changePriority(alice, scheduledSecondId, prioLow); - - await helper.wait.newBlocks(periodic.period); - - const dispatchEvents = capture.extractCapturedEvents(); - expect(dispatchEvents.length).to.be.equal(4); - - const dispatchedIds = dispatchEvents.map(r => r.event.data[1].toString()); - - const firstExecuctionIds = [dispatchedIds[0], dispatchedIds[1]]; - const secondExecuctionIds = [dispatchedIds[2], dispatchedIds[3]]; - - expect(firstExecuctionIds[0]).to.be.equal(scheduledSecondId); - expect(firstExecuctionIds[1]).to.be.equal(scheduledFirstId); - - expect(secondExecuctionIds[0]).to.be.equal(scheduledFirstId); - expect(secondExecuctionIds[1]).to.be.equal(scheduledSecondId); - }); -}); - -describe('Negative Test: Scheduling', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKeyWrapper) => { - alice = await privateKeyWrapper('//Alice'); - bob = await privateKeyWrapper('//Bob'); - - await helper.testUtils.enable(); - }); - }); - - itSub.ifWithPallets("Can't overwrite a scheduled ID", [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(alice); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(alice, {Substrate: bob.address}); - - const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks); - await expect(scheduled.balance.transferToSubstrate(alice, bob.address, 1n * helper.balance.getOneTokenNominal())) - .to.be.rejectedWith(/scheduler\.FailedToSchedule/); - - const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(bobsBalanceBefore).to.be.equal(bobsBalanceAfter); - }); - - itSub.ifWithPallets("Can't cancel an operation which is not scheduled", [Pallets.Scheduler], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - await expect(helper.scheduler.cancelScheduled(alice, scheduledId)) - .to.be.rejectedWith(/scheduler\.NotFound/); - }); - - itSub.ifWithPallets("Can't cancel a non-owned scheduled operation", [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(alice); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(alice, {Substrate: bob.address}); - - await expect(helper.scheduler.cancelScheduled(bob, scheduledId)) - .to.be.rejectedWith(/BadOrigin/); - - await helper.wait.newBlocks(waitForBlocks + 1); - - expect(await token.getOwner()).to.be.deep.equal({Substrate: bob.address}); - }); - - itSub.ifWithPallets("Regular user can't set prioritized scheduled operation", [Pallets.Scheduler], async ({helper}) => { - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - const amount = 42n * helper.balance.getOneTokenNominal(); - - const balanceBefore = await helper.balance.getSubstrate(bob.address); - - const scheduled = helper.scheduler.scheduleAfter(scheduledId, waitForBlocks, {priority: 42}); - - await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount)) - .to.be.rejectedWith(/BadOrigin/); - - await helper.wait.newBlocks(waitForBlocks + 1); - - const balanceAfter = await helper.balance.getSubstrate(bob.address); - - expect(balanceAfter).to.be.equal(balanceBefore); - }); - - itSub.ifWithPallets("Regular user can't change scheduled operation's priority", [Pallets.Scheduler], async ({helper}) => { - const collection = await helper.nft.mintCollection(bob, {tokenPrefix: 'schd'}); - const token = await collection.mintToken(bob); - - const scheduledId = await helper.arrange.makeScheduledId(); - const waitForBlocks = 4; - - await token.scheduleAfter(scheduledId, waitForBlocks) - .transfer(bob, {Substrate: alice.address}); - - const priority = 112; - await expect(helper.scheduler.changePriority(alice, scheduledId, priority)) - .to.be.rejectedWith(/BadOrigin/); - - const priorityChanged = await helper.wait.event( - waitForBlocks, - 'scheduler', - 'PriorityChanged', - ); - - expect(priorityChanged === null).to.be.true; - }); -}); - -// Implementation of the functionality tested here was postponed/shelved -describe.skip('Sponsoring scheduling', () => { - // let alice: IKeyringPair; - // let bob: IKeyringPair; - - // before(async() => { - // await usingApi(async (_, privateKeyWrapper) => { - // alice = privateKeyWrapper('//Alice'); - // bob = privateKeyWrapper('//Bob'); - // }); - // }); - - it('Can sponsor scheduling a transaction', async () => { - // const collectionId = await createCollectionExpectSuccess(); - // await setCollectionSponsorExpectSuccess(collectionId, bob.address); - // await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); - - // await usingApi(async api => { - // const scheduledId = await makeScheduledId(); - // const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); - - // const bobBalanceBefore = await getFreeBalance(bob); - // const waitForBlocks = 4; - // // no need to wait to check, fees must be deducted on scheduling, immediately - // await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, bob, 0, waitForBlocks, scheduledId); - // const bobBalanceAfter = await getFreeBalance(bob); - // // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true; - // expect(bobBalanceAfter < bobBalanceBefore).to.be.true; - // // wait for sequentiality matters - // await waitNewBlocks(waitForBlocks - 1); - // }); - }); - - it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => { - // await usingApi(async (api, privateKeyWrapper) => { - // // Find an empty, unused account - // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - // const collectionId = await createCollectionExpectSuccess(); - - // // Add zeroBalance address to allow list - // await enablePublicMintingExpectSuccess(alice, collectionId); - // await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); - - // // Grace zeroBalance with money, enough to cover future transactions - // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); - // await submitTransactionAsync(alice, balanceTx); - - // // Mint a fresh NFT - // const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT'); - // const scheduledId = await makeScheduledId(); - - // // Schedule transfer of the NFT a few blocks ahead - // const waitForBlocks = 5; - // await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId); - - // // Get rid of the account's funds before the scheduled transaction takes place - // const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n); - // const events = await submitTransactionAsync(zeroBalance, balanceTx2); - // expect(getGenericResult(events).success).to.be.true; - // /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved? - // const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any); - // const events = await submitTransactionAsync(alice, sudoTx); - // expect(getGenericResult(events).success).to.be.true;*/ - - // // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions - // await waitNewBlocks(waitForBlocks - 3); - - // expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); - // }); - }); - - it('Sponsor going bankrupt does not impact a scheduled transaction', async () => { - // const collectionId = await createCollectionExpectSuccess(); - - // await usingApi(async (api, privateKeyWrapper) => { - // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); - // await submitTransactionAsync(alice, balanceTx); - - // await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address); - // await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance); - - // const scheduledId = await makeScheduledId(); - // const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); - - // const waitForBlocks = 5; - // await scheduleTransferExpectSuccess(api, collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, scheduledId); - - // const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); - // const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any); - // const events = await submitTransactionAsync(alice, sudoTx); - // expect(getGenericResult(events).success).to.be.true; - - // // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions - // await waitNewBlocks(waitForBlocks - 3); - - // expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address)); - // }); - }); - - it('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => { - // const collectionId = await createCollectionExpectSuccess(); - // await setCollectionSponsorExpectSuccess(collectionId, bob.address); - // await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); - - // await usingApi(async (api, privateKeyWrapper) => { - // const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - // await enablePublicMintingExpectSuccess(alice, collectionId); - // await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); - - // const bobBalanceBefore = await getFreeBalance(bob); - - // const createData = {nft: {const_data: [], variable_data: []}}; - // const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any); - // const scheduledId = await makeScheduledId(); - - // /*const badTransaction = async function () { - // await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); - // }; - // await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/ - - // await expect(scheduleAfter(api, creationTx, zeroBalance, 3, scheduledId, 1, 3)).to.be.rejectedWith(/Inability to pay some fees/); - - // expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore); - // }); - }); -}); --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -73,8 +73,8 @@ export interface IEthCrossAccountId { 0: TEthereumAccount; 1: TSubstrateAccount; - field_0: TEthereumAccount; - field_1: TSubstrateAccount; + eth: TEthereumAccount; + sub: TSubstrateAccount; } export interface ICollectionLimits { --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -422,8 +422,8 @@ return promise; } - async forParachainBlockNumber(blockNumber: bigint, timeout?: number) { - timeout = timeout ?? 300_000; + async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) { + timeout = timeout ?? 30 * 60 * 1000; // eslint-disable-next-line no-async-promise-executor const promise = new Promise(async (resolve) => { const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => { @@ -437,8 +437,8 @@ return promise; } - async forRelayBlockNumber(blockNumber: bigint, timeout?: number) { - timeout = timeout ?? 300_000; + async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) { + timeout = timeout ?? 30 * 60 * 1000; // eslint-disable-next-line no-async-promise-executor const promise = new Promise(async (resolve) => { const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -7,8 +7,10 @@ import {ApiPromise, WsProvider, Keyring} from '@polkadot/api'; import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types'; -import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; +import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; +import {hexToU8a} from '@polkadot/util/hex'; +import {u8aConcat} from '@polkadot/util/u8a'; import { IApiListeners, IBlock, @@ -38,6 +40,7 @@ AcalaAssetMetadata, MoonbeamAssetInfo, DemocracyStandardAccountVote, + IEthCrossAccountId, } from './types'; import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; @@ -2368,6 +2371,73 @@ return CrossAccountId.translateSubToEth(subAddress); } + /** + * Encode key to substrate address + * @param key key for encoding address + * @param ss58Format prefix for encoding to the address of the corresponding network + * @returns encoded substrate address + */ + encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string { + const u8a :Uint8Array = typeof key === 'string' + ? hexToU8a(key) + : typeof key === 'bigint' + ? hexToU8a(key.toString(16)) + : key; + + if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) { + throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`); + } + + const allowedDecodedLengths = [1, 2, 4, 8, 32, 33]; + if (!allowedDecodedLengths.includes(u8a.length)) { + throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`); + } + + const u8aPrefix = ss58Format < 64 + ? new Uint8Array([ss58Format]) + : new Uint8Array([ + ((ss58Format & 0xfc) >> 2) | 0x40, + (ss58Format >> 8) | ((ss58Format & 0x03) << 6), + ]); + + const input = u8aConcat(u8aPrefix, u8a); + + return base58Encode(u8aConcat( + input, + blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1), + )); + } + + /** + * Restore substrate address from bigint representation + * @param number decimal representation of substrate address + * @returns substrate address + */ + restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount { + if (this.helper.api === null) { + throw 'Not connected'; + } + const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON(); + if (res === undefined || res === null) { + throw 'Restore address error'; + } + return res.toString(); + } + + /** + * Convert etherium cross account id to substrate cross account id + * @param ethCrossAccount etherium cross account + * @returns substrate cross account id + */ + convertCrossAccountFromEthCrossAcoount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId { + if (ethCrossAccount.sub === '0') { + return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()}; + } + + const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub)); + return {Substrate: ss58}; + } + paraSiblingSovereignAccount(paraid: number) { // We are getting a *sibling* parachain sovereign account, // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c -- gitstuff