diff --git a/Cargo.lock b/Cargo.lock index d59225a28..582a28358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3235,6 +3235,8 @@ dependencies = [ "crypto-utils", "eip712-account-claim", "eip712-common", + "eip712-common-test-utils", + "eip712-token-claim", "fp-rpc", "fp-self-contained", "frame-benchmarking", @@ -3247,6 +3249,7 @@ dependencies = [ "frontier-api", "hex-literal", "keystore-bioauth-account-id", + "libsecp256k1 0.7.0", "pallet-authorship", "pallet-babe", "pallet-balances", @@ -3254,6 +3257,7 @@ dependencies = [ "pallet-bioauth", "pallet-bootnodes", "pallet-chain-properties", + "pallet-chain-start-moment", "pallet-dynamic-fee", "pallet-ethereum", "pallet-ethereum-chain-id", @@ -3271,8 +3275,10 @@ dependencies = [ "pallet-session", "pallet-sudo", "pallet-timestamp", + "pallet-token-claims", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-vesting", "parity-scale-codec", "precompile-bioauth", "precompile-evm-accounts-mapping", @@ -3296,6 +3302,8 @@ dependencies = [ "sp-version", "static_assertions", "substrate-wasm-builder", + "vesting-schedule-linear", + "vesting-scheduling-timestamp", ] [[package]] diff --git a/crates/crypto-utils/Cargo.toml b/crates/crypto-utils/Cargo.toml index af5459627..3fd0f54c2 100644 --- a/crates/crypto-utils/Cargo.toml +++ b/crates/crypto-utils/Cargo.toml @@ -5,7 +5,11 @@ edition = "2021" publish = false [dependencies] -pallet-im-online = { git = "https://github.com/humanode-network/substrate", branch = "master" } -sp-consensus-babe = { git = "https://github.com/humanode-network/substrate", branch = "master" } -sp-finality-grandpa = { git = "https://github.com/humanode-network/substrate", branch = "master" } -sp-runtime = { git = "https://github.com/humanode-network/substrate", branch = "master" } +pallet-im-online = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +sp-consensus-babe = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +sp-finality-grandpa = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +sp-runtime = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } + +[features] +default = ["std"] +std = ["pallet-im-online/std", "sp-consensus-babe/std", "sp-finality-grandpa/std", "sp-runtime/std"] diff --git a/crates/crypto-utils/src/lib.rs b/crates/crypto-utils/src/lib.rs index d6d5958b5..449a3a9f1 100644 --- a/crates/crypto-utils/src/lib.rs +++ b/crates/crypto-utils/src/lib.rs @@ -1,5 +1,7 @@ //! Various crypto helper functions. +#![cfg_attr(not(feature = "std"), no_std)] + use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_consensus_babe::AuthorityId as BabeId; use sp_finality_grandpa::AuthorityId as GrandpaId; diff --git a/crates/eip712-account-claim/src/lib.rs b/crates/eip712-account-claim/src/lib.rs index c29503704..fbbe1c702 100644 --- a/crates/eip712-account-claim/src/lib.rs +++ b/crates/eip712-account-claim/src/lib.rs @@ -18,14 +18,21 @@ fn make_account_claim_hash(account: &EthBytes) -> [u8; 32] { keccak_256(&buf) } -/// Verify EIP-712 typed signature based on provided domain_separator and entire message. -pub fn verify_account_claim( +/// Prepare the EIP-712 message hash. +pub fn make_message_hash(domain: Domain<'_>, account: &[u8]) -> [u8; 32] { + let payload_hash = make_account_claim_hash(account); + eip712_common::make_message_hash(domain, &payload_hash) +} + +/// Verify EIP-712 typed signature based on provided domain and message params and recover +/// the signer address. +pub fn recover_signer( signature: &EcdsaSignature, domain: Domain<'_>, account: &[u8], ) -> Option { - let payload_hash = make_account_claim_hash(account); - eip712_common::verify_signature(signature, domain, &payload_hash) + let message = make_message_hash(domain, account); + eip712_common::recover_signer(signature, &message) } #[cfg(test)] @@ -85,7 +92,7 @@ mod tests { let signature = test_input(&pair); let domain = prepare_sample_domain(); - let ethereum_address = verify_account_claim(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); + let ethereum_address = recover_signer(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); assert_eq!(ethereum_address, ethereum_address_from_seed(b"Alice")); } @@ -95,7 +102,7 @@ mod tests { let signature = test_input(&pair); let domain = prepare_sample_domain(); - let ethereum_address = verify_account_claim(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); + let ethereum_address = recover_signer(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); assert_ne!(ethereum_address, ethereum_address_from_seed(b"Bob")); } @@ -113,7 +120,7 @@ mod tests { let signature = hex!("151d5f52e6c249db84b8705374c6f51dd08b50ddad5b1175ec20a7e00cbc48f55a23470ab0db16146b3b7d2a8565aaf2b700f548c9e9882a0034e654bd214e821b"); let ethereum_address = - verify_account_claim(&EcdsaSignature(signature), domain, &account_to_claim).unwrap(); + recover_signer(&EcdsaSignature(signature), domain, &account_to_claim).unwrap(); assert_eq!( ethereum_address.0, hex!("e9726f3d0a7736034e2a4c63ea28b3ab95622cb9"), diff --git a/crates/eip712-common-test-utils/Cargo.toml b/crates/eip712-common-test-utils/Cargo.toml index e2a6c506c..728d70f9e 100644 --- a/crates/eip712-common-test-utils/Cargo.toml +++ b/crates/eip712-common-test-utils/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -eip712-common = { version = "0.1", path = "../eip712-common" } -primitives-ethereum = { version = "0.1", path = "../primitives-ethereum" } +eip712-common = { default-features = false, version = "0.1", path = "../eip712-common" } +primitives-ethereum = { default-features = false, version = "0.1", path = "../primitives-ethereum" } -eth-eip-712 = { package = "eip-712", version = "=0.1.0" } +eth-eip-712 = { package = "eip-712", version = "=0.1.0", optional = true } hex-literal = "0.3" -secp256k1 = "0.22" -serde_json = "1" -sp-core = { git = "https://github.com/humanode-network/substrate", branch = "master" } +secp256k1 = { version = "0.22", optional = true } +serde_json = { version = "1", optional = true } +sp-core = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } + +[features] +default = ["std"] +std = ["eth-eip-712", "secp256k1", "serde_json", "eip712-common/std", "primitives-ethereum/std", "sp-core/std"] diff --git a/crates/eip712-common-test-utils/src/lib.rs b/crates/eip712-common-test-utils/src/lib.rs index 78714f576..05746c864 100644 --- a/crates/eip712-common-test-utils/src/lib.rs +++ b/crates/eip712-common-test-utils/src/lib.rs @@ -1,5 +1,7 @@ //! Common test utils for EIP-712 typed data message construction and signature verification. +#![cfg_attr(not(feature = "std"), no_std)] + use eip712_common::*; use primitives_ethereum::{EcdsaSignature, EthereumAddress}; pub use sp_core::{crypto::Pair, ecdsa, H256, U256}; @@ -10,8 +12,8 @@ pub fn ecdsa_pair(seed: &[u8]) -> ecdsa::Pair { } /// Sign a given message with the given ECDSA keypair. -pub fn ecdsa_sign(pair: &ecdsa::Pair, msg: [u8; 32]) -> EcdsaSignature { - EcdsaSignature(pair.sign_prehashed(&msg).0) +pub fn ecdsa_sign(pair: &ecdsa::Pair, msg: &[u8; 32]) -> EcdsaSignature { + EcdsaSignature(pair.sign_prehashed(msg).0) } /// Sign a given EIP-712 typed data JSON with the given ECDSA keypair. @@ -20,7 +22,7 @@ pub fn ecdsa_sign_typed_data(pair: &ecdsa::Pair, type_data_json: &str) -> EcdsaS let msg_bytes: [u8; 32] = eth_eip_712::hash_structured_data(typed_data) .unwrap() .into(); - ecdsa_sign(pair, msg_bytes) + ecdsa_sign(pair, &msg_bytes) } /// Create an Ethereum address from the given seed. diff --git a/crates/eip712-common/src/lib.rs b/crates/eip712-common/src/lib.rs index 09b5347f3..a07bd1c93 100644 --- a/crates/eip712-common/src/lib.rs +++ b/crates/eip712-common/src/lib.rs @@ -76,23 +76,28 @@ pub fn make_payload_hash<'a>( keccak_256(&buf) } +/// Prepare the EIP-712 message. +pub fn make_message_hash(domain: Domain<'_>, payload_hash: &[u8; 32]) -> [u8; 32] { + let domain_hash = make_domain_hash(domain); + make_eip712_message_hash(&domain_hash, payload_hash) +} + +/// Extract the signer address from the signature and the message. +pub fn recover_signer(sig: &EcdsaSignature, msg: &[u8; 32]) -> Option { + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig.0, msg).ok()?; + Some(ecdsa_public_key_to_ethereum_address(&pubkey)) +} + /// Verify EIP-712 typed signature based on provided domain and payload hash. pub fn verify_signature( signature: &EcdsaSignature, domain: Domain<'_>, payload_hash: &[u8; 32], ) -> Option { - let domain_hash = make_domain_hash(domain); - let msg_hash = make_eip712_message_hash(&domain_hash, payload_hash); + let msg_hash = make_message_hash(domain, payload_hash); recover_signer(signature, &msg_hash) } -/// Extract the signer address from the signatue and the message. -fn recover_signer(sig: &EcdsaSignature, msg: &[u8; 32]) -> Option { - let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig.0, msg).ok()?; - Some(ecdsa_public_key_to_ethereum_address(&pubkey)) -} - /// Convert the ECDSA public key to Ethereum address. fn ecdsa_public_key_to_ethereum_address(pubkey: &[u8; 64]) -> EthereumAddress { let mut address = [0u8; 20]; diff --git a/crates/eip712-token-claim/src/lib.rs b/crates/eip712-token-claim/src/lib.rs index 5bb290268..9c37e71ed 100644 --- a/crates/eip712-token-claim/src/lib.rs +++ b/crates/eip712-token-claim/src/lib.rs @@ -15,17 +15,24 @@ fn hash_token_claim_data(substrate_address: &EthBytes) -> [u8; 32] { keccak_256(substrate_address) } -/// Verify EIP-712 typed signature based on provided domain_separator and entire message. -pub fn verify_token_claim( - signature: &EcdsaSignature, - domain: Domain<'_>, - substrate_address: &[u8], -) -> Option { +/// Prepare the EIP-712 message hash. +pub fn make_message_hash(domain: Domain<'_>, substrate_address: &[u8]) -> [u8; 32] { let payload_hash = make_payload_hash( &TOKEN_CLAIM_TYPEHASH, [&hash_token_claim_data(substrate_address)], ); - eip712_common::verify_signature(signature, domain, &payload_hash) + eip712_common::make_message_hash(domain, &payload_hash) +} + +/// Verify EIP-712 typed signature based on provided domain and message params and recover +/// the signer address. +pub fn recover_signer( + signature: &EcdsaSignature, + domain: Domain<'_>, + substrate_address: &[u8], +) -> Option { + let message = make_message_hash(domain, substrate_address); + eip712_common::recover_signer(signature, &message) } #[cfg(test)] @@ -83,7 +90,7 @@ mod tests { let signature = test_input(&pair); let domain = prepare_sample_domain(); - let ethereum_address = verify_token_claim(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); + let ethereum_address = recover_signer(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); assert_eq!(ethereum_address, ethereum_address_from_seed(b"Alice")); } @@ -93,7 +100,7 @@ mod tests { let signature = test_input(&pair); let domain = prepare_sample_domain(); - let ethereum_address = verify_token_claim(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); + let ethereum_address = recover_signer(&signature, domain, &SAMPLE_ACCOUNT).unwrap(); assert_ne!(ethereum_address, ethereum_address_from_seed(b"Bob")); } @@ -114,7 +121,7 @@ mod tests { let signature = hex!("3027e569de1d835350ffa4f07218d3be7298de65f12ffc767c6d80ab16ee704245e158f660817433f3748563cf83cf8a53a5ab569e7751bf158d9215f0e9b58b1b"); let ethereum_address = - verify_token_claim(&EcdsaSignature(signature), domain, &substrate_account).unwrap(); + recover_signer(&EcdsaSignature(signature), domain, &substrate_account).unwrap(); assert_eq!( ethereum_address.0, hex!("6be02d1d3665660d22ff9624b7be0551ee1ac91b"), diff --git a/crates/humanode-peer/src/chain_spec.rs b/crates/humanode-peer/src/chain_spec.rs index f82938268..46423b65a 100644 --- a/crates/humanode-peer/src/chain_spec.rs +++ b/crates/humanode-peer/src/chain_spec.rs @@ -5,10 +5,11 @@ use std::{collections::BTreeMap, str::FromStr}; use crypto_utils::{authority_keys_from_seed, get_account_id_from_seed}; use hex_literal::hex; use humanode_runtime::{ - opaque::SessionKeys, robonode, AccountId, BabeConfig, Balance, BalancesConfig, BioauthConfig, - BootnodesConfig, ChainPropertiesConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, - EvmAccountsMappingConfig, GenesisConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, - Signature, SudoConfig, SystemConfig, WASM_BINARY, + opaque::SessionKeys, robonode, token_claims::types::ClaimInfo, AccountId, BabeConfig, Balance, + BalancesConfig, BioauthConfig, BootnodesConfig, ChainPropertiesConfig, EVMConfig, + EthereumAddress, EthereumChainIdConfig, EthereumConfig, EvmAccountsMappingConfig, + GenesisConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, Signature, SudoConfig, + SystemConfig, TokenClaimsConfig, WASM_BINARY, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup}; @@ -236,6 +237,10 @@ fn testnet_genesis( humanode_runtime::FeesPot::account_id(), EXISTANTIAL_DEPOSIT + DEV_ACCOUNT_BALANCE, ), + ( + humanode_runtime::TokenClaimsPot::account_id(), + EXISTANTIAL_DEPOSIT + DEV_ACCOUNT_BALANCE, + ), ]; pot_accounts .into_iter() @@ -333,6 +338,17 @@ fn testnet_genesis( transaction_payment: Default::default(), fees_pot: Default::default(), treasury_pot: Default::default(), + token_claims_pot: Default::default(), + token_claims: TokenClaimsConfig { + claims: vec![( + EthereumAddress(hex!("bf0b5a4099f0bf6c8bc4252ebec548bae95602ea")), + ClaimInfo { + balance: DEV_ACCOUNT_BALANCE, + vesting: vec![].try_into().unwrap(), + }, + )], + total_claimable: Some(DEV_ACCOUNT_BALANCE), + }, } } diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index 7fac63f64..3dc27417f 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -11,21 +11,29 @@ substrate-wasm-builder = { git = "https://github.com/humanode-network/substrate" [dependencies] author-ext-api = { version = "0.1", path = "../author-ext-api", default-features = false } bioauth-flow-api = { version = "0.1", path = "../bioauth-flow-api", default-features = false } +crypto-utils = { version = "0.1", path = "../crypto-utils", default-features = false, optional = true } eip712-account-claim = { version = "0.1", path = "../eip712-account-claim", default-features = false } eip712-common = { version = "0.1", path = "../eip712-common", default-features = false } +eip712-common-test-utils = { version = "0.1", path = "../eip712-common-test-utils", default-features = false, optional = true } +eip712-token-claim = { version = "0.1", path = "../eip712-token-claim", default-features = false } frontier-api = { version = "0.1", path = "../frontier-api", default-features = false } keystore-bioauth-account-id = { version = "0.1", path = "../keystore-bioauth-account-id", default-features = false } pallet-bioauth = { version = "0.1", path = "../pallet-bioauth", default-features = false } pallet-bootnodes = { version = "0.1", path = "../pallet-bootnodes", default-features = false } pallet-chain-properties = { version = "0.1", path = "../pallet-chain-properties", default-features = false } +pallet-chain-start-moment = { version = "0.1", path = "../pallet-chain-start-moment", default-features = false } pallet-ethereum-chain-id = { version = "0.1", path = "../pallet-ethereum-chain-id", default-features = false } pallet-evm-accounts-mapping = { version = "0.1", path = "../pallet-evm-accounts-mapping", default-features = false } pallet-humanode-session = { version = "0.1", path = "../pallet-humanode-session", default-features = false } pallet-pot = { version = "0.1", path = "../pallet-pot", default-features = false } +pallet-token-claims = { version = "0.1", path = "../pallet-token-claims", default-features = false } +pallet-vesting = { version = "0.1", path = "../pallet-vesting", default-features = false } precompile-bioauth = { version = "0.1", path = "../precompile-bioauth", default-features = false } precompile-evm-accounts-mapping = { version = "0.1", path = "../precompile-evm-accounts-mapping", default-features = false } primitives-auth-ticket = { version = "0.1", path = "../primitives-auth-ticket", default-features = false } robonode-crypto = { version = "0.1", path = "../robonode-crypto", default-features = false } +vesting-schedule-linear = { version = "0.1", path = "../vesting-schedule-linear", default-features = false } +vesting-scheduling-timestamp = { version = "0.1", path = "../vesting-scheduling-timestamp", default-features = false } chrono = { version = "0.4.19", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } @@ -39,6 +47,7 @@ frame-system-benchmarking = { default-features = false, optional = true, git = " frame-system-rpc-runtime-api = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } frame-try-runtime = { default-features = false, optional = true, git = "https://github.com/humanode-network/substrate", branch = "master" } hex-literal = { version = "0.3", optional = true } +libsecp256k1 = { version = "0.7.0", default-features = false } pallet-authorship = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } pallet-babe = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } pallet-balances = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } @@ -79,6 +88,7 @@ static_assertions = { version = "1.1.0", default-features = false } [dev-dependencies] crypto-utils = { version = "0.1", path = "../crypto-utils" } +eip712-common-test-utils = { version = "0.1", path = "../eip712-common-test-utils" } sp-io = { git = "https://github.com/humanode-network/substrate", branch = "master" } @@ -94,13 +104,18 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-ethereum/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "pallet-token-claims/runtime-benchmarks", + "pallet-vesting/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "libsecp256k1/hmac", + "libsecp256k1/static-context", ] std = [ "author-ext-api/std", "bioauth-flow-api/std", - "eip712-common/std", "eip712-account-claim/std", + "eip712-common/std", + "eip712-token-claim/std", "keystore-bioauth-account-id/std", "precompile-bioauth/std", "precompile-evm-accounts-mapping/std", @@ -121,6 +136,7 @@ std = [ "pallet-balances/std", "pallet-base-fee/std", "pallet-chain-properties/std", + "pallet-chain-start-moment/std", "pallet-ethereum/std", "pallet-ethereum-chain-id/std", "pallet-evm-accounts-mapping/std", @@ -136,9 +152,11 @@ std = [ "pallet-randomness-collective-flip/std", "pallet-session/std", "pallet-sudo/std", + "pallet-token-claims/std", "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-vesting/std", "robonode-crypto/std", "sp-application-crypto/std", "sp-api/std", @@ -153,6 +171,8 @@ std = [ "sp-std/std", "sp-transaction-pool/std", "sp-version/std", + "vesting-schedule-linear/std", + "vesting-scheduling-timestamp/std", ] try-runtime = [ "frame-executive/try-runtime", diff --git a/crates/humanode-runtime/src/benchmarking.rs b/crates/humanode-runtime/src/benchmarking.rs new file mode 100644 index 000000000..28eee89c2 --- /dev/null +++ b/crates/humanode-runtime/src/benchmarking.rs @@ -0,0 +1,78 @@ +//! The benchmarking utilities. + +use eip712_common::{keccak_256, EcdsaSignature, EthereumAddress}; + +use super::*; + +const ALICE: [u8; 32] = + hex_literal::hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + +fn alice_secret() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +} + +fn alice_ethereum_address() -> EthereumAddress { + let mut ethereum_address = [0u8; 20]; + ethereum_address.copy_from_slice( + &keccak_256(&libsecp256k1::PublicKey::from_secret_key(&alice_secret()).serialize()[1..65]) + [12..], + ); + EthereumAddress(ethereum_address) +} + +fn alice_sign(msg_hash: &[u8; 32]) -> EcdsaSignature { + let (sig, recovery_id) = + libsecp256k1::sign(&libsecp256k1::Message::parse(&msg_hash), &alice_secret()); + let mut ecdsa_signature = [0u8; 65]; + ecdsa_signature[0..64].copy_from_slice(&sig.serialize()[..]); + ecdsa_signature[64] = recovery_id.serialize(); + EcdsaSignature(ecdsa_signature) +} + +impl pallet_token_claims::benchmarking::Interface for Runtime { + fn account_id_to_claim_to() -> ::AccountId { + AccountId::from(ALICE) + } + + fn ethereum_address() -> EthereumAddress { + alice_ethereum_address() + } + + fn create_ecdsa_signature( + account_id: &::AccountId, + ethereum_address: &EthereumAddress, + ) -> EcdsaSignature { + if ethereum_address != &alice_ethereum_address() { + panic!("bad ethereum address"); + } + + let chain_id: [u8; 32] = U256::from(crate::eip712::ETHEREUM_MAINNET_CHAIN_ID).into(); + let verifying_contract = crate::eip712::genesis_verifying_contract(); + let domain = eip712_common::Domain { + name: "Humanode Token Claim", + version: "1", + chain_id: &chain_id, + verifying_contract: &verifying_contract, + }; + + let msg_hash = eip712_token_claim::make_message_hash(domain, account_id.as_ref()); + alice_sign(&msg_hash) + } +} + +impl pallet_vesting::benchmarking::Interface for Runtime { + fn account_id() -> ::AccountId { + AccountId::from(ALICE) + } + + fn schedule() -> ::Schedule { + use vesting_schedule_linear::LinearSchedule; + vec![LinearSchedule { + balance: 100, + cliff: 10 * 24 * 60 * 60 * 1000, // 10 days + vesting: 10 * 24 * 60 * 60 * 1000, // 10 days + }] + .try_into() + .unwrap() + } +} diff --git a/crates/humanode-runtime/src/dev_utils.rs b/crates/humanode-runtime/src/dev_utils.rs new file mode 100644 index 000000000..9b73a1091 --- /dev/null +++ b/crates/humanode-runtime/src/dev_utils.rs @@ -0,0 +1,29 @@ +//! The utils we share among tests and benches - generally consider them both `dev`. + +// The code may or may not be used depending on the feature flags - so omit the noise altogether and +// disable the check for the entire module. +#![allow(dead_code)] + +use crypto_utils::{authority_keys_from_seed, get_account_id_from_seed}; +use sp_application_crypto::ByteArray; +use sp_runtime::app_crypto::sr25519; + +use super::*; + +/// The public key for the accounts. +pub type AccountPublic = ::Signer; + +/// A helper function to return [`AccountId`] based on runtime data and provided seed. +pub fn account_id(seed: &str) -> AccountId { + get_account_id_from_seed::(seed) +} + +/// A helper function to return authorities keys based on runtime data and provided seed. +pub fn authority_keys(seed: &str) -> (AccountId, BabeId, GrandpaId, ImOnlineId) { + authority_keys_from_seed::(seed) +} + +/// A helper function to get a corresponding EVM truncated address for provided AccountId. +pub fn evm_truncated_address(account_id: AccountId) -> H160 { + H160::from_slice(&account_id.as_slice()[0..20]) +} diff --git a/crates/humanode-runtime/src/eip712.rs b/crates/humanode-runtime/src/eip712.rs index 16755c594..038192376 100644 --- a/crates/humanode-runtime/src/eip712.rs +++ b/crates/humanode-runtime/src/eip712.rs @@ -4,6 +4,13 @@ use eip712_common::{EcdsaSignature, EthereumAddress}; use super::*; +pub(crate) fn genesis_verifying_contract() -> [u8; 20] { + let genesis_hash: [u8; 32] = System::block_hash(0).into(); + let mut verifying_contract = [0u8; 20]; + verifying_contract.copy_from_slice(&genesis_hash[0..20]); + verifying_contract +} + /// The verifier for the EIP-712 signature of the EVM accout claim message. pub enum AccountClaimVerifier {} @@ -12,15 +19,37 @@ impl pallet_evm_accounts_mapping::SignedClaimVerifier for AccountClaimVerifier { fn verify(account_id: &Self::AccountId, signature: &EcdsaSignature) -> Option { let chain_id: [u8; 32] = U256::from(EthereumChainId::chain_id()).into(); - let genesis_hash: [u8; 32] = System::block_hash(0).into(); - let mut verifying_contract = [0u8; 20]; - verifying_contract.copy_from_slice(&genesis_hash[0..20]); + let verifying_contract = genesis_verifying_contract(); let domain = eip712_common::Domain { name: "Humanode EVM Account Claim", version: "1", chain_id: &chain_id, verifying_contract: &verifying_contract, }; - eip712_account_claim::verify_account_claim(signature, domain, account_id.as_ref()) + eip712_account_claim::recover_signer(signature, domain, account_id.as_ref()) + } +} + +/// The verifier for the EIP-712 signature of the token claim message. +pub enum TokenClaimVerifier {} + +pub(crate) const ETHEREUM_MAINNET_CHAIN_ID: u32 = 1; + +impl pallet_token_claims::traits::EthereumSignatureVerifier for TokenClaimVerifier { + type MessageParams = pallet_token_claims::types::EthereumSignatureMessageParams; + + fn recover_signer( + signature: &EcdsaSignature, + message_params: &Self::MessageParams, + ) -> Option { + let chain_id: [u8; 32] = U256::from(ETHEREUM_MAINNET_CHAIN_ID).into(); + let verifying_contract = genesis_verifying_contract(); + let domain = eip712_common::Domain { + name: "Humanode Token Claim", + version: "1", + chain_id: &chain_id, + verifying_contract: &verifying_contract, + }; + eip712_token_claim::recover_signer(signature, domain, message_params.account_id.as_ref()) } } diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index d83751e74..80180dff8 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -13,7 +13,9 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); // A few exports that help ease life for downstream crates. use codec::{alloc::string::ToString, Decode, Encode, MaxEncodedLen}; +pub use eip712_common::EthereumAddress; use fp_rpc::TransactionStatus; +use frame_support::traits::LockIdentifier; pub use frame_support::{ construct_runtime, parameter_types, traits::{ @@ -38,6 +40,7 @@ use pallet_grandpa::{ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_session::historical as pallet_session_historical; pub use pallet_timestamp::Call as TimestampCall; +pub use pallet_token_claims as token_claims; use primitives_auth_ticket::OpaqueAuthTicket; use scale_info::TypeInfo; #[cfg(feature = "std")] @@ -65,8 +68,13 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; mod frontier_precompiles; +mod vesting; use frontier_precompiles::FrontierPrecompiles; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod dev_utils; mod display_moment; pub mod eip712; mod find_author; @@ -358,6 +366,10 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } +impl pallet_chain_start_moment::Config for Runtime { + type Time = Timestamp; +} + impl pallet_authorship::Config for Runtime { type FindAuthor = find_author::FindAuthorFromSession; type UncleGenerations = ConstU32<5>; @@ -368,10 +380,12 @@ impl pallet_authorship::Config for Runtime { parameter_types! { pub const TreasuryPotPalletId: PalletId = PalletId(*b"hmnd/tr1"); pub const FeesPotPalletId: PalletId = PalletId(*b"hmnd/fe1"); + pub const TokenClaimsPotPalletId: PalletId = PalletId(*b"hmnd/tc1"); } type PotInstanceTreasury = pallet_pot::Instance1; type PotInstanceFees = pallet_pot::Instance2; +type PotInstanceTokenClaims = pallet_pot::Instance3; impl pallet_pot::Config for Runtime { type Event = Event; @@ -385,6 +399,12 @@ impl pallet_pot::Config for Runtime { type Currency = Balances; } +impl pallet_pot::Config for Runtime { + type Event = Event; + type PalletId = TokenClaimsPotPalletId; + type Currency = Balances; +} + impl pallet_balances::Config for Runtime { type MaxLocks = ConstU32<50>; type MaxReserves = (); @@ -636,6 +656,33 @@ impl pallet_evm_accounts_mapping::Config for Runtime { type Verifier = eip712::AccountClaimVerifier; } +parameter_types! { + pub TokenClaimsPotAccountId: AccountId = TokenClaimsPot::account_id(); +} + +impl pallet_token_claims::Config for Runtime { + type Event = Event; + type Currency = Balances; + type PotAccountId = TokenClaimsPotAccountId; + type VestingSchedule = ::Schedule; + type VestingInterface = vesting::TokenClaimsInterface; + type EthereumSignatureVerifier = eip712::TokenClaimVerifier; + type WeightInfo = (); +} + +parameter_types! { + pub VestingLockId: LockIdentifier = *b"hmnd/vs1"; +} + +impl pallet_vesting::Config for Runtime { + type Event = Event; + type Currency = Balances; + type LockId = VestingLockId; + type Schedule = vesting::Schedule; + type SchedulingDriver = vesting::Driver; + type WeightInfo = (); +} + // Create the runtime by composing the FRAME pallets that were previously // configured. construct_runtime!( @@ -647,6 +694,7 @@ construct_runtime!( System: frame_system, RandomnessCollectiveFlip: pallet_randomness_collective_flip, Timestamp: pallet_timestamp, + ChainStartMoment: pallet_chain_start_moment, Bootnodes: pallet_bootnodes, Bioauth: pallet_bioauth, Babe: pallet_babe, @@ -655,6 +703,7 @@ construct_runtime!( Balances: pallet_balances, TreasuryPot: pallet_pot::, FeesPot: pallet_pot::, + TokenClaimsPot: pallet_pot::, TransactionPayment: pallet_transaction_payment, Session: pallet_session, Offences: pallet_offences, @@ -670,6 +719,8 @@ construct_runtime!( BaseFee: pallet_base_fee, ImOnline: pallet_im_online, EvmAccountsMapping: pallet_evm_accounts_mapping, + TokenClaims: pallet_token_claims, + Vesting: pallet_vesting, } ); @@ -689,6 +740,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_bioauth::CheckBioauthTx, pallet_transaction_payment::ChargeTransactionPayment, + pallet_token_claims::CheckTokenClaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -736,6 +788,7 @@ impl frame_system::offchain::CreateSignedTransaction for Runtime { frame_system::CheckWeight::::new(), pallet_bioauth::CheckBioauthTx::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + pallet_token_claims::CheckTokenClaim::::new(), ); let raw_payload = SignedPayload::new(call, extra).ok()?; let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; @@ -1238,6 +1291,9 @@ impl_runtime_apis! { list_benchmark!(list, extra, pallet_balances, Balances); list_benchmark!(list, extra, pallet_timestamp, Timestamp); list_benchmark!(list, extra, pallet_bioauth, Bioauth); + // TODO(#447): re-add token-claims and vesting benches + // list_benchmark!(list, extra, pallet_token_claims, TokenClaims); + // list_benchmark!(list, extra, pallet_vesting, Vesting); let storage_info = AllPalletsWithSystem::storage_info(); @@ -1281,6 +1337,9 @@ impl_runtime_apis! { add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_timestamp, Timestamp); add_benchmark!(params, batches, pallet_bioauth, Bioauth); + // TODO(#447): re-add token-claims and vesting benches + // add_benchmark!(params, batches, pallet_token_claims, TokenClaims); + // add_benchmark!(params, batches, pallet_vesting, Vesting); Ok(batches) } diff --git a/crates/humanode-runtime/src/tests/fixed_supply.rs b/crates/humanode-runtime/src/tests/fixed_supply.rs index 284818ae7..ebae63642 100644 --- a/crates/humanode-runtime/src/tests/fixed_supply.rs +++ b/crates/humanode-runtime/src/tests/fixed_supply.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use frame_support::{ + assert_ok, dispatch::DispatchInfo, weights::{DispatchClass, Pays}, }; @@ -10,6 +11,8 @@ use pallet_evm::AddressMapping; use sp_runtime::traits::SignedExtension; use super::*; +use crate::dev_utils::*; +use crate::opaque::SessionKeys; const INIT_BALANCE: u128 = 10u128.pow(18 + 6); @@ -26,9 +29,16 @@ fn new_test_ext_with() -> sp_io::TestExternalities { let pot_accounts = vec![TreasuryPot::account_id(), FeesPot::account_id()]; endowed_accounts .iter() - .chain(pot_accounts.iter()) .cloned() + .chain(pot_accounts.into_iter()) .map(|k| (k, INIT_BALANCE)) + .chain( + [( + TokenClaimsPot::account_id(), + >::minimum_balance(), + )] + .into_iter(), + ) .collect() }, }, diff --git a/crates/humanode-runtime/src/tests/mod.rs b/crates/humanode-runtime/src/tests/mod.rs index 76a135c48..0a4759c26 100644 --- a/crates/humanode-runtime/src/tests/mod.rs +++ b/crates/humanode-runtime/src/tests/mod.rs @@ -1,27 +1,3 @@ -use crypto_utils::{authority_keys_from_seed, get_account_id_from_seed}; -use frame_support::assert_ok; -use sp_application_crypto::ByteArray; -use sp_runtime::app_crypto::sr25519; - use super::*; -use crate::{opaque::SessionKeys, AccountId, Signature}; mod fixed_supply; - -/// The public key for the accounts. -type AccountPublic = ::Signer; - -/// A helper function to return [`AccountId`] based on runtime data and provided seed. -fn account_id(seed: &str) -> AccountId { - get_account_id_from_seed::(seed) -} - -/// A helper function to return authorities keys based on runtime data and provided seed. -fn authority_keys(seed: &str) -> (AccountId, BabeId, GrandpaId, ImOnlineId) { - authority_keys_from_seed::(seed) -} - -/// A helper function to get a corresponding EVM truncated address for provided AccountId. -fn evm_truncated_address(account_id: AccountId) -> H160 { - H160::from_slice(&account_id.as_slice()[0..20]) -} diff --git a/crates/humanode-runtime/src/vesting.rs b/crates/humanode-runtime/src/vesting.rs new file mode 100644 index 000000000..95d766071 --- /dev/null +++ b/crates/humanode-runtime/src/vesting.rs @@ -0,0 +1,52 @@ +use super::*; + +pub enum TokenClaimsInterface {} + +impl pallet_token_claims::traits::VestingInterface for TokenClaimsInterface { + type AccountId = AccountId; + type Balance = Balance; + type Schedule = ::VestingSchedule; + + fn lock_under_vesting( + account: &Self::AccountId, + _balance_to_lock: Self::Balance, + schedule: Self::Schedule, + ) -> frame_support::pallet_prelude::DispatchResult { + Vesting::lock_under_vesting(account, schedule) + } +} + +pub enum GetTimestampNow {} + +impl Get for GetTimestampNow { + fn get() -> UnixMilliseconds { + Timestamp::now() + } +} + +pub enum GetTimestampChainStart {} + +impl Get> for GetTimestampChainStart { + fn get() -> Option { + ChainStartMoment::chain_start() + } +} + +impl vesting_scheduling_timestamp::Config for Runtime { + type Balance = Balance; + type Timestamp = ::Moment; + type StartingPoint = GetTimestampChainStart; + type Now = GetTimestampNow; +} + +impl vesting_scheduling_timestamp::LinearScheduleConfig for Runtime { + type FracScale = + vesting_schedule_linear::traits::SimpleFracScaler; +} +impl vesting_scheduling_timestamp::MultiLinearScheduleConfig for Runtime { + type MaxSchedulesPerAccount = ConstU32<8>; +} + +pub type Schedule = vesting_scheduling_timestamp::MultiLinearScheduleOf; + +pub type Driver = vesting_scheduling_timestamp::Adapter; diff --git a/crates/pallet-token-claims/src/lib.rs b/crates/pallet-token-claims/src/lib.rs index f5314b5a1..851414492 100644 --- a/crates/pallet-token-claims/src/lib.rs +++ b/crates/pallet-token-claims/src/lib.rs @@ -20,7 +20,7 @@ pub mod types; pub mod weights; #[cfg(feature = "runtime-benchmarks")] -mod benchmarking; +pub mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] diff --git a/crates/pallet-token-claims/src/tests.rs b/crates/pallet-token-claims/src/tests.rs index 82ca9dab1..9a75ef931 100644 --- a/crates/pallet-token-claims/src/tests.rs +++ b/crates/pallet-token-claims/src/tests.rs @@ -762,7 +762,7 @@ fn signed_ext_validate_works() { /// This test verifies that signed extension's `validate` properly fails when the eth signature is /// invalid. #[test] -fn signed_ext_validate_fails_invalid_eth_signatue() { +fn signed_ext_validate_fails_invalid_eth_signature() { new_test_ext().execute_with_ext(|_| { // Check test preconditions. assert!(>::contains_key(ð(EthAddr::Existing))); diff --git a/crates/pallet-vesting/src/lib.rs b/crates/pallet-vesting/src/lib.rs index 40115e83c..8b7c920dc 100644 --- a/crates/pallet-vesting/src/lib.rs +++ b/crates/pallet-vesting/src/lib.rs @@ -10,7 +10,7 @@ pub mod traits; pub mod weights; #[cfg(feature = "runtime-benchmarks")] -mod benchmarking; +pub mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)]