Compare commits
6 Commits
ghost-exod
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e228d890d | |||
| 5e17c12677 | |||
| c112ad22c2 | |||
| 0dd27d6429 | |||
| 8252107e96 | |||
| 6419d4ba16 |
1219
Cargo.lock
generated
1219
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@ -17,7 +17,7 @@ homepage.workspace = true
|
||||
[workspace.package]
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["571nky", "57r37ch", "f4750"]
|
||||
version = "0.9.0"
|
||||
version = "0.8.5"
|
||||
edition = "2021"
|
||||
homepage = "https://ghostchain.io"
|
||||
repository = "https://git.ghostchain.io/ghostchain/ghost-node"
|
||||
@ -67,21 +67,18 @@ serial_test = { version = "2.0.0" }
|
||||
color-eyre = { version = "0.6.1" }
|
||||
bs58 = { version = "0.5.0" }
|
||||
prometheus-parse = { version = "0.2.2" }
|
||||
k256 = { version = "0.13.0", default-features = false }
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
num-traits = { version = "0.2.17", default-features = false }
|
||||
libsecp256k1 = { version = "0.7", default-features = false }
|
||||
bip39 = { package = "parity-bip39", version = "2.0.1" }
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.10.7", default-features = false }
|
||||
serde = { version = "1.0.197", default-features = false }
|
||||
serde_derive = { version = "1.0.117", default-features = false }
|
||||
serde_json = { version = "1.0.114", default-features = false }
|
||||
blake2-rfc = { version = "0.2.18", default-features = false }
|
||||
impl-serde = { version = "0.4.0", default-features = false }
|
||||
hex = { version = "0.4.3", default-features = false }
|
||||
strum = { version = "0.28.0", default-features = false }
|
||||
metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false }
|
||||
|
||||
scale-value = { version = "0.16.0" }
|
||||
@ -89,13 +86,6 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
|
||||
scale-info = { version = "2.5.0", default-features = false }
|
||||
subxt = { version = "0.37.0", default-features = false }
|
||||
|
||||
frost-secp256k1 = { version = "2.2.0", default-features = false, features = ["serialization"] }
|
||||
frost-ed25519 = { version = "2.2.0", default-features = false, features = ["serialization"] }
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["alloc"] }
|
||||
frost-core = { version = "2.2.0", default-features = false }
|
||||
rand_chacha = { version = "0.3.0", default-features = false }
|
||||
hkdf = { version = "0.12.4", default-features = false }
|
||||
|
||||
# Frames
|
||||
frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
@ -151,7 +141,6 @@ pallet-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk
|
||||
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
@ -166,6 +155,8 @@ pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
|
||||
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
@ -193,6 +184,8 @@ generate-bags = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
|
||||
pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
|
||||
# Substrate Client dependencies
|
||||
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" }
|
||||
@ -234,22 +227,15 @@ ghost-metrics = { path = "metrics", default-features = false }
|
||||
ghost-rpc = { path = "rpc", default-features = false }
|
||||
service = { package = "ghost-service", path = "service", default-features = false }
|
||||
ghost-traits = { path = "pallets/traits", default-features = false }
|
||||
ghost-helpers = { path = "pallets/helpers", default-features = false }
|
||||
ghost-networks = { path = "pallets/networks", default-features = false }
|
||||
ghost-governance = { path = "pallets/governance", default-features = false }
|
||||
ghost-weaver = { path = "pallets/weaver", default-features = false }
|
||||
ghost-exodus = { path = "pallets/exodus", default-features = false }
|
||||
ghost-claims = { path = "pallets/claims", default-features = false }
|
||||
ghost-slow-clap = { path = "pallets/slow-clap", default-features = false }
|
||||
ghost-sudo = { path = "pallets/sudo", default-features = false }
|
||||
ghost-nomination-pools = { path = "pallets/pools", default-features = false }
|
||||
ghost-nomination-pools-runtime-api = { path = "pallets/pools/runtime-api", default-features = false }
|
||||
ghost-nomination-pools-benchmarking = { path = "pallets/pools/benchmarking", default-features = false }
|
||||
ghost-nomination-pools-fuzzer = { path = "pallets/pools/benchmarking", default-features = false }
|
||||
|
||||
ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false }
|
||||
casper-runtime = { path = "runtime/casper", default-features = false }
|
||||
casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false }
|
||||
primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false }
|
||||
runtime-common = { package = "runtime-common", path = "runtime/common", default-features = false }
|
||||
runtime-common = { package = "ghost-runtime-common", path = "runtime/common", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
color-eyre = { workspace = true }
|
||||
@ -285,13 +271,9 @@ members = [
|
||||
"runtime/casper",
|
||||
"runtime/casper/constants",
|
||||
"pallets/networks",
|
||||
"pallets/governance",
|
||||
"pallets/pools",
|
||||
"pallets/claims",
|
||||
"pallets/slow-clap",
|
||||
"pallets/sudo",
|
||||
"pallets/exodus",
|
||||
"pallets/exodus/runtime-api",
|
||||
"pallets/helpers",
|
||||
"pallets/weaver",
|
||||
"utils/bags-list",
|
||||
"utils/chain-spec-builder",
|
||||
"utils/generate-bags",
|
||||
@ -344,12 +326,9 @@ crypto-mac = { opt-level = 3 }
|
||||
curve25519-dalek = { opt-level = 3 }
|
||||
ed25519-dalek = { opt-level = 3 }
|
||||
flate2 = { opt-level = 3 }
|
||||
frost-core = { opt-level = 3 }
|
||||
frost-secp256k1 = { opt-level = 3 }
|
||||
futures-channel = { opt-level = 3 }
|
||||
hash-db = { opt-level = 3 }
|
||||
hashbrown = { opt-level = 3 }
|
||||
hkdf = { opt-level = 3 }
|
||||
hmac = { opt-level = 3 }
|
||||
httparse = { opt-level = 3 }
|
||||
integer-sqrt = { opt-level = 3 }
|
||||
@ -365,7 +344,6 @@ parking_lot = { opt-level = 3 }
|
||||
parking_lot_core = { opt-level = 3 }
|
||||
percent-encoding = { opt-level = 3 }
|
||||
primitive-types = { opt-level = 3 }
|
||||
rand_chacha = { opt-level = 3 }
|
||||
ring = { opt-level = 3 }
|
||||
rustls = { opt-level = 3 }
|
||||
sha2 = { opt-level = 3 }
|
||||
|
||||
@ -67,11 +67,11 @@ impl SubstrateCli for Cli {
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper" => Box::new(service::chain_spec::casper_config()?),
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper-dev" | "casper-development" => {
|
||||
"casper-dev" | "dev" | "development" => {
|
||||
Box::new(service::chain_spec::casper_development_config()?)
|
||||
}
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper-local" => {
|
||||
"casper-local" | "local" => {
|
||||
Box::new(service::chain_spec::casper_local_testnet_config()?)
|
||||
}
|
||||
#[cfg(feature = "casper-native")]
|
||||
@ -126,10 +126,7 @@ where
|
||||
|
||||
set_ss58_version(chain_spec);
|
||||
|
||||
runner.run_node_until_exit(move |mut config| async move {
|
||||
config.offchain_worker.enabled = true;
|
||||
config.offchain_worker.indexing_enabled = true;
|
||||
|
||||
runner.run_node_until_exit(move |config| async move {
|
||||
let hwbench = (!cli.run.no_hardware_benchmarks)
|
||||
.then_some(config.database.path().map(|database_path| {
|
||||
let _ = std::fs::create_dir_all(&database_path);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ghost-governance"
|
||||
version = "0.3.0"
|
||||
description = "Full-chain and cross-chain governance pallet with early adopter share claims"
|
||||
name = "ghost-claims"
|
||||
version = "0.2.4"
|
||||
description = "Ghost balance and rank claims based on EVM actions"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -20,6 +20,8 @@ frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
|
||||
pallet-ranked-collective = { workspace = true }
|
||||
pallet-vesting = { workspace = true }
|
||||
pallet-balances = { workspace = true }
|
||||
|
||||
sp-core = { features = ["serde"], workspace = true }
|
||||
@ -27,15 +29,10 @@ sp-runtime = { features = ["serde"], workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
ghost-traits = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
ghost-networks = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true, default-features = true }
|
||||
libsecp256k1 = { workspace = true, default-features = true }
|
||||
hex = { workspace = true }
|
||||
# hex-literal = { workspace = true, default-features = true }
|
||||
# serde_json = { workspace = true, default-features = true }
|
||||
serde_json = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@ -43,7 +40,6 @@ std = [
|
||||
"frame-benchmarking?/std",
|
||||
"serde/std",
|
||||
"codec/std",
|
||||
"hex/std",
|
||||
"scale-info/std",
|
||||
"libsecp256k1/std",
|
||||
"frame-support/std",
|
||||
@ -52,11 +48,10 @@ std = [
|
||||
"sp-runtime/std",
|
||||
"sp-io/std",
|
||||
"sp-std/std",
|
||||
"pallet-ranked-collective/std",
|
||||
"pallet-vesting/std",
|
||||
"pallet-balances/std",
|
||||
"rustc-hex/std",
|
||||
"ghost-traits/std",
|
||||
"ghost-helpers/std",
|
||||
"ghost-networks/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"libsecp256k1/hmac",
|
||||
@ -64,13 +59,16 @@ runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-ranked-collective/runtime-benchmarks",
|
||||
"pallet-vesting/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"ghost-networks/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-ranked-collective/try-runtime",
|
||||
"pallet-vesting/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
59
pallets/claims/src/benchmarking.rs
Normal file
59
pallets/claims/src/benchmarking.rs
Normal file
@ -0,0 +1,59 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
use frame_support::dispatch::RawOrigin;
|
||||
use pallet_ranked_collective::Pallet as Club;
|
||||
|
||||
#[benchmark]
|
||||
fn claim() {
|
||||
let i = 1337u32;
|
||||
let ethereum_secret_key =
|
||||
libsecp256k1::SecretKey::parse(&keccak_256(&i.to_le_bytes())).unwrap();
|
||||
let eth_address = crate::secp_utils::eth(ðereum_secret_key);
|
||||
|
||||
let balance = CurrencyOf::<T, I>::minimum_balance();
|
||||
let pseudo_account: T::AccountId = Pallet::<T, I>::into_account_id(eth_address).unwrap();
|
||||
let _ = CurrencyOf::<T, I>::deposit_creating(&pseudo_account, balance);
|
||||
Total::<T, I>::put(balance);
|
||||
|
||||
let pseudo_rank = 5u16;
|
||||
let _ = Club::<T, I>::do_add_member_to_rank(pseudo_account.clone(), pseudo_rank, false);
|
||||
|
||||
let user_account: T::AccountId = account("user", i, 0);
|
||||
let signature =
|
||||
crate::secp_utils::sig::<T, I>(ðereum_secret_key, &user_account.encode());
|
||||
|
||||
let prev_balance = CurrencyOf::<T, I>::free_balance(&user_account);
|
||||
let prev_rank = Club::<T, I>::rank_of(&user_account);
|
||||
|
||||
#[extrinsic_call]
|
||||
claim(
|
||||
RawOrigin::Signed(user_account.clone()),
|
||||
eth_address,
|
||||
signature,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CurrencyOf::<T, I>::free_balance(&user_account),
|
||||
prev_balance + balance
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T, I>::free_balance(&pseudo_account),
|
||||
balance - balance
|
||||
);
|
||||
|
||||
let rank = match prev_rank {
|
||||
Some(current_rank) if pseudo_rank <= current_rank => Some(current_rank),
|
||||
_ => Some(pseudo_rank),
|
||||
};
|
||||
assert_eq!(Club::<T, I>::rank_of(&user_account), rank);
|
||||
assert_eq!(Club::<T, I>::rank_of(&pseudo_account), None);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,);
|
||||
}
|
||||
336
pallets/claims/src/lib.rs
Normal file
336
pallets/claims/src/lib.rs
Normal file
@ -0,0 +1,336 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::{
|
||||
ensure,
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
Currency, ExistenceRequirement, Get, RankedMembers, RankedMembersSwapHandler,
|
||||
VestingSchedule,
|
||||
},
|
||||
DefaultNoBound,
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
pub use pallet::*;
|
||||
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
|
||||
use sp_runtime::traits::{BlockNumberProvider, CheckedDiv, CheckedSub};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
extern crate alloc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, string::String};
|
||||
|
||||
mod weights;
|
||||
pub use crate::weights::WeightInfo;
|
||||
|
||||
mod benchmarking;
|
||||
mod mock;
|
||||
mod secp_utils;
|
||||
mod tests;
|
||||
|
||||
/// An ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
|
||||
///
|
||||
/// This gets serialized to the 0x-prefixed hex representation.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)]
|
||||
pub struct EthereumAddress(pub [u8; 20]);
|
||||
|
||||
impl Serialize for EthereumAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
|
||||
serializer.serialize_str(&format!("0x{}", hex))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for EthereumAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base_string = String::deserialize(deserializer)?;
|
||||
let offset = if base_string.starts_with("0x") { 2 } else { 0 };
|
||||
let s = &base_string[offset..];
|
||||
if s.len() != 40 {
|
||||
Err(serde::de::Error::custom(
|
||||
"Bad length of Ethereum address (should be 42 including `0x`)",
|
||||
))?;
|
||||
}
|
||||
let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
|
||||
let mut r = Self::default();
|
||||
r.0.copy_from_slice(&raw);
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, TypeInfo)]
|
||||
pub struct EcdsaSignature(pub [u8; 65]);
|
||||
|
||||
impl PartialEq for EcdsaSignature {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
&self.0[..] == &other.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_std::fmt::Debug for EcdsaSignature {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
write!(f, "EcdsaSignature({:?})", &self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
type CurrencyOf<T, I> = <<T as Config<I>>::VestingSchedule as VestingSchedule<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::Currency;
|
||||
|
||||
type BalanceOf<T, I> =
|
||||
<CurrencyOf<T, I> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
type RankOf<T, I> = <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::Rank;
|
||||
type AccountIdOf<T, I> = <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::AccountId;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>:
|
||||
frame_system::Config + pallet_ranked_collective::Config<I>
|
||||
{
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>;
|
||||
type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
|
||||
type MemberSwappedHandler: RankedMembersSwapHandler<AccountIdOf<Self, I>, RankOf<Self, I>>;
|
||||
|
||||
#[pallet::constant]
|
||||
type Prefix: Get<&'static [u8]>;
|
||||
|
||||
#[pallet::constant]
|
||||
type MaximumWithdrawAmount: Get<BalanceOf<Self, I>>;
|
||||
|
||||
#[pallet::constant]
|
||||
type VestingBlocks: Get<u32>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
Claimed {
|
||||
receiver: T::AccountId,
|
||||
donor: T::AccountId,
|
||||
amount: BalanceOf<T, I>,
|
||||
rank: Option<RankOf<T, I>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
InvalidEthereumSignature,
|
||||
InvalidEthereumAddress,
|
||||
NoBalanceToClaim,
|
||||
AddressDecodingFailed,
|
||||
ArithmeticError,
|
||||
PotUnderflow,
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Total<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
pub total: BalanceOf<T, I>,
|
||||
pub members_and_ranks: Vec<(T::AccountId, u16)>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
let cult_accounts: Vec<_> = self
|
||||
.members_and_ranks
|
||||
.iter()
|
||||
.map(|(account_id, rank)| {
|
||||
assert!(
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(
|
||||
account_id.clone(),
|
||||
*rank,
|
||||
false
|
||||
)
|
||||
.is_ok(),
|
||||
"error during adding and promotion"
|
||||
);
|
||||
account_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
self.members_and_ranks.len() == cult_accounts.len(),
|
||||
"duplicates in `members_and_ranks`"
|
||||
);
|
||||
|
||||
Total::<T, I>::put(self.total);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::claim())]
|
||||
pub fn claim(
|
||||
origin: OriginFor<T>,
|
||||
ethereum_address: EthereumAddress,
|
||||
ethereum_signature: EcdsaSignature,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let data = who.using_encoded(to_ascii_hex);
|
||||
let recovered_address = Self::recover_ethereum_address(ðereum_signature, &data)
|
||||
.ok_or(Error::<T, I>::InvalidEthereumSignature)?;
|
||||
|
||||
ensure!(
|
||||
recovered_address == ethereum_address,
|
||||
Error::<T, I>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
Self::do_claim(who, ethereum_address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(data.len() * 2);
|
||||
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
|
||||
for &b in data.iter() {
|
||||
push_nibble(b / 16);
|
||||
push_nibble(b % 16);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn ethereum_signable_message(what: &[u8]) -> Vec<u8> {
|
||||
let prefix = T::Prefix::get();
|
||||
let mut l = prefix.len() + what.len();
|
||||
let mut rev = Vec::new();
|
||||
while l > 0 {
|
||||
rev.push(b'0' + (l % 10) as u8);
|
||||
l /= 10;
|
||||
}
|
||||
let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
|
||||
v.extend(rev.into_iter().rev());
|
||||
v.extend_from_slice(prefix);
|
||||
v.extend_from_slice(what);
|
||||
v
|
||||
}
|
||||
|
||||
fn recover_ethereum_address(s: &EcdsaSignature, what: &[u8]) -> Option<EthereumAddress> {
|
||||
let msg = keccak_256(&Self::ethereum_signable_message(what));
|
||||
let mut res = EthereumAddress::default();
|
||||
res.0
|
||||
.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn into_account_id(address: EthereumAddress) -> Result<T::AccountId, codec::Error> {
|
||||
let mut data = [0u8; 32];
|
||||
data[0..4].copy_from_slice(b"evm:");
|
||||
data[4..24].copy_from_slice(&address.0[..]);
|
||||
|
||||
let hash = sp_core::keccak_256(&data);
|
||||
T::AccountId::decode(&mut &hash[..])
|
||||
}
|
||||
|
||||
fn do_claim(receiver: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult {
|
||||
let donor = Self::into_account_id(ethereum_address)
|
||||
.ok()
|
||||
.ok_or(Error::<T, I>::AddressDecodingFailed)?;
|
||||
|
||||
let balance_due = CurrencyOf::<T, I>::free_balance(&donor);
|
||||
ensure!(
|
||||
balance_due >= CurrencyOf::<T, I>::minimum_balance(),
|
||||
Error::<T, I>::NoBalanceToClaim
|
||||
);
|
||||
|
||||
let new_total = Total::<T, I>::get()
|
||||
.checked_sub(&balance_due)
|
||||
.ok_or(Error::<T, I>::PotUnderflow)?;
|
||||
|
||||
CurrencyOf::<T, I>::transfer(
|
||||
&donor,
|
||||
&receiver,
|
||||
balance_due,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)?;
|
||||
|
||||
let max_amount = T::MaximumWithdrawAmount::get();
|
||||
if balance_due > max_amount {
|
||||
let vesting_balance = balance_due
|
||||
.checked_sub(&max_amount)
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
let vesting_blocks = T::VestingBlocks::get();
|
||||
let per_block_balance = vesting_balance
|
||||
.checked_div(&vesting_blocks.into())
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
T::VestingSchedule::add_vesting_schedule(
|
||||
&receiver,
|
||||
vesting_balance,
|
||||
per_block_balance,
|
||||
T::BlockNumberProvider::current_block_number(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let rank = if let Some(rank) =
|
||||
<pallet_ranked_collective::Pallet<T, I> as RankedMembers>::rank_of(&donor)
|
||||
{
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_remove_member_from_rank(&donor, rank)?;
|
||||
let new_rank =
|
||||
match <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::rank_of(&receiver)
|
||||
{
|
||||
Some(current_rank) if current_rank >= rank => current_rank,
|
||||
Some(current_rank) if current_rank < rank => {
|
||||
for _ in 0..rank - current_rank {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_promote_member(
|
||||
receiver.clone(),
|
||||
None,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
rank
|
||||
}
|
||||
_ => {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(
|
||||
receiver.clone(),
|
||||
rank,
|
||||
false,
|
||||
)?;
|
||||
rank
|
||||
}
|
||||
};
|
||||
<T as pallet::Config<I>>::MemberSwappedHandler::swapped(&donor, &receiver, new_rank);
|
||||
Some(new_rank)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Total::<T, I>::put(new_total);
|
||||
Self::deposit_event(Event::<T, I>::Claimed {
|
||||
receiver,
|
||||
donor,
|
||||
amount: balance_due,
|
||||
rank,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
281
pallets/claims/src/mock.rs
Normal file
281
pallets/claims/src/mock.rs
Normal file
@ -0,0 +1,281 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use crate as ghost_claims;
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU16, ConstU64, PollStatus, Polling, WithdrawReasons},
|
||||
};
|
||||
use frame_system::EnsureRootWithSuccess;
|
||||
pub use pallet_ranked_collective::{Rank, TallyOf};
|
||||
use sp_runtime::{traits::Convert, BuildStorage};
|
||||
|
||||
pub mod eth_keys {
|
||||
use crate::{mock::Test, EcdsaSignature, EthereumAddress};
|
||||
use codec::Encode;
|
||||
use hex_literal::hex;
|
||||
|
||||
pub fn total_claims() -> u64 {
|
||||
10 + 100 + 1000
|
||||
}
|
||||
pub fn alice_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
69
|
||||
}
|
||||
pub fn bob_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
1337
|
||||
}
|
||||
|
||||
pub fn first_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("1A69d2D5568D1878023EeB121a73d33B9116A760"))
|
||||
}
|
||||
pub fn second_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("2f86cfBED3fbc1eCf2989B9aE5fc019a837A9C12"))
|
||||
}
|
||||
pub fn third_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("e83f67361Ac74D42A48E2DAfb6706eb047D8218D"))
|
||||
}
|
||||
pub fn fourth_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("827ee4ad9b259b6fa1390ed60921508c78befd63"))
|
||||
}
|
||||
|
||||
fn first_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"01c928771aea942a1e7ac06adf2b73dfbc9a25d9eaa516e3673116af7f345198"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn second_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"b19a435901872f817185f7234a1484eae837613f9d10cf21927a23c2d8cb9139"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn third_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"d3baf57b74d65719b2dc33f5a464176022d0cc5edbca002234229f3e733875fc"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn fourth_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"c4683d566436af6b58b4a59c8f501319226e85b21869bf93d5eeb4596d4791d4"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn wrong_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn first_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&first_eth_private_key())
|
||||
}
|
||||
pub fn second_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&second_eth_private_key())
|
||||
}
|
||||
pub fn third_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&third_eth_private_key())
|
||||
}
|
||||
pub fn fourth_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&fourth_eth_private_key())
|
||||
}
|
||||
|
||||
pub fn first_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(first_eth_public_key())
|
||||
}
|
||||
pub fn second_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(second_eth_public_key())
|
||||
}
|
||||
pub fn third_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(third_eth_public_key())
|
||||
}
|
||||
pub fn fourth_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(fourth_eth_public_key())
|
||||
}
|
||||
|
||||
pub fn first_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&first_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn second_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&second_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn third_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&third_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn fourth_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&fourth_eth_private_key(), &bob_account_id().encode())
|
||||
}
|
||||
pub fn wrong_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&wrong_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MinVestedTransfer: u64 = 1;
|
||||
pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
|
||||
WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
|
||||
}
|
||||
|
||||
impl pallet_vesting::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type BlockNumberToBalance = sp_runtime::traits::ConvertInto;
|
||||
type MinVestedTransfer = MinVestedTransfer;
|
||||
type WeightInfo = ();
|
||||
type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons;
|
||||
|
||||
type BlockNumberProvider = System;
|
||||
const MAX_VESTING_SCHEDULES: u32 = 28;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub MinRankOfClassDelta: Rank = 1;
|
||||
}
|
||||
|
||||
pub struct MinRankOfClass<Delta>(sp_std::marker::PhantomData<Delta>);
|
||||
impl<Delta: Get<Rank>> Convert<Class, Rank> for MinRankOfClass<Delta> {
|
||||
fn convert(a: Class) -> Rank {
|
||||
a.saturating_sub(Delta::get())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPolls;
|
||||
impl Polling<TallyOf<Test>> for TestPolls {
|
||||
type Index = u8;
|
||||
type Votes = u32;
|
||||
type Moment = u64;
|
||||
type Class = Class;
|
||||
|
||||
fn classes() -> Vec<Self::Class> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_ongoing(_index: u8) -> Option<(TallyOf<Test>, Self::Class)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn access_poll<R>(
|
||||
_index: Self::Index,
|
||||
_f: impl FnOnce(PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>) -> R,
|
||||
) -> R {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn try_access_poll<R>(
|
||||
_index: Self::Index,
|
||||
_f: impl FnOnce(
|
||||
PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>,
|
||||
) -> Result<R, DispatchError>,
|
||||
) -> Result<R, DispatchError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn create_ongoing(_class: Self::Class) -> Result<Self::Index, ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn end_ongoing(_index: Self::Index, _approved: bool) -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_ranked_collective::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type AddOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type RemoveOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type PromoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type DemoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type ExchangeOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
|
||||
type Polls = TestPolls;
|
||||
type MemberSwappedHandler = ();
|
||||
type MinRankOfClass = MinRankOfClass<MinRankOfClassDelta>;
|
||||
type VoteWeight = pallet_ranked_collective::Geometric;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkSetup = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub Prefix: &'static [u8] = b"AccountId:";
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type VestingSchedule = Vesting;
|
||||
type BlockNumberProvider = System;
|
||||
type MemberSwappedHandler = ();
|
||||
|
||||
type Prefix = Prefix;
|
||||
type MaximumWithdrawAmount = ConstU64<200>;
|
||||
type VestingBlocks = ConstU32<80>;
|
||||
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
type Class = Rank;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Vesting: pallet_vesting,
|
||||
Club: pallet_ranked_collective,
|
||||
Claims: ghost_claims,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(crate::mock::eth_keys::first_account_id(), 10),
|
||||
(crate::mock::eth_keys::second_account_id(), 100),
|
||||
(crate::mock::eth_keys::third_account_id(), 1000),
|
||||
],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
ghost_claims::GenesisConfig::<Test> {
|
||||
total: crate::mock::eth_keys::total_claims(),
|
||||
members_and_ranks: vec![
|
||||
(crate::mock::eth_keys::second_account_id(), 1),
|
||||
(crate::mock::eth_keys::third_account_id(), 3),
|
||||
],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
ext
|
||||
}
|
||||
35
pallets/claims/src/secp_utils.rs
Normal file
35
pallets/claims/src/secp_utils.rs
Normal file
@ -0,0 +1,35 @@
|
||||
#![cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
|
||||
use crate::{keccak_256, Config, EcdsaSignature, EthereumAddress};
|
||||
|
||||
pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
|
||||
libsecp256k1::PublicKey::from_secret_key(secret)
|
||||
}
|
||||
|
||||
pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
|
||||
let mut res = EthereumAddress::default();
|
||||
res.0
|
||||
.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn into_account_id<T: Config<I>, I: 'static>(address: EthereumAddress) -> T::AccountId {
|
||||
super::Pallet::<T, I>::into_account_id(address).unwrap()
|
||||
}
|
||||
|
||||
pub fn sig<T: Config<I>, I: 'static>(
|
||||
secret: &libsecp256k1::SecretKey,
|
||||
what: &[u8],
|
||||
) -> EcdsaSignature {
|
||||
let msg = keccak_256(&super::Pallet::<T, I>::ethereum_signable_message(
|
||||
&crate::to_ascii_hex(what)[..],
|
||||
));
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
|
||||
|
||||
let mut r = [0u8; 65];
|
||||
r[0..64].copy_from_slice(&sig.serialize()[..]);
|
||||
r[64] = recovery_id.serialize();
|
||||
EcdsaSignature(r)
|
||||
}
|
||||
285
pallets/claims/src/tests.rs
Normal file
285
pallets/claims/src/tests.rs
Normal file
@ -0,0 +1,285 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use hex_literal::hex;
|
||||
use mock::{
|
||||
eth_keys::{
|
||||
alice_account_id, bob_account_id, first_account_id, first_eth_public_key,
|
||||
first_eth_public_known, first_signature, fourth_account_id, fourth_eth_public_key,
|
||||
fourth_eth_public_known, fourth_signature, second_account_id, second_eth_public_key,
|
||||
second_eth_public_known, second_signature, third_account_id, third_eth_public_key,
|
||||
third_eth_public_known, third_signature, total_claims, wrong_signature,
|
||||
},
|
||||
ghost_claims, new_test_ext, Balances, Claims, Club, RuntimeEvent, RuntimeOrigin, System, Test,
|
||||
Vesting,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serde_works() {
|
||||
let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
|
||||
let y = serde_json::to_string(&x).unwrap();
|
||||
assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
|
||||
let z: EthereumAddress = serde_json::from_str(&y).unwrap();
|
||||
assert_eq!(x, z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_eth_public_accounts_are_correct() {
|
||||
assert_eq!(first_eth_public_key(), first_eth_public_known());
|
||||
assert_eq!(second_eth_public_key(), second_eth_public_known());
|
||||
assert_eq!(third_eth_public_key(), third_eth_public_known());
|
||||
assert_eq!(fourth_eth_public_key(), fourth_eth_public_known());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature()
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 10);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&second_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 100);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn big_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 200);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(
|
||||
ghost_claims::Total::<Test, ()>::get(),
|
||||
total_claims() - 1000
|
||||
);
|
||||
assert_ok!(Balances::transfer_allow_death(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
bob_account_id(),
|
||||
200
|
||||
));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_accounts_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_accounts_reverese_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_with_bad_signature() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
wrong_signature()
|
||||
),
|
||||
crate::Error::<Test>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_with_wrong_address() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(bob_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature()
|
||||
),
|
||||
crate::Error::<Test>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_nothing() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(bob_account_id()),
|
||||
fourth_eth_public_key(),
|
||||
fourth_signature()
|
||||
),
|
||||
crate::Error::<Test>::NoBalanceToClaim
|
||||
);
|
||||
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&fourth_account_id()), 0);
|
||||
assert_eq!(Vesting::vesting_balance(&bob_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_emitted_during_claim() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let account = third_account_id();
|
||||
let amount = Balances::usable_balance(&account);
|
||||
let rank = Club::rank_of(&account);
|
||||
|
||||
System::reset_events();
|
||||
assert_eq!(System::event_count(), 0);
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
System::assert_has_event(RuntimeEvent::Claims(crate::Event::Claimed {
|
||||
receiver: alice_account_id(),
|
||||
donor: account,
|
||||
amount,
|
||||
rank,
|
||||
}));
|
||||
})
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-exodus"
|
||||
version = "0.0.0"
|
||||
description = "Threshold signature generation with DKG included"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
codec = { workspace = true, features = ["max-encoded-len"] }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
num-traits = { workspace = true }
|
||||
|
||||
frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-application-crypto = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-staking = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
frost-secp256k1 = { workspace = true }
|
||||
frost-ed25519 = { workspace = true }
|
||||
frost-core = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
hkdf = { workspace = true }
|
||||
chacha20poly1305 = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
k256 = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
|
||||
ghost-traits = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
|
||||
pallet-balances = { workspace = true }
|
||||
pallet-session = { workspace = true }
|
||||
pallet-staking = { workspace = true }
|
||||
ghost-networks = { workspace = true }
|
||||
pallet-staking-reward-curve = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-keystore = { workspace = true }
|
||||
frame-executive = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"frame-benchmarking?/std",
|
||||
"scale-info/std",
|
||||
"strum/std",
|
||||
"log/std",
|
||||
"hex/std",
|
||||
"k256/std",
|
||||
"num-traits/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"sha2/std",
|
||||
"hkdf/std",
|
||||
"chacha20poly1305/std",
|
||||
"rand_chacha/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"frost-secp256k1/std",
|
||||
"frost-ed25519/std",
|
||||
"frost-core/std",
|
||||
"ghost-traits/std",
|
||||
"ghost-helpers/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-session/std",
|
||||
"pallet-staking/std",
|
||||
"ghost-networks/std",
|
||||
"frame-executive/std",
|
||||
"sp-keystore/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-staking/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
"ghost-networks/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
"pallet-session/try-runtime",
|
||||
"frame-executive/try-runtime",
|
||||
"ghost-networks/try-runtime",
|
||||
]
|
||||
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-exodus-runtime-api"
|
||||
version = "0.0.0"
|
||||
description = "RPC runtime API for ghost EXODUS pallet"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true, features = ["derive"] }
|
||||
sp-api = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "sp-api/std"]
|
||||
@ -1,7 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
pub trait ExodusApi<NetworkId> {
|
||||
fn verifying_key(network_id: NetworkId) -> Vec<u8>;
|
||||
}
|
||||
}
|
||||
@ -1,768 +0,0 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
use ghost_helpers::networks::NetworkDataBuilder;
|
||||
|
||||
fn prepare_pallet<T: Config>(authorities_len: usize) -> (
|
||||
PendingDkgAuthorities<ParticipantsBitmap<T>, BlockNumberFor<T>>,
|
||||
NetworkCurve,
|
||||
T::AuthorityId,
|
||||
AuthIndex
|
||||
) {
|
||||
let network_curve = NetworkCurve::Secp256k1;
|
||||
let authority = T::AuthorityId::generate_pair(None);
|
||||
|
||||
let authorities = vec![authority.clone(); authorities_len];
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
|
||||
.expect("more than the maximum number of keys provided");
|
||||
|
||||
QualificationAuthorities::<T>::insert(network_curve, bounded_authorities);
|
||||
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
let network_data = NetworkDataBuilder::default()
|
||||
.with_network_type(NetworkType::Evm)
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.build();
|
||||
|
||||
T::NetworkDataHandler::register(network_id, network_data)
|
||||
.expect("network should be valid for insertion");
|
||||
|
||||
let mut dkg_state = PendingDkgAuthorities::default();
|
||||
dkg_state.new_dkg(authorities_len);
|
||||
|
||||
(dkg_state, network_curve, authority, 0)
|
||||
}
|
||||
|
||||
#[benchmarks(where T: Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn register_round0_package() -> Result<(), BenchmarkError> {
|
||||
let dummy_hash = ExodusHash::repeat_byte(69);
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(1);
|
||||
|
||||
dkg_state.next_phase();
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let round0_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(0)
|
||||
.with_dkg_round0(dummy_hash);
|
||||
|
||||
let dkg_package = DkgPackage::Round0(round0_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let expected_hash = Round0Packages::<T>::get(&network_curve, authority_index);
|
||||
assert_eq!(expected_hash, dummy_hash);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_round1_package(
|
||||
c: Linear<4, { T::MaxAuthorities::get() }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let max_signers = c as AuthIndex;
|
||||
let min_signers = get_byzantium_threshold(max_signers);
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
dkg_state.insert_index(authority_index);
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let mut rng = Pallet::<T>::get_offchain_rng(authority_index);
|
||||
|
||||
let (_, public_package) = network_curve.dkg_part1(
|
||||
authority_index,
|
||||
max_signers,
|
||||
min_signers,
|
||||
&mut rng,
|
||||
).expect("DKG part1 should work");
|
||||
|
||||
let package_hash = ExodusHash::from(blake2_256(&public_package));
|
||||
Round0Packages::<T>::insert(network_curve, authority_index, package_hash);
|
||||
|
||||
let round1_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(0)
|
||||
.with_dkg_round1(public_package)
|
||||
.expect("Round1 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round1(round1_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let participants = Round1Packages::<T>::get(&network_curve);
|
||||
assert!(participants.contains(authority_index));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_encrypted_round2_packages(
|
||||
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let max_signers = (c * 8) as AuthIndex;
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
dkg_state.insert_index(i);
|
||||
});
|
||||
|
||||
let padded_participants = dkg_state
|
||||
.highest_bit::<usize>()
|
||||
.map(|bit_pos| bit_pos.saturating_add(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let bitmask_len = padded_participants.div_ceil(8);
|
||||
let blob_len = round2_encrypted_package_size(
|
||||
padded_participants,
|
||||
network_curve.scalar_bytes_len(),
|
||||
network_curve.header_bytes_len(),
|
||||
);
|
||||
|
||||
let mut bitmask = vec![0u8; bitmask_len];
|
||||
let blob = vec![0u8; blob_len];
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
if i != authority_index {
|
||||
let byte_index = (i / 8) as usize;
|
||||
let bit_index = (i % 8) as u8;
|
||||
bitmask[byte_index] |= 1 << bit_index;
|
||||
}
|
||||
});
|
||||
|
||||
let encryption_bundle = BundledPackages { bitmask, blob };
|
||||
|
||||
let round2_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_dkg_round2(encryption_bundle)
|
||||
.expect("Round2 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round2(round2_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let participants = Round2Packages::<T>::get(&network_curve);
|
||||
assert!(participants.contains(authority_index));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_round3_complaints(
|
||||
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let max_signers = (c * 8) as AuthIndex;
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
dkg_state.insert_index(i);
|
||||
});
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let bitvec_len = (max_signers + 7) / 8;
|
||||
let mut accused_indexes = vec![0u8; bitvec_len as usize];
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
if i != authority_index {
|
||||
let byte_index = (i / 8) as usize;
|
||||
let bit_index = (i % 8) as u8;
|
||||
accused_indexes[byte_index] |= 1 << bit_index;
|
||||
}
|
||||
});
|
||||
|
||||
let round3_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_dkg_round3(accused_indexes)
|
||||
.expect("Round3 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round3(round3_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let complaints = Complaints::<T>::get(&network_curve);
|
||||
let my_complaints = complaints.get(&authority_index)
|
||||
.expect("Comlpaints should be registered");
|
||||
|
||||
assert_eq!(my_complaints.count_ones::<AuthIndex>(), max_signers - 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_round4_justifications(
|
||||
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let max_signers = (c * 8) as AuthIndex;
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
dkg_state.insert_index(i);
|
||||
});
|
||||
|
||||
let padded_participants = dkg_state
|
||||
.highest_bit::<usize>()
|
||||
.map(|bit_pos| bit_pos.saturating_add(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let bitmask_len = padded_participants.div_ceil(8);
|
||||
let blob_len = round2_package_size(
|
||||
padded_participants,
|
||||
network_curve.scalar_bytes_len(),
|
||||
network_curve.header_bytes_len(),
|
||||
);
|
||||
|
||||
let mut bitmask = vec![0u8; bitmask_len];
|
||||
let blob = vec![0u8; blob_len];
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
if i != authority_index {
|
||||
let byte_index = (i / 8) as usize;
|
||||
let bit_index = (i % 8) as u8;
|
||||
bitmask[byte_index] |= 1 << bit_index;
|
||||
}
|
||||
});
|
||||
|
||||
let justification_bundle = BundledPackages { bitmask, blob };
|
||||
|
||||
let round4_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_dkg_round4(justification_bundle)
|
||||
.expect("Round4 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round4(round4_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let justifications = Justifications::<T>::get(&network_curve);
|
||||
let my_justifications = justifications.get(&authority_index)
|
||||
.expect("Justifications should be registered");
|
||||
|
||||
assert_eq!(my_justifications.count_ones::<AuthIndex>(), max_signers - 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_round5_verifying_package() -> Result<(), BenchmarkError> {
|
||||
let max_signers = 4 as AuthIndex;
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
dkg_state.insert_index(i);
|
||||
});
|
||||
|
||||
let dkg_index = dkg_state.get_dkg_index();
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let dummy_merkle_root = ExodusHash::repeat_byte(69);
|
||||
let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()];
|
||||
|
||||
let round5_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_dkg_round5(dummy_verifying_key.clone(), dummy_merkle_root)
|
||||
.expect("Round5 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round5(round5_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let bounded_verifying_key =
|
||||
BoundedVec::<u8, ConstU32< { ELEMENT_MAX_BYTES }>>::try_from(
|
||||
dummy_verifying_key,
|
||||
).expect("Bounded verifying key should be correct");
|
||||
|
||||
let metadata_hashes = [
|
||||
SubstrateBlake2Hasher::hash(dummy_merkle_root.as_ref()),
|
||||
SubstrateBlake2Hasher::hash(bounded_verifying_key.as_ref()),
|
||||
];
|
||||
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
||||
|
||||
let verification_key = (network_curve, dkg_index, verifying_hash);
|
||||
let participants = Verifications::<T>::get(verification_key);
|
||||
assert!(participants.contains(authority_index));
|
||||
|
||||
let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, dkg_index);
|
||||
assert!(consensus.participants.contains(authority_index));
|
||||
assert_eq!(consensus.participants.count_ones::<AuthIndex>(), 1);
|
||||
assert_eq!(consensus.merkle_root, dummy_merkle_root);
|
||||
assert_eq!(consensus.element_bytes, bounded_verifying_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_round6_public_share_package(
|
||||
c: Linear<1, { T::MaxAuthorities::get().next_power_of_two().trailing_zeros() }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let computed_max_signers = (1u32 << c) as AuthIndex;
|
||||
let constant_max_signers = T::MaxAuthorities::get() as AuthIndex;
|
||||
|
||||
let max_signers = sp_std::cmp::min(computed_max_signers, constant_max_signers);
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
dkg_state.next_phase();
|
||||
|
||||
(0..max_signers).for_each(|i| {
|
||||
dkg_state.insert_index(i);
|
||||
});
|
||||
|
||||
let dkg_index = dkg_state.get_dkg_index();
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
|
||||
let dummy_value = vec![69u8; network_curve.element_bytes_len()];
|
||||
|
||||
let (dummy_merkle_root, dummy_verifying_share, dummy_merkle_proof) =
|
||||
with_ciphersuite!(network_curve, |f| {
|
||||
let mut dummy_values = BTreeMap::new();
|
||||
|
||||
for i in 0..max_signers {
|
||||
let non_zero_index = i.saturating_add(1);
|
||||
let identifier: frost_core::Identifier<Ciphersuite> =
|
||||
frost_core::Identifier::<Ciphersuite>::try_from(non_zero_index)
|
||||
.expect("Identifier should be created");
|
||||
|
||||
dummy_values.insert(identifier, dummy_value.clone());
|
||||
}
|
||||
|
||||
let merkle_tree =
|
||||
NetworkCurve::build_merkle_tree(
|
||||
&dummy_values,
|
||||
|value: &Vec<u8>| Ok(value.clone()),
|
||||
).expect("Merkle tree for verifying shares should be valid");
|
||||
|
||||
let merkle_root = merkle_tree.last().copied().unwrap();
|
||||
|
||||
let (verifying_share, merkle_proof) = NetworkCurve::generate_merkle_proof_from_tree(
|
||||
&dummy_values,
|
||||
&merkle_tree,
|
||||
authority_index,
|
||||
|value: &Vec<u8>| Ok(value.clone())
|
||||
).expect("Merkle tree proof should be valid");
|
||||
|
||||
(merkle_root, verifying_share, merkle_proof)
|
||||
});
|
||||
|
||||
VerifyingKeyConsensus::<T>::mutate(&network_curve, &dkg_index, |consensus| {
|
||||
consensus.participants.insert(authority_index);
|
||||
consensus.merkle_root = dummy_merkle_root;
|
||||
});
|
||||
|
||||
let round6_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_dkg_round6(dummy_verifying_share.clone(), dummy_merkle_proof)
|
||||
.expect("Round6 package should be valid");
|
||||
|
||||
let dkg_package = DkgPackage::Round6(round6_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&dkg_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, dkg_package, signature);
|
||||
|
||||
let participants = VerifyingSharesParticipants::<T>::get(&network_curve, dkg_index);
|
||||
assert!(participants.contains(authority_index));
|
||||
|
||||
let bundled_verifying_share =
|
||||
BoundedVec::<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>::try_from(
|
||||
dummy_verifying_share,
|
||||
).expect("Verifying share should be bundled");
|
||||
|
||||
let verifying_share_key = (network_curve, dkg_index, authority_index);
|
||||
assert_eq!(
|
||||
VerifyingShares::<T>::get(verifying_share_key).unwrap(),
|
||||
bundled_verifying_share,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_nonce_commitment() -> Result<(), BenchmarkError> {
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(1);
|
||||
|
||||
loop {
|
||||
if !dkg_state.is_dkg_pending() { break; }
|
||||
dkg_state.next_phase();
|
||||
}
|
||||
|
||||
dkg_state.insert_index(authority_index);
|
||||
let exodus_session = CurrentExodus::<T>::get();
|
||||
|
||||
let dummy_bytes = EvmBytes32::repeat_byte(69);
|
||||
let dummy_nonce = vec![0u8; network_curve.element_bytes_len()];
|
||||
let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0);
|
||||
|
||||
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
||||
let active_dkg: ActivatedState<T> = (&dkg_state).into();
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
|
||||
ActiveAuthorities::<T>::insert(&network_curve, authorities);
|
||||
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
|
||||
|
||||
let nonce_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_exodus_nonce_commitment(
|
||||
exodus_session,
|
||||
dummy_nonce.clone(),
|
||||
dummy_nonce.clone(),
|
||||
)
|
||||
.expect("Nonce commitment package should be valid");
|
||||
|
||||
let exodus_package = ExodusPackage::NonceCommitment(nonce_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&exodus_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, exodus_package, signature);
|
||||
|
||||
let roast_session = RoastSessions::<T>::get(&exodus_session, authority_index)
|
||||
.expect("ROAST session should exist after nonce commitment");
|
||||
|
||||
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
|
||||
assert!(roast_state.nonce_committers.contains(authority_index));
|
||||
assert!(!roast_state.group_committers.contains(authority_index));
|
||||
assert!(!roast_state.partial_signers.contains(authority_index));
|
||||
|
||||
let registered_nonces = NonceCommitments::<T>::get(
|
||||
(exodus_session, roast_session),
|
||||
authority_index,
|
||||
).expect("Nonces should be registered during nonce commitment");
|
||||
|
||||
assert_eq!(registered_nonces.hiding, dummy_nonce.clone());
|
||||
assert_eq!(registered_nonces.binding, dummy_nonce);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_group_commitment() -> Result<(), BenchmarkError> {
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(1);
|
||||
|
||||
loop {
|
||||
if !dkg_state.is_dkg_pending() { break; }
|
||||
dkg_state.next_phase();
|
||||
}
|
||||
|
||||
dkg_state.insert_index(authority_index);
|
||||
let exodus_session = CurrentExodus::<T>::get();
|
||||
|
||||
let dummy_bytes = EvmBytes32::repeat_byte(69);
|
||||
let dummy_merkle_root = ExodusHash::repeat_byte(69);
|
||||
let dummy_group_commitment = vec![0u8; network_curve.element_bytes_len()];
|
||||
let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0);
|
||||
|
||||
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
||||
let active_dkg: ActivatedState<T> = (&dkg_state).into();
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
|
||||
ActiveAuthorities::<T>::insert(&network_curve, authorities);
|
||||
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
|
||||
|
||||
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
|
||||
roast_state.status = RoastStatus::GroupCommitments;
|
||||
roast_state.nonce_committers.insert(authority_index);
|
||||
|
||||
let roast_session = 3;
|
||||
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
|
||||
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
|
||||
|
||||
let group_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_exodus_group_commitment(
|
||||
exodus_session,
|
||||
dummy_group_commitment.clone(),
|
||||
dummy_bytes,
|
||||
)
|
||||
.expect("Group commitment package should be valid");
|
||||
|
||||
let exodus_package = ExodusPackage::GroupCommitment(group_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&exodus_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, exodus_package, signature);
|
||||
|
||||
let bounded_group_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>::try_from(
|
||||
dummy_group_commitment,
|
||||
).expect("Group commtiment should be valid");
|
||||
|
||||
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
|
||||
assert!(roast_state.nonce_committers.contains(authority_index));
|
||||
assert!(roast_state.group_committers.contains(authority_index));
|
||||
|
||||
let consensus = GroupCommitmentsConsensus::<T>::get(exodus_session, roast_session);
|
||||
assert!(consensus.participants.contains(authority_index));
|
||||
assert_eq!(&consensus.element_bytes, &bounded_group_commitment);
|
||||
assert_eq!(consensus.merkle_root, dummy_merkle_root);
|
||||
|
||||
let group_commitment_key = (exodus_session, roast_session, dummy_merkle_root, bounded_group_commitment);
|
||||
let participants = GroupCommitters::<T>::get(group_commitment_key);
|
||||
assert!(participants.contains(authority_index));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register_signature_share() -> Result<(), BenchmarkError> {
|
||||
let max_signers = T::MaxAuthorities::get();
|
||||
let threshold = get_byzantium_threshold(max_signers);
|
||||
|
||||
let (mut dkg_state, network_curve, authority, authority_index) =
|
||||
prepare_pallet::<T>(max_signers as usize);
|
||||
|
||||
loop {
|
||||
if !dkg_state.is_dkg_pending() { break; }
|
||||
dkg_state.next_phase();
|
||||
}
|
||||
|
||||
dkg_state.insert_index(authority_index);
|
||||
let dkg_index = dkg_state.get_dkg_index();
|
||||
let exodus_session = CurrentExodus::<T>::get();
|
||||
|
||||
let mut dummy_element = vec![0u8; network_curve.element_bytes_len()];
|
||||
dummy_element[0] = 0x02;
|
||||
let g_x_coords = [
|
||||
0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
|
||||
0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
|
||||
0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
|
||||
0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98
|
||||
];
|
||||
dummy_element[1..33].copy_from_slice(&g_x_coords);
|
||||
|
||||
let dummy_bytes = EvmBytes32::repeat_byte(69);
|
||||
let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0);
|
||||
|
||||
let mut dummy_scalar = vec![0u8; network_curve.scalar_bytes_len()];
|
||||
dummy_scalar[network_curve.scalar_bytes_len() - 1] = 1;
|
||||
|
||||
let dummy_signature_share = dummy_scalar.clone();
|
||||
let dummy_binding_factor = dummy_scalar.clone();
|
||||
let dummy_group_commitment = dummy_element.clone();
|
||||
|
||||
let (dummy_merkle_root, dummy_merkle_proof) =
|
||||
with_ciphersuite!(network_curve, |f| {
|
||||
let mut dummy_values = BTreeMap::new();
|
||||
|
||||
for i in 0..threshold {
|
||||
let non_zero_index = (i as AuthIndex).saturating_add(1);
|
||||
let identifier: frost_core::Identifier<Ciphersuite> =
|
||||
frost_core::Identifier::<Ciphersuite>::try_from(non_zero_index)
|
||||
.expect("Identifier should be created");
|
||||
|
||||
dummy_values.insert(identifier, dummy_binding_factor.clone());
|
||||
}
|
||||
|
||||
let merkle_tree =
|
||||
NetworkCurve::build_merkle_tree(
|
||||
&dummy_values,
|
||||
|value: &Vec<u8>| Ok(value.clone()),
|
||||
).expect("Merkle tree for binding factor should be valid");
|
||||
|
||||
let merkle_root = merkle_tree.last().copied().unwrap();
|
||||
|
||||
let (_, merkle_proof) =
|
||||
NetworkCurve::generate_merkle_proof_from_tree(
|
||||
&dummy_values,
|
||||
&merkle_tree,
|
||||
authority_index,
|
||||
|value: &Vec<u8>| Ok(value.clone())
|
||||
).expect("Merkle tree proof should be valid");
|
||||
|
||||
(merkle_root, merkle_proof)
|
||||
});
|
||||
|
||||
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
||||
let active_dkg: ActivatedState<T> = (&dkg_state).into();
|
||||
let active_dkg_index = active_dkg.get_dkg_index();
|
||||
|
||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
|
||||
ActiveAuthorities::<T>::insert(&network_curve, authorities);
|
||||
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
|
||||
|
||||
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
|
||||
roast_state.status = RoastStatus::PartialSignatures;
|
||||
roast_state.nonce_committers.insert(authority_index);
|
||||
roast_state.group_committers.insert(authority_index);
|
||||
|
||||
(0..threshold).for_each(|i| {
|
||||
if i as AuthIndex == authority_index { return; }
|
||||
roast_state.partial_signers.insert(i);
|
||||
});
|
||||
|
||||
let roast_session = 3;
|
||||
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
|
||||
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
|
||||
|
||||
let bounded_group_commitment = BoundedVec::try_from(dummy_group_commitment).unwrap();
|
||||
let dummy_verifying_key = BoundedVec::try_from(dummy_element.clone()).unwrap();
|
||||
|
||||
let dummy_verifying_share = BoundedVec::try_from(dummy_element.clone()).unwrap();
|
||||
let dummy_nonce = ExodusSeparatedNonce::new(
|
||||
BoundedVec::try_from(dummy_element.clone()).unwrap(),
|
||||
BoundedVec::try_from(dummy_element.clone()).unwrap(),
|
||||
);
|
||||
|
||||
ActiveVerifyingKey::<T>::insert(&network_curve, dummy_verifying_key);
|
||||
VerifyingShares::<T>::insert((&network_curve, active_dkg_index, authority_index), dummy_verifying_share);
|
||||
NonceCommitments::<T>::insert((&exodus_session, &roast_session), authority_index, dummy_nonce);
|
||||
|
||||
GroupCommitmentsConsensus::<T>::mutate(&exodus_session, &roast_session, |consensus| {
|
||||
consensus.element_bytes = bounded_group_commitment.clone();
|
||||
consensus.merkle_root = dummy_merkle_root;
|
||||
(0..threshold).for_each(|i| {
|
||||
consensus.participants.insert(i);
|
||||
});
|
||||
});
|
||||
|
||||
let share_package = PackageContext::default()
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_authority_index(authority_index)
|
||||
.with_exodus_signature_share(
|
||||
exodus_session,
|
||||
dummy_signature_share.clone(),
|
||||
dummy_merkle_proof,
|
||||
dummy_binding_factor
|
||||
)
|
||||
.expect("Signature share package should be valid");
|
||||
|
||||
let exodus_package = ExodusPackage::SignatureShare(share_package);
|
||||
|
||||
let signature = authority
|
||||
.sign(&exodus_package.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, exodus_package, signature);
|
||||
|
||||
let maybe_roast_session = RoastSessions::<T>::get(&exodus_session, authority_index);
|
||||
assert!(maybe_roast_session.is_none());
|
||||
|
||||
let scalar_exists = SignatureScalars::<T>::contains_key(&exodus_session, &roast_session);
|
||||
let consensus_exists = GroupCommitmentsConsensus::<T>::contains_key(&exodus_session, &roast_session);
|
||||
let committers_exists = GroupCommitters::<T>::contains_key((
|
||||
&exodus_session,
|
||||
&roast_session,
|
||||
&dummy_merkle_root,
|
||||
&bounded_group_commitment,
|
||||
));
|
||||
|
||||
assert!(!scalar_exists);
|
||||
assert!(!consensus_exists);
|
||||
assert!(!committers_exists);
|
||||
|
||||
assert!(!ExodusRequests::<T>::contains_key(&network_curve, &exodus_session));
|
||||
assert!(ExodusSignedRotations::<T>::contains_key((&exodus_session, NetworkType::Evm), dkg_index));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext_benchmarks(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
use ghost_helpers::networks::NetworkCurve;
|
||||
|
||||
use crate::AuthIndex;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExodusError {
|
||||
InvalidParticipantId,
|
||||
DeserializationError,
|
||||
SerializationError,
|
||||
InvalidMinSigners(AuthIndex, AuthIndex),
|
||||
InvalidMaxSigners(AuthIndex),
|
||||
DKGNotSupported(NetworkCurve),
|
||||
IncorrectNumberOfCoefficients,
|
||||
IncorrectNumberOfCommitments,
|
||||
IncorrectNumberOfPackages,
|
||||
IncorrectPackage,
|
||||
PackageNotFound,
|
||||
InvalidSecretShare,
|
||||
InvalidProofOfKnowledge,
|
||||
MissingCommitment,
|
||||
IncorrectCommitment,
|
||||
UnknownIdentifier,
|
||||
IdentityCommitment,
|
||||
DuplicatedIdentifier,
|
||||
IncorrectNumberOfIdentifiers,
|
||||
InvalidSignature,
|
||||
InvalidSignatureShare,
|
||||
InvalidNonceCommitments,
|
||||
EncryptionFailed,
|
||||
DecryptionFailed,
|
||||
HKDFFailed,
|
||||
InvalidNonceLength,
|
||||
MalformedSigningKey,
|
||||
NoStoredNetworks,
|
||||
OffchainTimeoutPeriod,
|
||||
BadSignature(AuthIndex),
|
||||
TransactionSubmissionFailed(AuthIndex),
|
||||
UnknownReceiverIndex(AuthIndex),
|
||||
CouldNotConstructMessage(AuthIndex),
|
||||
NothingToWrite,
|
||||
UnknownPrefix,
|
||||
UnexpectedRound,
|
||||
InvalidMerkleProof,
|
||||
InvalidIdentityElement,
|
||||
NoActiveAuthorities,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ExodusError {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match *self {
|
||||
ExodusError::InvalidParticipantId => write!(fmt, "Participant index could not be parsed to identifier."),
|
||||
ExodusError::DeserializationError => write!(fmt, "Error during deserializing value."),
|
||||
ExodusError::SerializationError => write!(fmt, "Error serializing value."),
|
||||
ExodusError::InvalidMinSigners(min, max) => write!(fmt, "Number of min_signers ({min}) must be at least 2 and not larger than max_signers ({max})."),
|
||||
ExodusError::InvalidMaxSigners(max_signers) => write!(fmt, "Number of max_signers must be at least 2, {max_signers} given."),
|
||||
ExodusError::DKGNotSupported(ref curve) => write!(fmt, "The ciphersuite {curve:?} does not support DKG."),
|
||||
ExodusError::IncorrectNumberOfCoefficients => write!(fmt, "Incorrect number of coefficients; should be equal to min signers."),
|
||||
ExodusError::IncorrectNumberOfCommitments => write!(fmt, "Incorrect number of commitments; should be equal to min signers."),
|
||||
ExodusError::InvalidNonceCommitments => write!(fmt, "Hiding nonce commitments indexes should match binding nonce commitments."),
|
||||
ExodusError::InvalidProofOfKnowledge => write!(fmt, "The proof of knowledge is not valid."),
|
||||
ExodusError::IncorrectNumberOfPackages => write!(fmt, "Incorrect number of packages; should be equal to max signers."),
|
||||
ExodusError::IncorrectPackage => write!(fmt, "The incorrect package was specified."),
|
||||
ExodusError::PackageNotFound => write!(fmt, "Round 1 package not found for Round 2 participant."),
|
||||
ExodusError::InvalidSecretShare => write!(fmt, "Invalid secret share."),
|
||||
ExodusError::MissingCommitment => write!(fmt, "The Signing Package must contain the participant's Commitments."),
|
||||
ExodusError::IncorrectCommitment => write!(fmt, "The participant's commitment is incorrect."),
|
||||
ExodusError::UnknownIdentifier => write!(fmt, "Unknown identifier."),
|
||||
ExodusError::IdentityCommitment => write!(fmt, "Commitment equals the identity."),
|
||||
ExodusError::DuplicatedIdentifier => write!(fmt, "Duplicated identifier."),
|
||||
ExodusError::IncorrectNumberOfIdentifiers => write!(fmt, "Incorrect number of identifiers."),
|
||||
ExodusError::InvalidSignature => write!(fmt, "Invalid signature."),
|
||||
ExodusError::InvalidSignatureShare => write!(fmt, "Invalid signature share."),
|
||||
ExodusError::EncryptionFailed => write!(fmt, "Encryption failed."),
|
||||
ExodusError::DecryptionFailed => write!(fmt, "Decryption failed."),
|
||||
ExodusError::HKDFFailed => write!(fmt, "HKDF failed."),
|
||||
ExodusError::InvalidNonceLength => write!(fmt, "Encryption nonce has invalid length."),
|
||||
ExodusError::MalformedSigningKey => write!(fmt, "Malformed signing key encoding."),
|
||||
ExodusError::NoStoredNetworks => write!(fmt, "No stored networks to be worked on."),
|
||||
ExodusError::OffchainTimeoutPeriod => write!(fmt, "Offchain request should be in-flight."),
|
||||
ExodusError::BadSignature(auth_index) => write!(fmt, "Signature could not be created from {auth_index}."),
|
||||
ExodusError::TransactionSubmissionFailed(auth_index) => write!(fmt, "Could not submit transaction from {auth_index}."),
|
||||
ExodusError::UnknownReceiverIndex(receiver_index) => write!(fmt, "Unknown receiver index {receiver_index} found."),
|
||||
ExodusError::CouldNotConstructMessage(auth_index) => write!(fmt, "Could not construct message during OCW from {auth_index}."),
|
||||
ExodusError::NothingToWrite => write!(fmt, "Nothing to write into local storage."),
|
||||
ExodusError::UnknownPrefix => write!(fmt, "Unknown prefix used for local storage."),
|
||||
ExodusError::UnexpectedRound => write!(fmt, "Unexpected round provided."),
|
||||
ExodusError::InvalidMerkleProof => write!(fmt, "Invalid merkle proof provided."),
|
||||
ExodusError::InvalidIdentityElement => write!(fmt, "Invalid identity element provided."),
|
||||
ExodusError::NoActiveAuthorities => write!(fmt, "No active authorities for signing exodus transactions."),
|
||||
ExodusError::Unknown => write!(fmt, "Unknown error."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,496 +0,0 @@
|
||||
use crate::{EncryptedMessage, FrostError};
|
||||
use ghost_networks::NetworkCurve;
|
||||
|
||||
use chacha20poly1305::{
|
||||
aead::{self, Aead, AeadCore, KeyInit},
|
||||
Key as EncryptionKey,
|
||||
Nonce as EncryptionNonce,
|
||||
ChaCha20Poly1305 as EncryptionCipher,
|
||||
};
|
||||
|
||||
use hkdf::Hkdf as KeyDerivation;
|
||||
use sha2::{Digest, Sha256 as Hashing};
|
||||
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use sp_std::collections::btree_map::BTreeMap;
|
||||
|
||||
macro_rules! with_ciphersuite {
|
||||
($curve:expr, |$alias:ident| $body:block) => {
|
||||
match $curve {
|
||||
ExodusCurve::Secp256k1 => {
|
||||
use frost_secp256k1 as $alias;
|
||||
$body
|
||||
},
|
||||
NetworkCurve::Ed25519 => {
|
||||
use frost_ed25519 as $alias;
|
||||
$body
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExodusCurve {
|
||||
pub fn dkg_verify_proof_of_knowledge(
|
||||
&self,
|
||||
index: u16,
|
||||
round1_package_bytes: &[u8],
|
||||
) -> Result<(), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
frost_core::keys::dkg::verify_proof_of_knowledge(
|
||||
identifier,
|
||||
&round1_package.commitment(),
|
||||
&round1_package.proof_of_knowledge(),
|
||||
).map_err(|_| FrostError::InvalidProofOfKnowledge)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_signature_share(
|
||||
&self,
|
||||
index: u16,
|
||||
verifying_share_bytes: &[u8],
|
||||
signature_share_bytes: &[u8],
|
||||
signing_package_bytes: &[u8],
|
||||
verifying_key_bytes: &[u8],
|
||||
) -> Result<(), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let verifying_share = f::keys::VerifyingShare::deserialize(verifying_share_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
frost_core::verify_signature_share(
|
||||
identifier,
|
||||
&verifying_share,
|
||||
&signature_share,
|
||||
&signing_package,
|
||||
&verifying_key,
|
||||
).map_err(|err| match err {
|
||||
f::Error::IdentityCommitment => FrostError::IdentityCommitment,
|
||||
f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
|
||||
f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
|
||||
f::Error::InvalidSignatureShare { .. } => FrostError::InvalidSignatureShare,
|
||||
_ => FrostError::Unknown,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn aggregate_signature(
|
||||
&self,
|
||||
signing_package_bytes: &[u8],
|
||||
signature_shares_bytes: &BTreeMap<u16, Vec<u8>>,
|
||||
pubkeys_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let mut signature_shares = BTreeMap::new();
|
||||
for (&index, signature_share_bytes) in signature_shares_bytes.iter() {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
signature_shares.insert(identifier, signature_share);
|
||||
}
|
||||
|
||||
let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkeys_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signature = f::aggregate(&signing_package, &signature_shares, &pubkeys)
|
||||
.map_err(|err| match err {
|
||||
f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
|
||||
f::Error::SerializationError => FrostError::SerializationError,
|
||||
f::Error::IdentityCommitment => FrostError::IdentityCommitment,
|
||||
f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers,
|
||||
f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
|
||||
f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare,
|
||||
f::Error::InvalidSignature { .. } => FrostError::InvalidSignature,
|
||||
_ => FrostError::Unknown,
|
||||
})?;
|
||||
|
||||
let signature_bytes = signature.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
Ok(signature_bytes)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_signature_valid(
|
||||
&self,
|
||||
pubkey_bytes: &[u8],
|
||||
signature_bytes: &[u8],
|
||||
message: &[u8],
|
||||
) -> bool {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let get_verification_result = || -> Result<(), FrostError> {
|
||||
let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkey_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signature = f::Signature::deserialize(signature_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
pubkeys.verifying_key()
|
||||
.verify(message, &signature)
|
||||
.map_err(|_| FrostError::InvalidSignature)?;
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
get_verification_result().is_ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dkg_part1<R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
index: u16,
|
||||
max_signers: u16,
|
||||
min_signers: u16,
|
||||
mut rng: R,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let (round1_secret_package, round1_package) = f::keys::dkg::part1(
|
||||
identifier,
|
||||
max_signers,
|
||||
min_signers,
|
||||
&mut rng,
|
||||
).map_err(|err| {
|
||||
match err {
|
||||
f::Error::InvalidMinSigners => FrostError::InvalidMinSigners(min_signers, max_signers),
|
||||
f::Error::InvalidMaxSigners => FrostError::InvalidMaxSigners(max_signers),
|
||||
f::Error::DKGNotSupported => FrostError::DKGNotSupported(*self),
|
||||
_ => FrostError::Unknown,
|
||||
}
|
||||
})?;
|
||||
|
||||
let round1_secret_package_bytes = round1_secret_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
|
||||
let round1_package_bytes = round1_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
Ok((round1_secret_package_bytes, round1_package_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_identifier_to_index(
|
||||
identifier_bytes: Vec<u8>
|
||||
) -> Result<u16, FrostError> {
|
||||
const SIZE: usize = core::mem::size_of::<u16>();
|
||||
|
||||
let start = identifier_bytes.len().checked_sub(SIZE)
|
||||
.ok_or(FrostError::InvalidParticipantId)?;
|
||||
|
||||
let bytes_array = identifier_bytes.get(start..)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.ok_or(FrostError::InvalidParticipantId)?;
|
||||
|
||||
Ok(u16::from_be_bytes(bytes_array))
|
||||
}
|
||||
|
||||
pub fn dkg_part2(
|
||||
&self,
|
||||
round1_secret_package_bytes: &[u8],
|
||||
round1_packages_bytes: &BTreeMap<u16, Vec<u8>>,
|
||||
) -> Result<(Vec<u8>, BTreeMap<u16, Vec<u8>>), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let mut round1_packages = BTreeMap::new();
|
||||
for (&index, round1_package_bytes) in round1_packages_bytes.iter() {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
round1_packages.insert(identifier, round1_package);
|
||||
}
|
||||
|
||||
let (round2_secret_package, round2_packages) = f::keys::dkg::part2(
|
||||
round1_secret_package,
|
||||
&round1_packages,
|
||||
).map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
|
||||
f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages,
|
||||
f::Error::InvalidProofOfKnowledge { .. } => FrostError::InvalidProofOfKnowledge,
|
||||
_ => FrostError::Unknown,
|
||||
})?;
|
||||
|
||||
let round2_secret_package_bytes = round2_secret_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
let mut round2_packages_bytes = BTreeMap::new();
|
||||
for (participant_identifier, round2_package) in round2_packages.iter() {
|
||||
let index = Self::convert_identifier_to_index(
|
||||
participant_identifier.serialize(),
|
||||
)?;
|
||||
|
||||
let round2_package_bytes = round2_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
round2_packages_bytes.insert(index as u16, round2_package_bytes);
|
||||
}
|
||||
|
||||
Ok((round2_secret_package_bytes, round2_packages_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dkg_encrypt_round2_package<R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
associated_data: &[u8],
|
||||
round2_package_bytes: &[u8],
|
||||
mut rng: R,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
|
||||
let payload = aead::Payload {
|
||||
msg: round2_package_bytes,
|
||||
aad: associated_data,
|
||||
};
|
||||
|
||||
let nonce = EncryptionCipher::generate_nonce(&mut rng);
|
||||
let ciphertext = cipher.encrypt(&nonce, payload)
|
||||
.map_err(|_| FrostError::EncryptionFailed)?;
|
||||
|
||||
Ok((ciphertext, nonce.to_vec()))
|
||||
}
|
||||
|
||||
pub fn dkg_decrypt_round2_package(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
encrypted_message: &EncryptedMessage,
|
||||
associated_data: &[u8],
|
||||
) -> Result<Vec<u8>, FrostError> {
|
||||
let nonce = EncryptionNonce::from_iter(encrypted_message.nonce.iter().cloned());
|
||||
let payload = aead::Payload {
|
||||
msg: &encrypted_message.ciphertext,
|
||||
aad: associated_data,
|
||||
};
|
||||
|
||||
cipher.decrypt(&nonce, payload)
|
||||
.map_err(|_| FrostError::DecryptionFailed)
|
||||
}
|
||||
|
||||
pub fn prepare_additional_info(
|
||||
&self,
|
||||
sender_index: u16,
|
||||
receiver_index: u16,
|
||||
session_index: u16,
|
||||
) -> Vec<u8> {
|
||||
let mut additional_info = match self {
|
||||
ExodusCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(),
|
||||
};
|
||||
|
||||
additional_info.extend_from_slice(&sender_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&receiver_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&session_index.to_be_bytes());
|
||||
|
||||
additional_info
|
||||
}
|
||||
|
||||
pub fn prepare_cipher_from_keys(
|
||||
&self,
|
||||
round1_receiver_package_bytes: &[u8],
|
||||
round1_secret_package_bytes: &[u8],
|
||||
additional_info: &[u8],
|
||||
) -> Result<EncryptionCipher, FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let coefficients =
|
||||
f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?
|
||||
.coefficients();
|
||||
|
||||
let sender_secret_key = coefficients
|
||||
.first()
|
||||
.ok_or(FrostError::IncorrectNumberOfCoefficients)?;
|
||||
|
||||
let receiver_public_key =
|
||||
f::keys::dkg::round1::Package::deserialize(round1_receiver_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?
|
||||
.commitment()
|
||||
.coefficients()
|
||||
.first()
|
||||
.ok_or(FrostError::MissingCommitment)?
|
||||
.value();
|
||||
|
||||
let shared_secret_bytes = f::VerifyingKey::new(receiver_public_key * sender_secret_key)
|
||||
.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
let salt = Hashing::digest(&additional_info);
|
||||
let hk = KeyDerivation::<Hashing>::new(Some(&salt), &shared_secret_bytes);
|
||||
|
||||
let mut encryption_key = EncryptionKey::default();
|
||||
hk.expand(additional_info, &mut encryption_key)
|
||||
.map_err(|_| FrostError::HKDFFailed)?;
|
||||
|
||||
let cipher = EncryptionCipher::new_from_slice(&encryption_key)
|
||||
.expect("could not happen; length is already correct qed");
|
||||
|
||||
Ok(cipher)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dkg_verify_private_package(
|
||||
&self,
|
||||
index: u16,
|
||||
round1_package_bytes: &[u8],
|
||||
round2_package_bytes: &[u8],
|
||||
) -> Result<(), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map(|round1_package| round1_package.commitment().clone())
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
|
||||
.map(|round2_package| {
|
||||
let signing_share = round2_package.signing_share().clone();
|
||||
f::keys::SecretShare::new(identifier, signing_share, commitment)
|
||||
})
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let _ = secret_share.verify().map_err(|_| FrostError::InvalidSecretShare)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dkg_part3(
|
||||
&self,
|
||||
round2_secret_package_bytes: &[u8],
|
||||
round1_packages_bytes: &BTreeMap<u16, Vec<u8>>,
|
||||
round2_packages_bytes: &BTreeMap<u16, Vec<u8>>,
|
||||
) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let mut round1_packages = BTreeMap::new();
|
||||
for (&index, round1_package_bytes) in round1_packages_bytes.iter() {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
round1_packages.insert(identifier, round1_package);
|
||||
}
|
||||
|
||||
let mut round2_packages = BTreeMap::new();
|
||||
for (&index, round2_package_bytes) in round2_packages_bytes.iter() {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
round2_packages.insert(identifier, round2_package);
|
||||
}
|
||||
|
||||
let (key_package, pubkey_package) = f::keys::dkg::part3(
|
||||
&round2_secret_package,
|
||||
&round1_packages,
|
||||
&round2_packages,
|
||||
).map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages,
|
||||
f::Error::IncorrectPackage => FrostError::IncorrectPackage,
|
||||
f::Error::PackageNotFound => FrostError::PackageNotFound,
|
||||
f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare,
|
||||
f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
|
||||
_ => FrostError::Unknown,
|
||||
})?;
|
||||
|
||||
let key_package_bytes = key_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
let pubkey_package_bytes = pubkey_package.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
Ok((key_package_bytes, pubkey_package_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_nonce<R: CryptoRng + RngCore>(
|
||||
&self,
|
||||
secret_key_package_bytes: &[u8],
|
||||
mut rng: R
|
||||
) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let signing_share = f::keys::KeyPackage::deserialize(secret_key_package_bytes)
|
||||
.map(|key_package| *key_package.signing_share())
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let (signing_nonces, signing_commitments) =
|
||||
f::round1::commit(&signing_share, &mut rng);
|
||||
|
||||
let signing_nonces_bytes = signing_nonces.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
let signing_commitments_bytes = signing_commitments.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)?;
|
||||
|
||||
Ok((signing_nonces_bytes, signing_commitments_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate_signing_package(
|
||||
&self,
|
||||
commitments_map_bytes: &BTreeMap<u16, Vec<u8>>,
|
||||
message: &[u8],
|
||||
) -> Result<Vec<u8>, FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let mut commitments_map = BTreeMap::new();
|
||||
for (&index, commitment_bytes) in commitments_map_bytes.iter() {
|
||||
let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
|
||||
let commitment = f::round1::SigningCommitments::deserialize(commitment_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
commitments_map.insert(identifier, commitment);
|
||||
}
|
||||
|
||||
f::SigningPackage::new(commitments_map, message)
|
||||
.serialize()
|
||||
.map_err(|_| FrostError::SerializationError)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sign_message(
|
||||
&self,
|
||||
signing_package_bytes: &[u8],
|
||||
signer_nonces_bytes: &[u8],
|
||||
secret_key_share_bytes: &[u8]
|
||||
) -> Result<Vec<u8>, FrostError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let secret_key_share = f::keys::KeyPackage::deserialize(secret_key_share_bytes)
|
||||
.map_err(|_| FrostError::DeserializationError)?;
|
||||
|
||||
let signature_share_bytes = f::round2::sign(&signing_package, &signer_nonces, &secret_key_share)
|
||||
.map(|signature_share| signature_share.serialize())
|
||||
.map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
|
||||
f::Error::MissingCommitment => FrostError::MissingCommitment,
|
||||
f::Error::IncorrectCommitment => FrostError::IncorrectCommitment,
|
||||
f::Error::SerializationError => FrostError::SerializationError,
|
||||
f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
|
||||
f::Error::IdentityCommitment => FrostError::IdentityCommitment,
|
||||
f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
|
||||
f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers,
|
||||
_ => FrostError::Unknown,
|
||||
})?;
|
||||
|
||||
Ok(signature_share_bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
use crate::{AuthIndex, ExodusError, NetworkCurve};
|
||||
|
||||
use ghost_traits::exodus::IdentifierConverter;
|
||||
|
||||
impl IdentifierConverter<AuthIndex, ExodusError> for NetworkCurve {
|
||||
fn convert_identifier_to_index(
|
||||
identifier_bytes: &[u8],
|
||||
) -> Result<AuthIndex, ExodusError> {
|
||||
const SIZE: usize = core::mem::size_of::<AuthIndex>();
|
||||
|
||||
let start = identifier_bytes.len().checked_sub(SIZE)
|
||||
.ok_or(ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let bytes_array = identifier_bytes.get(start..)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.ok_or(ExodusError::InvalidParticipantId)?;
|
||||
|
||||
AuthIndex::from_be_bytes(bytes_array)
|
||||
.checked_sub(1)
|
||||
.ok_or(ExodusError::InvalidParticipantId)
|
||||
}
|
||||
|
||||
fn non_zero_index(index: AuthIndex) -> Result<AuthIndex, ExodusError> {
|
||||
index.checked_add(1).ok_or(ExodusError::InvalidParticipantId)
|
||||
}
|
||||
}
|
||||
@ -1,351 +0,0 @@
|
||||
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use ghost_traits::exodus::{
|
||||
DistributedKeyGeneration, IdentifierConverter, MerkleTreeBuilder,
|
||||
};
|
||||
|
||||
use crate::{AuthIndex, ExodusError, NetworkCurve};
|
||||
|
||||
impl DistributedKeyGeneration<AuthIndex, ExodusError> for NetworkCurve {
|
||||
type RoundPackages = BTreeMap<AuthIndex, Vec<u8>>;
|
||||
|
||||
type Part1Result = Result<(Vec<u8>, Vec<u8>), ExodusError>;
|
||||
type Part2Result = Result<(Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>), ExodusError>;
|
||||
type Part3Result = Result<
|
||||
(
|
||||
Vec<u8>, Vec<u8>,
|
||||
Vec<<Self as MerkleTreeBuilder<AuthIndex, ExodusError>>::Hash>,
|
||||
<Self as MerkleTreeBuilder<AuthIndex, ExodusError>>::Hash,
|
||||
),
|
||||
ExodusError
|
||||
>;
|
||||
|
||||
fn dkg_narrow_round_max_signers(
|
||||
&self,
|
||||
secret_package_bytes: &[u8],
|
||||
new_max_signers: AuthIndex,
|
||||
round_number: u8,
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
macro_rules! process_secret_package {
|
||||
(
|
||||
$package_type:ty,
|
||||
$field1:ident : $action1:tt,
|
||||
$field2:ident : $action2:tt,
|
||||
) => {{
|
||||
let secret_package = <$package_type>::deserialize(secret_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let min_signers = *secret_package.min_signers();
|
||||
|
||||
frost_core::keys::validate_num_of_signers(min_signers, new_max_signers)
|
||||
.map_err(|err| match err {
|
||||
f::Error::InvalidMinSigners => {
|
||||
ExodusError::InvalidMinSigners(min_signers, new_max_signers)
|
||||
},
|
||||
f::Error::InvalidMaxSigners => {
|
||||
ExodusError::InvalidMaxSigners(new_max_signers)
|
||||
},
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
macro_rules! apply_action {
|
||||
($field:ident, clone) => { secret_package.$field().clone() };
|
||||
($field:ident, value) => { secret_package.$field() };
|
||||
}
|
||||
|
||||
let new_secret_package = <$package_type>::new(
|
||||
secret_package.identifier().clone(),
|
||||
apply_action!($field1, $action1),
|
||||
apply_action!($field2, $action2),
|
||||
min_signers,
|
||||
new_max_signers,
|
||||
);
|
||||
|
||||
new_secret_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)
|
||||
}};
|
||||
}
|
||||
|
||||
match round_number {
|
||||
1 => process_secret_package!(
|
||||
f::keys::dkg::round1::SecretPackage,
|
||||
coefficients: value,
|
||||
commitment: clone,
|
||||
),
|
||||
2 => process_secret_package!(
|
||||
f::keys::dkg::round2::SecretPackage,
|
||||
commitment: clone,
|
||||
secret_share: value,
|
||||
),
|
||||
_ => Err(ExodusError::UnexpectedRound),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_verify_proof_of_knowledge(
|
||||
&self,
|
||||
index: AuthIndex,
|
||||
round1_package_bytes: &[u8],
|
||||
) -> Result<AuthIndex, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let commitment = round1_package.commitment();
|
||||
|
||||
frost_core::keys::dkg::verify_proof_of_knowledge(
|
||||
identifier,
|
||||
&commitment,
|
||||
&round1_package.proof_of_knowledge(),
|
||||
).map_err(|_| ExodusError::InvalidProofOfKnowledge)?;
|
||||
|
||||
let coefficients = commitment.coefficients();
|
||||
let coefficients_len = coefficients.len() as AuthIndex;
|
||||
|
||||
Ok(coefficients_len)
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_part1<R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
index: AuthIndex,
|
||||
max_signers: AuthIndex,
|
||||
min_signers: AuthIndex,
|
||||
mut rng: R,
|
||||
) -> Self::Part1Result {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let (round1_secret_package, round1_package) = f::keys::dkg::part1(
|
||||
identifier,
|
||||
max_signers,
|
||||
min_signers,
|
||||
&mut rng,
|
||||
).map_err(|err| {
|
||||
match err {
|
||||
f::Error::InvalidMinSigners => ExodusError::InvalidMinSigners(min_signers, max_signers),
|
||||
f::Error::InvalidMaxSigners => ExodusError::InvalidMaxSigners(max_signers),
|
||||
f::Error::DKGNotSupported => ExodusError::DKGNotSupported(*self),
|
||||
_ => ExodusError::Unknown,
|
||||
}
|
||||
})?;
|
||||
|
||||
let round1_secret_package_bytes = round1_secret_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let round1_package_bytes = round1_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
Ok((round1_secret_package_bytes, round1_package_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_part2(
|
||||
&self,
|
||||
round1_secret_package_bytes: &[u8],
|
||||
round1_packages_bytes: &Self::RoundPackages,
|
||||
) -> Self::Part2Result {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
type InnerField = <InnerGroup as frost_core::Group>::Field;
|
||||
|
||||
let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let round1_packages = round1_packages_bytes
|
||||
.iter()
|
||||
.map(|(&index, round1_package_bytes)| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok((identifier, round1_package))
|
||||
})
|
||||
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round1::Package>, ExodusError>>()?;
|
||||
|
||||
let (round2_secret_package, round2_packages) = f::keys::dkg::part2(
|
||||
round1_secret_package,
|
||||
&round1_packages,
|
||||
).map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
|
||||
f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages,
|
||||
f::Error::InvalidProofOfKnowledge { .. } => ExodusError::InvalidProofOfKnowledge,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let round2_secret_package_bytes = round2_secret_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let round2_packages_bytes = round2_packages
|
||||
.iter()
|
||||
.map(|(id, round2_package)| {
|
||||
let id_scalar = id.to_scalar();
|
||||
let id_bytes = <InnerField as frost_core::Field>::serialize(&id_scalar);
|
||||
|
||||
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
|
||||
let round2_package_bytes = round2_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
Ok((index, round2_package_bytes))
|
||||
})
|
||||
.collect::<Result<BTreeMap<AuthIndex, Vec<u8>>, ExodusError>>()?;
|
||||
|
||||
Ok((round2_secret_package_bytes, round2_packages_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_verify_private_package(
|
||||
&self,
|
||||
index: AuthIndex,
|
||||
round1_package_bytes: &[u8],
|
||||
round2_package_bytes: &[u8],
|
||||
) -> Result<(), ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map(|round1_package| round1_package.commitment().clone())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
|
||||
.map(|round2_package| {
|
||||
let signing_share = round2_package.signing_share().clone();
|
||||
f::keys::SecretShare::new(identifier, signing_share, commitment)
|
||||
})
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let _ = secret_share.verify().map_err(|_| ExodusError::InvalidSecretShare)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_part3(
|
||||
&self,
|
||||
authority_index: AuthIndex,
|
||||
round2_secret_package_bytes: &[u8],
|
||||
round1_packages_bytes: &Self::RoundPackages,
|
||||
round2_packages_bytes: &Self::RoundPackages,
|
||||
) -> Self::Part3Result {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let round1_packages = round1_packages_bytes
|
||||
.iter()
|
||||
.map(|(&index, round1_package_bytes)| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok((identifier, round1_package))
|
||||
})
|
||||
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round1::Package>, ExodusError>>()?;
|
||||
|
||||
let round2_packages = round2_packages_bytes
|
||||
.iter()
|
||||
.map(|(&index, round2_package_bytes)| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok((identifier, round2_package))
|
||||
})
|
||||
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round2::Package>, ExodusError>>()?;
|
||||
|
||||
let (key_package, pubkey_package) = f::keys::dkg::part3(
|
||||
&round2_secret_package,
|
||||
&round1_packages,
|
||||
&round2_packages,
|
||||
).map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages,
|
||||
f::Error::IncorrectPackage => ExodusError::IncorrectPackage,
|
||||
f::Error::PackageNotFound => ExodusError::PackageNotFound,
|
||||
f::Error::InvalidSecretShare { .. } => ExodusError::InvalidSecretShare,
|
||||
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let key_package_bytes = key_package.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let verifying_shares_merkle_tree =
|
||||
Self::build_merkle_tree(
|
||||
pubkey_package.verifying_shares(),
|
||||
|verifying_share| verifying_share.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)
|
||||
)?;
|
||||
|
||||
let (_, merkle_proof) =
|
||||
Self::generate_merkle_proof_from_tree(
|
||||
pubkey_package.verifying_shares(),
|
||||
&verifying_shares_merkle_tree,
|
||||
authority_index,
|
||||
|verifying_share| verifying_share.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)
|
||||
)?;
|
||||
|
||||
let merkle_root = verifying_shares_merkle_tree
|
||||
.last()
|
||||
.copied()
|
||||
.ok_or(ExodusError::IncorrectNumberOfPackages)?;
|
||||
|
||||
let verifying_key_bytes = pubkey_package.verifying_key()
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
Ok((
|
||||
key_package_bytes,
|
||||
verifying_key_bytes,
|
||||
merkle_proof,
|
||||
merkle_root,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_derive_verifying_share(
|
||||
&self,
|
||||
secret_key_package_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let secret_key_package =
|
||||
f::keys::KeyPackage::deserialize(secret_key_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
secret_key_package
|
||||
.verifying_share()
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)
|
||||
})
|
||||
}
|
||||
|
||||
fn dkg_decompress_key(
|
||||
&self,
|
||||
verifying_key_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
decomporess_key(verifying_key)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
use sp_std::{
|
||||
vec::Vec,
|
||||
result::Result,
|
||||
marker::PhantomData,
|
||||
};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use ghost_traits::exodus::EllipticCurveDiffieHellman;
|
||||
|
||||
use hkdf::Hkdf as KeyDerivationFunction;
|
||||
use sha2::Sha256 as Hashing;
|
||||
|
||||
use chacha20poly1305::{
|
||||
aead::{self, Aead, KeyInit},
|
||||
Key as EncryptionKey,
|
||||
Nonce as EncryptionNonce,
|
||||
ChaCha20Poly1305 as EncryptionCipher,
|
||||
};
|
||||
|
||||
use crate::pallet::Config;
|
||||
use crate::{AuthIndex, EncryptionData, ExodusError, NetworkCurve, DkgIndex};
|
||||
|
||||
impl<T: Config> EllipticCurveDiffieHellman<AuthIndex, EncryptionCipher, EncryptionData<T>, DkgIndex, ExodusError> for NetworkCurve {
|
||||
fn ecdh_encrypt_package<R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
aad: &[u8],
|
||||
msg: &[u8],
|
||||
mut rng: R,
|
||||
) -> Result<EncryptionData<T>, ExodusError> {
|
||||
let payload = aead::Payload { msg, aad };
|
||||
|
||||
let mut nonce_bytes = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = EncryptionNonce::from_slice(&nonce_bytes);
|
||||
|
||||
let ciphertext = cipher.encrypt(&nonce, payload)
|
||||
.map_err(|_| ExodusError::EncryptionFailed)?;
|
||||
|
||||
EncryptionData::try_new(ciphertext, nonce.as_slice())
|
||||
}
|
||||
|
||||
fn ecdh_decrypt_package(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
encrypted_data: &EncryptionData<T>,
|
||||
aad: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
let nonce = EncryptionNonce::from_iter(
|
||||
encrypted_data.nonce.iter().cloned()
|
||||
);
|
||||
let payload = aead::Payload { msg: &encrypted_data.ciphertext, aad };
|
||||
cipher.decrypt(&nonce, payload).map_err(|_| ExodusError::DecryptionFailed)
|
||||
}
|
||||
|
||||
fn ecdh_prepare_additional_info(
|
||||
&self,
|
||||
sender_index: AuthIndex,
|
||||
receiver_index: AuthIndex,
|
||||
dkg_index: DkgIndex,
|
||||
_marker: PhantomData<EncryptionData<T>>,
|
||||
) -> Vec<u8> {
|
||||
let mut additional_info = match self {
|
||||
NetworkCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(),
|
||||
NetworkCurve::Ed25519 => b"EXODUS-ED25519-DKG-V1".to_vec(),
|
||||
};
|
||||
|
||||
additional_info.extend_from_slice(&sender_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&receiver_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&dkg_index.to_be_bytes());
|
||||
|
||||
additional_info
|
||||
}
|
||||
|
||||
fn ecdh_get_cipher_from_keys(
|
||||
&self,
|
||||
public: &[u8],
|
||||
secret: &[u8],
|
||||
additional_info: &[u8],
|
||||
_marker: PhantomData<EncryptionData<T>>,
|
||||
) -> Result<EncryptionCipher, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let secret_package = f::keys::dkg::round1::SecretPackage::deserialize(secret)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
let public_package = f::keys::dkg::round1::Package::deserialize(public)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let coefficients = secret_package.coefficients();
|
||||
let local_sk = coefficients.first()
|
||||
.ok_or(ExodusError::IncorrectNumberOfCoefficients)?;
|
||||
let remote_pk = public_package.commitment().coefficients().first()
|
||||
.ok_or(ExodusError::MissingCommitment)?.value();
|
||||
|
||||
let shared_secret_bytes = f::VerifyingKey::new(remote_pk * local_sk)
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let local_pk: f::VerifyingKey = f::SigningKey::from_scalar(*local_sk)
|
||||
.map_err(|_| ExodusError::MalformedSigningKey)?
|
||||
.into();
|
||||
|
||||
let local_pk_bytes = local_pk.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
let remote_pk_bytes = f::VerifyingKey::new(remote_pk)
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let mut keys = [local_pk_bytes, remote_pk_bytes];
|
||||
keys.sort();
|
||||
|
||||
let salt = keys.concat();
|
||||
let hk = KeyDerivationFunction::<Hashing>::new(Some(&salt), &shared_secret_bytes);
|
||||
|
||||
let mut encryption_key = EncryptionKey::default();
|
||||
hk.expand(additional_info, &mut encryption_key)
|
||||
.map_err(|_| ExodusError::HKDFFailed)?;
|
||||
|
||||
let cipher = EncryptionCipher::new_from_slice(&encryption_key)
|
||||
.expect("could not happen; length is already correct qed");
|
||||
|
||||
Ok(cipher)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,430 +0,0 @@
|
||||
use ghost_traits::exodus::{
|
||||
IdentifierConverter, MerkleTreeBuilder,
|
||||
FlexibleRoundOptimizedSchnorrThresholdSignature,
|
||||
};
|
||||
use sp_std::{
|
||||
vec::Vec, result::Result,
|
||||
collections::{btree_set::BTreeSet, btree_map::BTreeMap},
|
||||
};
|
||||
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use ghost_helpers::networks::NetworkCurve;
|
||||
use crate::{AuthIndex, ExodusHash, ExodusError};
|
||||
|
||||
impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for NetworkCurve {
|
||||
type Packages<'a> = BTreeMap<AuthIndex, &'a [u8]>;
|
||||
type NoncePackages<'a> = BTreeMap<AuthIndex, (&'a [u8], &'a [u8])>;
|
||||
|
||||
fn header_bytes_len(&self) -> usize { 5 }
|
||||
|
||||
fn element_bytes_len(&self) -> usize {
|
||||
match self {
|
||||
NetworkCurve::Secp256k1 => 33,
|
||||
NetworkCurve::Ed25519 => 32,
|
||||
}
|
||||
}
|
||||
|
||||
fn scalar_bytes_len(&self) -> usize {
|
||||
match self {
|
||||
NetworkCurve::Secp256k1 => 32,
|
||||
NetworkCurve::Ed25519 => 32,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_signature_share(
|
||||
&self,
|
||||
index: AuthIndex,
|
||||
participants_iter: impl Iterator<Item = AuthIndex>,
|
||||
signature_share_bytes: &[u8],
|
||||
binding_factor_bytes: &[u8],
|
||||
nonce_hiding_bytes: &[u8],
|
||||
nonce_binding_bytes: &[u8],
|
||||
group_commitment_bytes: &[u8],
|
||||
verifying_share_bytes: &[u8],
|
||||
verifying_key_bytes: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<(), ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
type InnerField = <InnerGroup as frost_core::Group>::Field;
|
||||
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let identifier_set = participants_iter
|
||||
.filter_map(|auth_index| {
|
||||
let non_zero_index = Self::non_zero_index(auth_index).ok()?;
|
||||
non_zero_index.try_into().ok()
|
||||
})
|
||||
.collect::<BTreeSet<f::Identifier>>();
|
||||
|
||||
let lambda_i =
|
||||
frost_core::compute_lagrange_coefficient(&identifier_set, None, identifier)
|
||||
.map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
|
||||
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
|
||||
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let signature_share =
|
||||
f::round2::SignatureShare::deserialize(signature_share_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let group_commitment_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from(group_commitment_bytes.as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let group_commitment_element =
|
||||
<InnerGroup as frost_core::Group>::deserialize(&group_commitment_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_factor_serialization =
|
||||
<InnerField as frost_core::Field>::Serialization::try_from(binding_factor_bytes.as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_factor_scalar =
|
||||
<InnerField as frost_core::Field>::deserialize(&binding_factor_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let verifying_share =
|
||||
f::keys::VerifyingShare::deserialize(verifying_share_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let challenge = frost_core::challenge(
|
||||
&group_commitment_element,
|
||||
&verifying_key,
|
||||
message,
|
||||
).map_err(|err| match err {
|
||||
f::Error::SerializationError => ExodusError::SerializationError,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let hiding_element_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_hiding_bytes.as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let hiding_element =
|
||||
<InnerGroup as frost_core::Group>::deserialize(&hiding_element_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_element_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_binding_bytes.as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_element =
|
||||
<InnerGroup as frost_core::Group>::deserialize(&binding_element_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let r_i_element = hiding_element + (binding_element * binding_factor_scalar);
|
||||
let commitment_share =
|
||||
frost_core::round1::GroupCommitmentShare::from_element(r_i_element);
|
||||
|
||||
signature_share.verify(
|
||||
identifier,
|
||||
&commitment_share,
|
||||
&verifying_share,
|
||||
lambda_i,
|
||||
&challenge,
|
||||
).map_err(|err| match err {
|
||||
f::Error::InvalidSignatureShare { .. } => {
|
||||
ExodusError::InvalidSignatureShare
|
||||
},
|
||||
_ => ExodusError::Unknown,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn accumulate_signature_scalar(
|
||||
&self,
|
||||
signature_scalar_bytes: &[u8],
|
||||
signature_share_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
type InnerField = <InnerGroup as frost_core::Group>::Field;
|
||||
|
||||
let signature_scalar_serialization =
|
||||
<InnerField as frost_core::Field>::Serialization::try_from(signature_scalar_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let mut signature_scalar =
|
||||
<InnerField as frost_core::Field>::deserialize(&signature_scalar_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signature_share_serialization =
|
||||
<InnerField as frost_core::Field>::Serialization::try_from(signature_share_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signature_share =
|
||||
<InnerField as frost_core::Field>::deserialize(&signature_share_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
signature_scalar += signature_share;
|
||||
|
||||
Ok(<InnerField as frost_core::Field>::serialize(&signature_scalar).to_vec())
|
||||
})
|
||||
}
|
||||
|
||||
fn is_signature_valid(
|
||||
&self,
|
||||
verifying_key_bytes: &[u8],
|
||||
r_element_bytes: &[u8],
|
||||
z_scalar_bytes: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<(), ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
type InnerField = <InnerGroup as frost_core::Group>::Field;
|
||||
|
||||
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let r_element_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from(r_element_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let r_element =
|
||||
<InnerGroup as frost_core::Group>::deserialize(&r_element_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let z_scalar_serialization =
|
||||
<InnerField as frost_core::Field>::Serialization::try_from(z_scalar_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let z_scalar =
|
||||
<InnerField as frost_core::Field>::deserialize(&z_scalar_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signature = f::Signature::new(r_element, z_scalar);
|
||||
|
||||
verifying_key.verify(message, &signature)
|
||||
.map_err(|_| ExodusError::InvalidSignature)
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_nonce_commitment<R: CryptoRng + RngCore>(
|
||||
&self,
|
||||
secret_key_package_bytes: &[u8],
|
||||
mut rng: R
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let signing_share =
|
||||
f::keys::KeyPackage::deserialize(secret_key_package_bytes)
|
||||
.map(|key_package| *key_package.signing_share())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let (signing_nonce, signing_commitment) =
|
||||
f::round1::commit(&signing_share, &mut rng);
|
||||
|
||||
let signing_nonce_bytes = signing_nonce.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let hiding_nonce_bytes = signing_commitment.hiding()
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let binding_nonce_bytes = signing_commitment.binding()
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
Ok((signing_nonce_bytes, hiding_nonce_bytes, binding_nonce_bytes))
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_group_commitment(
|
||||
&self,
|
||||
authority_index: AuthIndex,
|
||||
nonce_commitments_bytes: &Self::NoncePackages<'_>,
|
||||
verifying_key_bytes: &[u8],
|
||||
message: &[u8],
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Vec<ExodusHash>, ExodusHash), ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
type InnerField = <InnerGroup as frost_core::Group>::Field;
|
||||
|
||||
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signing_commitments = nonce_commitments_bytes
|
||||
.iter()
|
||||
.filter_map(|(&index, &(hiding_bytes, binding_bytes))| {
|
||||
let non_zero_index = Self::non_zero_index(index).ok()?;
|
||||
let identifier = non_zero_index.try_into().ok()?;
|
||||
|
||||
let hiding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref()).ok()?;
|
||||
let hiding_nonce = f::round1::NonceCommitment::deserialize(&hiding_serialization).ok()?;
|
||||
|
||||
let binding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref()).ok()?;
|
||||
let binding_nonce = f::round1::NonceCommitment::deserialize(&binding_serialization).ok()?;
|
||||
|
||||
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
|
||||
|
||||
Some((identifier, commitments))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
if nonce_commitments_bytes.len() != signing_commitments.len() {
|
||||
return Err(ExodusError::InvalidParticipantId);
|
||||
}
|
||||
|
||||
let signing_package = f::SigningPackage::new(signing_commitments, message);
|
||||
|
||||
let binding_factors_list_wrapped =
|
||||
frost_core::compute_binding_factor_list(&signing_package, &verifying_key, &[])
|
||||
.map_err(|err| match err {
|
||||
f::Error::SerializationError => ExodusError::SerializationError,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let group_commitment =
|
||||
frost_core::compute_group_commitment(&signing_package, &binding_factors_list_wrapped)
|
||||
.map_err(|err| match err {
|
||||
f::Error::IdentityCommitment => ExodusError::IdentityCommitment,
|
||||
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
let binding_factors_list = nonce_commitments_bytes
|
||||
.iter()
|
||||
.filter_map(|(&index, _)| {
|
||||
let non_zero_index = Self::non_zero_index(index).ok()?;
|
||||
let identifier = non_zero_index.try_into().ok()?;
|
||||
|
||||
let binding_factor = binding_factors_list_wrapped
|
||||
.get(&identifier)
|
||||
.map(|binding_factor| {
|
||||
let fixed_bytes =
|
||||
<InnerField as frost_core::Field>::Serialization::try_from(
|
||||
binding_factor.serialize().as_slice()
|
||||
).ok()?;
|
||||
<InnerField as frost_core::Field>::deserialize(&fixed_bytes).ok()
|
||||
})
|
||||
.flatten()?;
|
||||
|
||||
Some((identifier, binding_factor))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
if nonce_commitments_bytes.len() != binding_factors_list.len() {
|
||||
return Err(ExodusError::InvalidParticipantId);
|
||||
}
|
||||
|
||||
let binding_factor_tree = Self::build_merkle_tree(
|
||||
&binding_factors_list,
|
||||
|binding_factor| {
|
||||
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
|
||||
Ok(serialized.as_ref().to_vec())
|
||||
}
|
||||
)?;
|
||||
|
||||
let (self_binding_factor_bytes, binding_factors_proof) =
|
||||
Self::generate_merkle_proof_from_tree(
|
||||
&binding_factors_list,
|
||||
&binding_factor_tree,
|
||||
authority_index,
|
||||
|binding_factor| {
|
||||
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
|
||||
Ok(serialized.as_ref().to_vec())
|
||||
}
|
||||
)?;
|
||||
|
||||
let group_commitment_bytes =
|
||||
<InnerGroup as frost_core::Group>::serialize(&group_commitment.to_element())
|
||||
.map(|serialization| serialization.to_vec())
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let binding_factors_root = binding_factor_tree
|
||||
.last()
|
||||
.cloned()
|
||||
.ok_or(ExodusError::IncorrectNumberOfPackages)?;
|
||||
|
||||
Ok((
|
||||
group_commitment_bytes,
|
||||
self_binding_factor_bytes,
|
||||
binding_factors_proof,
|
||||
binding_factors_root,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn init_signing_package(
|
||||
&self,
|
||||
nonce_commitments_bytes: &Self::NoncePackages<'_>,
|
||||
message: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
|
||||
|
||||
let signing_commitments = nonce_commitments_bytes
|
||||
.iter()
|
||||
.map(|(&index, &(hiding_bytes, binding_bytes))| {
|
||||
let identifier = Self::non_zero_index(index)?
|
||||
.try_into()
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let hiding_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let hiding_nonce =
|
||||
f::round1::NonceCommitment::deserialize(&hiding_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_serialization =
|
||||
<InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref())
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_nonce =
|
||||
f::round1::NonceCommitment::deserialize(&binding_serialization)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
|
||||
|
||||
Ok((identifier, commitments))
|
||||
})
|
||||
.collect::<Result<BTreeMap<f::Identifier, f::round1::SigningCommitments>, ExodusError>>()?;
|
||||
|
||||
f::SigningPackage::new(signing_commitments, message)
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)
|
||||
})
|
||||
}
|
||||
|
||||
fn partial_sign_message(
|
||||
&self,
|
||||
signing_package_bytes: &[u8],
|
||||
signer_nonces_bytes: &[u8],
|
||||
secret_share_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let secret_share = f::keys::KeyPackage::deserialize(secret_share_bytes)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let signature_shares =
|
||||
f::round2::sign(&signing_package, &signer_nonces, &secret_share)
|
||||
.map_err(|err| match err {
|
||||
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
|
||||
f::Error::MissingCommitment => ExodusError::MissingCommitment,
|
||||
f::Error::IncorrectCommitment => ExodusError::IncorrectCommitment,
|
||||
f::Error::SerializationError => ExodusError::SerializationError,
|
||||
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
|
||||
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
|
||||
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
|
||||
_ => ExodusError::Unknown,
|
||||
})?;
|
||||
|
||||
Ok(signature_shares.serialize())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result};
|
||||
|
||||
use frost_core::{Ciphersuite, Group, Identifier};
|
||||
use ghost_helpers::SubstrateBlake2Hasher;
|
||||
use ghost_traits::{
|
||||
exodus::{MerkleTreeBuilder, IdentifierConverter},
|
||||
hashing::GhostHasher,
|
||||
};
|
||||
|
||||
use crate::{AuthIndex, ExodusError, NetworkCurve};
|
||||
|
||||
impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
|
||||
type Hash = <SubstrateBlake2Hasher as GhostHasher>::Hash;
|
||||
|
||||
fn build_merkle_tree<C, V, F>(
|
||||
values: &BTreeMap<Identifier<C>, V>,
|
||||
serialize_fn: F,
|
||||
) -> Result<Vec<Self::Hash>, ExodusError>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
|
||||
{
|
||||
use ghost_helpers::merkle_tree::generate_tree;
|
||||
|
||||
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
|
||||
|
||||
let max_index = values.keys()
|
||||
.next_back()
|
||||
.and_then(|id| {
|
||||
let id_scalar = id.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
|
||||
})
|
||||
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
|
||||
|
||||
generate_tree::<SubstrateBlake2Hasher, _, _, _, _>(
|
||||
max_index,
|
||||
values.iter(),
|
||||
|(identifier, value)| {
|
||||
let id_scalar = identifier.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
|
||||
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
|
||||
let value_bytes = serialize_fn(value).map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
// NOTE: revisit
|
||||
// Is it possible to use [u8; CONSTANT] where constant is
|
||||
// purely dependant on provided generic.
|
||||
let mut preimage = Vec::<u8>::with_capacity(id_bytes.as_ref().len() + value_bytes.len());
|
||||
preimage.extend_from_slice(id_bytes.as_ref());
|
||||
preimage.extend_from_slice(value_bytes.as_ref());
|
||||
|
||||
Ok((index as usize, preimage))
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_merkle_proof_from_tree<C, V, F>(
|
||||
values: &BTreeMap<Identifier<C>, V>,
|
||||
merkle_tree: &[Self::Hash],
|
||||
authority_index: AuthIndex,
|
||||
serialize_fn: F,
|
||||
) -> Result<(Vec<u8>, Vec<Self::Hash>), ExodusError>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
|
||||
{
|
||||
use ghost_helpers::merkle_tree::generate_proof;
|
||||
|
||||
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
|
||||
|
||||
let non_zero_index = Self::non_zero_index(authority_index)?;
|
||||
let identifier = Identifier::try_from(non_zero_index)
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let value = values.get(&identifier).ok_or(ExodusError::InvalidParticipantId)?;
|
||||
let value_bytes = serialize_fn(&value).map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let max_index = values.keys()
|
||||
.next_back()
|
||||
.and_then(|id| {
|
||||
let id_scalar = id.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
|
||||
})
|
||||
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
|
||||
|
||||
let proof = generate_proof::<SubstrateBlake2Hasher, _>(
|
||||
merkle_tree,
|
||||
max_index,
|
||||
authority_index,
|
||||
);
|
||||
|
||||
Ok((value_bytes, proof))
|
||||
}
|
||||
|
||||
fn verify_merkle_proof(
|
||||
&self,
|
||||
values: &[u8],
|
||||
merkle_proof: &[Self::Hash],
|
||||
merkle_root: Self::Hash,
|
||||
authority_index: AuthIndex,
|
||||
) -> Result<(), ExodusError> {
|
||||
use ghost_helpers::merkle_tree::verify_tree_proof;
|
||||
|
||||
let preimage = with_ciphersuite!(self, |f| {
|
||||
let non_zero_index = Self::non_zero_index(authority_index)?;
|
||||
let identifier = f::Identifier::try_from(non_zero_index)
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let mut preimage = identifier.serialize();
|
||||
preimage.extend_from_slice(values);
|
||||
|
||||
Ok(preimage)
|
||||
})?;
|
||||
|
||||
let is_verified = verify_tree_proof::<SubstrateBlake2Hasher, _>(
|
||||
&preimage,
|
||||
merkle_proof,
|
||||
merkle_root,
|
||||
authority_index,
|
||||
);
|
||||
|
||||
is_verified.then(|| ()).ok_or(ExodusError::InvalidMerkleProof)
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
#[macro_export]
|
||||
macro_rules! with_ciphersuite {
|
||||
($curve:expr, |$alias:ident| $body:block) => {
|
||||
match $curve {
|
||||
NetworkCurve::Secp256k1 => {
|
||||
use k256::elliptic_curve::sec1::ToEncodedPoint;
|
||||
use frost_secp256k1 as $alias;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use frost_secp256k1::Secp256K1Sha256 as Ciphersuite;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn decomporess_key(
|
||||
verifying_key: $alias::VerifyingKey,
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
let element = verifying_key.to_element();
|
||||
|
||||
if element == k256::ProjectivePoint::IDENTITY {
|
||||
return Err(ExodusError::InvalidIdentityElement);
|
||||
}
|
||||
|
||||
let mut fixed_serialized = [0; 65];
|
||||
let serialized_point = element.to_affine().to_encoded_point(false);
|
||||
let serialized = serialized_point.as_bytes();
|
||||
fixed_serialized.copy_from_slice(serialized);
|
||||
|
||||
Ok(fixed_serialized.to_vec())
|
||||
}
|
||||
|
||||
$body
|
||||
},
|
||||
NetworkCurve::Ed25519 => {
|
||||
use frost_ed25519 as $alias;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use frost_ed25519::Ed25519Sha512 as Ciphersuite;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn decomporess_key(
|
||||
verifying_key: $alias::VerifyingKey,
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
verifying_key.serialize().map_err(|err| match err {
|
||||
f::Error::SerializationError => ExodusError::SerializationError,
|
||||
_ => ExodusError::Unknown,
|
||||
})
|
||||
}
|
||||
|
||||
$body
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod converter;
|
||||
|
||||
#[macro_use]
|
||||
mod merkle;
|
||||
|
||||
#[macro_use]
|
||||
mod dkg;
|
||||
|
||||
#[macro_use]
|
||||
mod ecdh;
|
||||
|
||||
#[macro_use]
|
||||
mod frost;
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,216 +0,0 @@
|
||||
#![cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
|
||||
use frame_system::EnsureRoot;
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64},
|
||||
};
|
||||
use crate as ghost_exodus;
|
||||
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
|
||||
use sp_runtime::{
|
||||
BuildStorage, curve::PiecewiseLinear, traits::ConvertInto,
|
||||
testing::{TestXt, UintAuthorityId},
|
||||
};
|
||||
|
||||
use sp_staking::SessionIndex;
|
||||
|
||||
#[cfg(test)]
|
||||
use ghost_helpers::networks::{NetworkInitiationBuilder, NetworkDataBuilder};
|
||||
|
||||
pub type MockedBalance = u64;
|
||||
pub type MockedNetworkId = u64;
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Session: pallet_session,
|
||||
Historical: pallet_session_historical,
|
||||
Balances: pallet_balances,
|
||||
Networks: ghost_networks,
|
||||
Exodus: ghost_exodus,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type Block = Block;
|
||||
type AccountData = pallet_balances::AccountData<MockedBalance>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3];
|
||||
}
|
||||
|
||||
pub struct TestSessionManager;
|
||||
impl pallet_session::SessionManager<u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
|
||||
Some(FixedAuthorities::get())
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
|
||||
Some(FixedAuthorities::get().iter().map(|l| (*l, *l)).collect())
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const Period: u64 = 1;
|
||||
pub const Offset: u64 = 0;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager =
|
||||
pallet_session::historical::NoteHistoricalRoot<Runtime, TestSessionManager>;
|
||||
type SessionHandler = (Exodus,);
|
||||
type ValidatorId = u64;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type Keys = UintAuthorityId;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Config for Runtime {
|
||||
type FullIdentification = u64;
|
||||
type FullIdentificationOf = ConvertInto;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxNetworks: u32 = 5;
|
||||
}
|
||||
|
||||
impl ghost_networks::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type NetworkId = MockedNetworkId;
|
||||
type RegisterOrigin = EnsureRoot<Self::AccountId>;
|
||||
type UpdateOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RemoveOrigin = EnsureRoot<Self::AccountId>;
|
||||
type MaxNetworks = MaxNetworks;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_006_000,
|
||||
max_inflation: 1_000_000,
|
||||
ideal_stake: 0_690_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 100,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: u64 = 2;
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub const HistoryDepth: u32 = 10;
|
||||
pub const EpochDuration: u64 = 80;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type MaxReserves = ConstU32<2>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl ghost_exodus::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AuthorityId = UintAuthorityId;
|
||||
|
||||
type ValidatorSet = Historical;
|
||||
type Currency = Balances;
|
||||
type NetworkDataHandler = Networks;
|
||||
type BlockNumberProvider = System;
|
||||
type DisabledValidators = Session;
|
||||
|
||||
type UnsignedPriority = ConstU64<{ 1 << 20 }>;
|
||||
type UnsignedLongevity = ConstU64<1>;
|
||||
type MaxAuthorities = ConstU32<10>;
|
||||
type MaxAuthoritiesChunks = ConstU32<1>;
|
||||
|
||||
type DkgRoundPeriod = ConstU64<100>;
|
||||
type RemovalLimit = ConstU32<20>;
|
||||
type MaxCursorLen = ConstU32<20>;
|
||||
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub type Extrinsic<Call> = TestXt<Call, ()>;
|
||||
|
||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
|
||||
where
|
||||
RuntimeCall: From<LocalCall>,
|
||||
{
|
||||
type OverarchingCall = RuntimeCall;
|
||||
type Extrinsic = Extrinsic<RuntimeCall>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_test_ext_benchmarks() -> sp_io::TestExternalities {
|
||||
let t = frame_system::GenesisConfig::<Runtime>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
sp_io::TestExternalities::new(t)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_test_ext() -> (
|
||||
sp_io::TestExternalities,
|
||||
std::sync::Arc<parking_lot::RwLock<sp_core::offchain::testing::PoolState>>,
|
||||
) {
|
||||
let mut t = frame_system::GenesisConfig::<Runtime>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
let network_data = NetworkDataBuilder::default()
|
||||
.with_block_deviation(69)
|
||||
.build();
|
||||
|
||||
let networks = vec![
|
||||
NetworkInitiationBuilder::<MockedNetworkId, u64>::default()
|
||||
.with_network_id(31_337)
|
||||
.with_gatekeeper_amount(0)
|
||||
.with_network_data(network_data)
|
||||
.build(),
|
||||
];
|
||||
|
||||
ghost_networks::GenesisConfig::<Runtime> { networks }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
|
||||
ext.execute_with(|| {
|
||||
for &id in FixedAuthorities::get().iter() {
|
||||
System::inc_providers(&id);
|
||||
assert_eq!(
|
||||
Session::set_keys(RuntimeOrigin::signed(id), id.into(), vec![],),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let (offchain, _) = sp_core::offchain::testing::TestOffchainExt::with_offchain_db(ext.offchain_db());
|
||||
let (tx_pool, tx_pool_state) = sp_core::offchain::testing::TestTransactionPoolExt::new();
|
||||
|
||||
ext.register_extension(sp_core::offchain::OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(sp_core::offchain::OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(sp_core::offchain::TransactionPoolExt::new(tx_pool));
|
||||
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
|
||||
(ext, tx_pool_state)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
|
||||
use crate::{ExodusHash, ELEMENT_MAX_BYTES};
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ConsensusState<Bitmap> {
|
||||
pub element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
pub participants: Bitmap,
|
||||
pub merkle_root: ExodusHash,
|
||||
}
|
||||
|
||||
impl<Bitmap> ConsensusState<Bitmap> {
|
||||
pub fn new(
|
||||
element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
participants: Bitmap,
|
||||
merkle_root: ExodusHash,
|
||||
) -> Self {
|
||||
Self { element_bytes, participants, merkle_root }
|
||||
}
|
||||
}
|
||||
@ -1,295 +0,0 @@
|
||||
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use strum::EnumCount;
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
|
||||
use ghost_helpers::networks::NetworkType;
|
||||
use ghost_traits::bounded_bitmap::{
|
||||
BoundedBitmapGenerator, BoundedBitmapReader, BoundedBitmapWriter,
|
||||
};
|
||||
|
||||
use crate::{ELEMENT_MAX_BYTES, ExodusSession, DkgIndex};
|
||||
|
||||
type RotationSessions = BoundedVec<RotationSession, ConstU32<{ NetworkType::COUNT as u32 }>>;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ReadyDkgAuthorities<AuthoritiesBitmap> {
|
||||
authorities_indexes: AuthoritiesBitmap,
|
||||
dkg_index: DkgIndex,
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap> ReadyDkgAuthorities<AuthoritiesBitmap>
|
||||
where
|
||||
AuthoritiesBitmap: BoundedBitmapReader,
|
||||
{
|
||||
pub fn contains_index<N>(&self, index: N) -> bool
|
||||
where N: UniqueSaturatedInto<usize>,
|
||||
{
|
||||
self.authorities_indexes.contains(index)
|
||||
}
|
||||
|
||||
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
|
||||
&self.authorities_indexes
|
||||
}
|
||||
|
||||
pub fn get_dkg_index(&self) -> DkgIndex {
|
||||
self.dkg_index
|
||||
}
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap, Block>
|
||||
From<&PendingDkgAuthorities<AuthoritiesBitmap, Block>>
|
||||
for ReadyDkgAuthorities<AuthoritiesBitmap>
|
||||
where
|
||||
Block: Copy,
|
||||
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
|
||||
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
|
||||
{
|
||||
fn from(pending: &PendingDkgAuthorities<AuthoritiesBitmap, Block>) -> Self {
|
||||
Self {
|
||||
authorities_indexes: pending.get_indexes().clone(),
|
||||
dkg_index: pending.get_dkg_index(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct RotationSession {
|
||||
pub exodus_session: ExodusSession,
|
||||
pub network_type: NetworkType
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct PendingDkgAuthorities<AuthoritiesBitmap, Block> {
|
||||
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
authorities_indexes: AuthoritiesBitmap,
|
||||
rotation_sessions: RotationSessions,
|
||||
dkg_state: DkgState<Block>,
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap, Block> PendingDkgAuthorities<AuthoritiesBitmap, Block>
|
||||
where
|
||||
Block: Copy,
|
||||
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
|
||||
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
|
||||
{
|
||||
pub fn new_dkg(&mut self, authorities_len: usize) {
|
||||
self.authorities_indexes = AuthoritiesBitmap::empty(authorities_len);
|
||||
self.dkg_state.restart();
|
||||
self.rotation_sessions = Default::default();
|
||||
}
|
||||
|
||||
pub fn set_block(&mut self, block: Block) {
|
||||
self.dkg_state.set_block(block);
|
||||
}
|
||||
|
||||
pub fn set_verifying_key(
|
||||
&mut self,
|
||||
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
) {
|
||||
self.verifying_key = verifying_key;
|
||||
}
|
||||
|
||||
pub fn nullify_indexes(&mut self) {
|
||||
self.verifying_key = Default::default();
|
||||
self.authorities_indexes.nullify();
|
||||
}
|
||||
|
||||
pub fn restart_dkg(&mut self) {
|
||||
self.verifying_key = Default::default();
|
||||
self.authorities_indexes.nullify();
|
||||
self.dkg_state.restart();
|
||||
self.rotation_sessions = Default::default();
|
||||
}
|
||||
|
||||
pub fn next_phase(&mut self) {
|
||||
self.dkg_state.next_phase()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersect_indexes(&mut self, mask: &AuthoritiesBitmap) {
|
||||
self.authorities_indexes &= mask;
|
||||
}
|
||||
|
||||
pub fn is_dkg_pending(&self) -> bool {
|
||||
self.dkg_state.is_dkg_pending()
|
||||
}
|
||||
|
||||
pub fn is_dkg_vacant(&self) -> bool {
|
||||
self.dkg_state.is_dkg_vacant()
|
||||
}
|
||||
|
||||
pub fn get_dkg_index(&self) -> DkgIndex {
|
||||
self.dkg_state.get_dkg_index()
|
||||
}
|
||||
|
||||
pub fn get_block(&self) -> Block {
|
||||
self.dkg_state.get_block()
|
||||
}
|
||||
|
||||
pub fn get_phase(&self) -> DkgPhase {
|
||||
self.dkg_state.get_phase()
|
||||
}
|
||||
|
||||
pub fn get_verifying_key(
|
||||
&self
|
||||
) -> BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>> {
|
||||
self.verifying_key.clone()
|
||||
}
|
||||
|
||||
pub fn is_zero_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round0
|
||||
}
|
||||
|
||||
pub fn is_first_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round1
|
||||
}
|
||||
|
||||
pub fn is_second_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round2
|
||||
}
|
||||
|
||||
pub fn is_third_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round3
|
||||
}
|
||||
|
||||
pub fn is_fourth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round4
|
||||
}
|
||||
|
||||
pub fn is_fifth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round5
|
||||
}
|
||||
|
||||
pub fn is_sixth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round6
|
||||
}
|
||||
|
||||
pub fn is_dkg_finalized(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Finalized
|
||||
}
|
||||
|
||||
pub fn contains_index<N>(&self, index: N) -> bool
|
||||
where N: UniqueSaturatedInto<usize>,
|
||||
{
|
||||
self.authorities_indexes.contains(index)
|
||||
}
|
||||
|
||||
pub fn insert_index<N>(&mut self, i: N) -> Option<N>
|
||||
where N: Copy + TryInto<usize>
|
||||
{
|
||||
self.authorities_indexes.insert(i)
|
||||
}
|
||||
|
||||
pub fn try_push_rotation_session(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
network_type: NetworkType,
|
||||
) -> Option<()> {
|
||||
let rotation_session = RotationSession {
|
||||
exodus_session,
|
||||
network_type,
|
||||
};
|
||||
|
||||
self.rotation_sessions.try_push(rotation_session).ok()
|
||||
}
|
||||
|
||||
pub fn count_ones<N>(&self) -> N
|
||||
where u32: UniqueSaturatedInto<N>,
|
||||
{
|
||||
self.authorities_indexes.count_ones()
|
||||
}
|
||||
|
||||
pub fn highest_bit<N>(&self) -> Option<N>
|
||||
where usize: UniqueSaturatedInto<N>,
|
||||
{
|
||||
self.authorities_indexes.highest_bit()
|
||||
}
|
||||
|
||||
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
|
||||
&self.authorities_indexes
|
||||
}
|
||||
|
||||
pub fn iter_rotation_sessions(&self) -> impl Iterator<Item = &RotationSession> {
|
||||
self.rotation_sessions.iter()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct DkgState<Block> {
|
||||
block: Block,
|
||||
index: DkgIndex,
|
||||
phase: DkgPhase,
|
||||
}
|
||||
|
||||
impl<Block: Copy> DkgState<Block> {
|
||||
fn is_dkg_pending(&self) -> bool { self.phase.is_dkg_pending() }
|
||||
fn is_dkg_vacant(&self) -> bool { self.phase.is_dkg_vacant() }
|
||||
|
||||
fn get_dkg_index(&self) -> DkgIndex { self.index }
|
||||
fn get_phase(&self) -> DkgPhase { self.phase }
|
||||
fn get_block(&self) -> Block { self.block }
|
||||
|
||||
fn restart(&mut self) {
|
||||
self.phase = Default::default();
|
||||
self.index = self.index.checked_add(1)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
fn next_phase(&mut self) {
|
||||
self.phase.next_phase()
|
||||
}
|
||||
|
||||
fn set_block(&mut self, block: Block) {
|
||||
self.block = block;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub enum DkgPhase {
|
||||
#[default]
|
||||
Vacant,
|
||||
Round0,
|
||||
Round1,
|
||||
Round2,
|
||||
Round3,
|
||||
Round4,
|
||||
Round5,
|
||||
Round6,
|
||||
Round7,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
impl DkgPhase {
|
||||
fn is_dkg_pending(&self) -> bool {
|
||||
match &self {
|
||||
Self::Finalized | Self::Round7 => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dkg_vacant(&self) -> bool {
|
||||
match &self {
|
||||
Self::Vacant => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_phase(&mut self) {
|
||||
*self = match *self {
|
||||
Self::Vacant => Self::Round0,
|
||||
Self::Round0 => Self::Round1,
|
||||
Self::Round1 => Self::Round2,
|
||||
Self::Round2 => Self::Round3,
|
||||
Self::Round3 => Self::Round4,
|
||||
Self::Round4 => Self::Round5,
|
||||
Self::Round5 => Self::Round6,
|
||||
Self::Round6 => Self::Round7,
|
||||
Self::Round7 => Self::Finalized,
|
||||
Self::Finalized => Self::Finalized,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use frame_support::BoundedVec;
|
||||
use crate::ExodusError;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, Ord, PartialOrd, RuntimeDebug, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct EncryptionData<T: crate::pallet::Config> {
|
||||
pub ciphertext: BoundedVec<u8, crate::CiphertextMaxBytes<T>>,
|
||||
pub nonce: [u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize],
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> EncryptionData<T> {
|
||||
pub fn try_new(
|
||||
ciphertext_vec: Vec<u8>,
|
||||
nonce_bytes: &[u8],
|
||||
) -> Result<Self, ExodusError> {
|
||||
if nonce_bytes.len() != crate::ENCRYPTION_NONCE_MAX_BYTES as usize {
|
||||
return Err(ExodusError::EncryptionFailed);
|
||||
}
|
||||
|
||||
let ciphertext =
|
||||
BoundedVec::<u8, crate::CiphertextMaxBytes<T>>::try_from(ciphertext_vec)
|
||||
.map_err(|_| ExodusError::EncryptionFailed)?;
|
||||
|
||||
let mut nonce = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
|
||||
nonce.copy_from_slice(nonce_bytes);
|
||||
|
||||
Ok(Self { ciphertext, nonce })
|
||||
}
|
||||
}
|
||||
@ -1,236 +0,0 @@
|
||||
use scale_info::TypeInfo;
|
||||
use codec::{Encode, Decode};
|
||||
use sp_runtime::{
|
||||
Perbill, RuntimeDebug, traits::UniqueSaturatedInto,
|
||||
helpers_128bit::multiply_by_rational_with_rounding,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
EvmAddress, EvmBytes32, ExodusSession, RoastSession,
|
||||
MAX_MESSAGE_SIZE,
|
||||
};
|
||||
|
||||
pub trait EvmAbiEncode {
|
||||
fn size(&self) -> usize;
|
||||
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()>;
|
||||
}
|
||||
|
||||
macro_rules! impl_evm_encodable {
|
||||
(numeric: $( $ty:ty ),* ) => {
|
||||
$(
|
||||
impl EvmAbiEncode for $ty {
|
||||
#[inline(always)]
|
||||
fn size(&self) -> usize {
|
||||
core::mem::size_of::<Self>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
|
||||
let val_len = self.size();
|
||||
let padding = 32 - val_len;
|
||||
|
||||
let padding_slice = buffer.get_mut(*cursor..*cursor + padding)?;
|
||||
padding_slice.fill(0);
|
||||
*cursor += padding;
|
||||
|
||||
let value_slice = buffer.get_mut(*cursor..*cursor + val_len)?;
|
||||
value_slice.copy_from_slice(&self.to_be_bytes());
|
||||
*cursor += val_len;
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
(bytes_fixed: $( $ty:ty ),* ) => {
|
||||
$(
|
||||
impl EvmAbiEncode for $ty {
|
||||
#[inline(always)]
|
||||
fn size(&self) -> usize {
|
||||
<Self as AsRef<[u8]>>::as_ref(self).len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
|
||||
if self.size() > 32 { return None; }
|
||||
|
||||
let val_len = self.size();
|
||||
let padding = 32 - val_len;
|
||||
|
||||
let padding_slice = buffer.get_mut(*cursor..*cursor + padding)?;
|
||||
padding_slice.fill(0);
|
||||
*cursor += padding;
|
||||
|
||||
let value_slice = buffer.get_mut(*cursor..*cursor + val_len)?;
|
||||
value_slice.copy_from_slice(self.as_ref());
|
||||
*cursor += val_len;
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_evm_encodable!(numeric: u8, u16, u32, u64, u128);
|
||||
impl_evm_encodable!(bytes_fixed: EvmAddress, EvmBytes32);
|
||||
|
||||
macro_rules! abi_encode_evm_message {
|
||||
($buffer:expr, $sig:expr, [ $( $val:expr ),* $(,)? ]) => {{
|
||||
let mut cursor = 0;
|
||||
let mut no_overflow = true;
|
||||
|
||||
let hash = sp_io::hashing::keccak_256($sig.as_bytes());
|
||||
|
||||
if let Some(selector_slice) = $buffer.get_mut(0..4) {
|
||||
selector_slice.copy_from_slice(&hash[0..4]);
|
||||
cursor += 4;
|
||||
|
||||
$(
|
||||
if no_overflow {
|
||||
if EvmAbiEncode::abi_encode($val, $buffer, &mut cursor).is_none() {
|
||||
no_overflow = false;
|
||||
}
|
||||
}
|
||||
)*
|
||||
} else {
|
||||
no_overflow = false;
|
||||
}
|
||||
|
||||
no_overflow.then(|| &$buffer[0..cursor])
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct ExodusRequest<NetworkId, Balance> {
|
||||
pub r#type: ExodusRequestType<NetworkId, Balance>,
|
||||
pub next_roast_session: RoastSession,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub enum ExodusRequestType<NetworkId, Balance> {
|
||||
EvmRotation {
|
||||
exodus_session: ExodusSession,
|
||||
public_key: EvmBytes32,
|
||||
parity: u8,
|
||||
},
|
||||
UtxoRotation,
|
||||
EvmBridgeOut {
|
||||
exodus_session: ExodusSession,
|
||||
network_id: NetworkId,
|
||||
amount: Balance,
|
||||
commission: Perbill,
|
||||
receiver: EvmAddress,
|
||||
},
|
||||
EvmGovernance { network_id: NetworkId },
|
||||
UtxoBridgeOut { network_id: NetworkId },
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> ExodusRequestType<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: UniqueSaturatedInto<u64> + Copy,
|
||||
Balance: UniqueSaturatedInto<u128> + Copy,
|
||||
{
|
||||
pub fn get_message<'a>(
|
||||
&self,
|
||||
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
|
||||
) -> Option<&'a [u8]> {
|
||||
match &self {
|
||||
ExodusRequestType::EvmRotation { exodus_session, public_key, parity } => {
|
||||
abi_encode_evm_message!(
|
||||
message_buffer,
|
||||
"rotate(uint256,bytes32,uint8)",
|
||||
[exodus_session, public_key, parity]
|
||||
)
|
||||
},
|
||||
ExodusRequestType::EvmBridgeOut { exodus_session, network_id, amount, commission, receiver } => {
|
||||
let mut buffer = [0u8; 32];
|
||||
buffer[0..20].copy_from_slice(receiver.as_ref());
|
||||
|
||||
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
|
||||
buffer[20..28].copy_from_slice(&chain_id_u64.to_be_bytes());
|
||||
|
||||
let scaled_commission = multiply_by_rational_with_rounding(
|
||||
commission.deconstruct() as u128,
|
||||
1u128 << 32,
|
||||
1_000_000_000,
|
||||
sp_runtime::Rounding::Down
|
||||
)?;
|
||||
|
||||
let commission_uint32 = scaled_commission as u32;
|
||||
buffer[28..32].copy_from_slice(&commission_uint32.to_be_bytes());
|
||||
|
||||
let mut amount_buffer = [0u8; 32];
|
||||
let amount_u128: u128 = (*amount).unique_saturated_into();
|
||||
amount_buffer[16..32].copy_from_slice(&amount_u128.to_be_bytes());
|
||||
|
||||
let packed_metadata = EvmBytes32::from(buffer);
|
||||
let amount_u256 = EvmBytes32::from(amount_buffer);
|
||||
|
||||
abi_encode_evm_message!(
|
||||
message_buffer,
|
||||
"materialize(uint256,uint256,uint256)",
|
||||
[exodus_session, &amount_u256, &packed_metadata]
|
||||
)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> ExodusRequest<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: UniqueSaturatedInto<u64> + Copy,
|
||||
Balance: UniqueSaturatedInto<u128> + Copy,
|
||||
{
|
||||
pub fn get_message<'a>(
|
||||
&self,
|
||||
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
|
||||
) -> Option<&'a [u8]> {
|
||||
self.r#type.get_message(message_buffer)
|
||||
}
|
||||
|
||||
pub fn evm_rotation(
|
||||
exodus_session: ExodusSession,
|
||||
public_key: EvmBytes32,
|
||||
parity: u8,
|
||||
) -> Self {
|
||||
let rotation = ExodusRequestType::EvmRotation {
|
||||
exodus_session,
|
||||
public_key,
|
||||
parity,
|
||||
};
|
||||
Self::default_request_for(rotation)
|
||||
}
|
||||
|
||||
pub fn evm_bridge_out(
|
||||
exodus_session: ExodusSession,
|
||||
network_id: NetworkId,
|
||||
amount: Balance,
|
||||
commission: Perbill,
|
||||
receiver: EvmAddress,
|
||||
) -> Self {
|
||||
let evm_bridge_out = ExodusRequestType::EvmBridgeOut {
|
||||
exodus_session,
|
||||
network_id,
|
||||
amount,
|
||||
commission,
|
||||
receiver,
|
||||
};
|
||||
Self::default_request_for(evm_bridge_out)
|
||||
}
|
||||
|
||||
pub fn is_rotation(&self) -> bool {
|
||||
match &self.r#type {
|
||||
ExodusRequestType::EvmRotation { .. } | ExodusRequestType::UtxoRotation { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn default_request_for(
|
||||
request_type: ExodusRequestType<NetworkId, Balance>,
|
||||
) -> Self {
|
||||
Self { r#type: request_type, next_roast_session: Default::default() }
|
||||
}
|
||||
}
|
||||
@ -1,611 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use sp_std::{vec::Vec, collections::btree_map::BTreeMap};
|
||||
use sp_runtime::{
|
||||
RuntimeDebug, RuntimeAppPublic,
|
||||
traits::{AtLeast32BitUnsigned, Saturating},
|
||||
offchain::storage::StorageValueRef,
|
||||
};
|
||||
|
||||
use super::{DkgPackage, ExodusPackage, ExodusOk, PackageContext};
|
||||
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
|
||||
|
||||
fn create_storage_key(prefix: &[u8], index: &[u8], network: &[u8]) -> Vec<u8> {
|
||||
let mut key = crate::DB_STORAGE_PREFIX.to_vec();
|
||||
key.extend(prefix);
|
||||
key.extend(index);
|
||||
key.extend(network);
|
||||
key
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct PersistentData<BlockNumber, DataType> {
|
||||
pub release_block: BlockNumber,
|
||||
pub data: DataType,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub enum DkgStorageEnum {
|
||||
Round0 { package_hash: ExodusHash },
|
||||
Round1 {
|
||||
secret_package: Vec<u8>,
|
||||
public_package: Vec<u8>,
|
||||
},
|
||||
Round2 {
|
||||
secret_package: Vec<u8>,
|
||||
public_packages: BTreeMap<AuthIndex, Vec<u8>>,
|
||||
},
|
||||
Round3 { decrypted: BTreeMap<AuthIndex, Vec<u8>> },
|
||||
Round4,
|
||||
Round5 {
|
||||
secret_share: Vec<u8>,
|
||||
merkle_proof: Vec<ExodusHash>,
|
||||
},
|
||||
Round6,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusNonce(Vec<u8>);
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusGroup {
|
||||
pub self_binding_factor: Vec<u8>,
|
||||
pub binding_factors_proof: Vec<ExodusHash>,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusShare;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusExile;
|
||||
|
||||
type ExodusNonces<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusNonce>>;
|
||||
type ExodusGroups<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusGroup>>;
|
||||
type ExodusShares<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusShare>>;
|
||||
type ExodusExiles<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusExile>>;
|
||||
|
||||
pub struct ExodusStorageWrapper<ExodusInnerType> {
|
||||
pub inner: ExodusInnerType,
|
||||
pub data_changed: bool,
|
||||
}
|
||||
|
||||
impl<ExodusInnerType> ExodusStorageWrapper<ExodusInnerType> {
|
||||
pub fn new(inner: ExodusInnerType) -> Self {
|
||||
Self { inner, data_changed: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExodusStorage<BlockNumber, NetworkCurve>
|
||||
where
|
||||
BlockNumber: Default,
|
||||
NetworkCurve: Default,
|
||||
{
|
||||
current_block: BlockNumber,
|
||||
block_longevity: BlockNumber,
|
||||
pub network_curve: NetworkCurve,
|
||||
pub authority_index: AuthIndex,
|
||||
}
|
||||
|
||||
impl<B, NC> ExodusStorage<B, NC>
|
||||
where
|
||||
B: AtLeast32BitUnsigned + Copy,
|
||||
B: Encode + Decode + Default,
|
||||
NC: Encode + Decode + Default + Copy,
|
||||
{
|
||||
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_block(mut self, current_block: B) -> Self {
|
||||
self.current_block = current_block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
|
||||
self.block_longevity = block_longevity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn read_local_storage(&self) -> ExodusRecords<B> {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
let exodus_nonces = StorageValueRef::persistent(&nonces_key)
|
||||
.get::<ExodusNonces<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
let exodus_shares = StorageValueRef::persistent(&shares_key)
|
||||
.get::<ExodusShares<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
let exodus_groups = StorageValueRef::persistent(&groups_key)
|
||||
.get::<ExodusGroups<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
let exodus_exiles = StorageValueRef::persistent(&exiles_key)
|
||||
.get::<ExodusExiles<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
ExodusRecords {
|
||||
exodus_nonces: ExodusStorageWrapper::new(exodus_nonces),
|
||||
exodus_shares: ExodusStorageWrapper::new(exodus_shares),
|
||||
exodus_groups: ExodusStorageWrapper::new(exodus_groups),
|
||||
exodus_exiles: ExodusStorageWrapper::new(exodus_exiles),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_signed_call_with_context<T, A, F1, F2, F3>(
|
||||
&self,
|
||||
authority_key: &A,
|
||||
exodus_records: &mut ExodusRecords<B>,
|
||||
make_data: F1,
|
||||
submit_call: F2,
|
||||
insert_data: F3,
|
||||
) -> Result<(), ExodusError>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
A: RuntimeAppPublic,
|
||||
F1: FnOnce(PackageContext<NC>) -> Result<ExodusPackage<NC, T>, ExodusError>,
|
||||
F2: FnOnce(ExodusPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
|
||||
F3: FnOnce(&mut ExodusRecords<B>, B),
|
||||
{
|
||||
let context = PackageContext::default()
|
||||
.with_authority_index(self.authority_index)
|
||||
.with_network_curve(self.network_curve);
|
||||
|
||||
let package = make_data(context)?;
|
||||
|
||||
let signature = authority_key
|
||||
.sign(&package.encode())
|
||||
.ok_or(ExodusError::BadSignature(self.authority_index))?;
|
||||
|
||||
submit_call(package, signature)?;
|
||||
|
||||
let next_release_block = self.current_block.saturating_add(self.block_longevity);
|
||||
insert_data(exodus_records, next_release_block);
|
||||
|
||||
self.try_write_local_storage(exodus_records);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn force_clear_local_storage(&self) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
|
||||
StorageValueRef::persistent(&nonces_key).clear();
|
||||
StorageValueRef::persistent(&shares_key).clear();
|
||||
StorageValueRef::persistent(&groups_key).clear();
|
||||
StorageValueRef::persistent(&exiles_key).clear();
|
||||
}
|
||||
|
||||
pub fn try_write_local_storage(&self, records: &ExodusRecords<B>) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
if records.exodus_nonces.data_changed {
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&nonces_key).set(&records.exodus_nonces.inner);
|
||||
}
|
||||
|
||||
if records.exodus_shares.data_changed {
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&shares_key).set(&records.exodus_shares.inner);
|
||||
}
|
||||
|
||||
if records.exodus_groups.data_changed {
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&groups_key).set(&records.exodus_groups.inner);
|
||||
}
|
||||
|
||||
if records.exodus_exiles.data_changed {
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&exiles_key).set(&records.exodus_exiles.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExodusRecords<BlockNumber>
|
||||
where
|
||||
BlockNumber: AtLeast32BitUnsigned,
|
||||
{
|
||||
exodus_nonces: ExodusStorageWrapper<ExodusNonces<BlockNumber>>,
|
||||
exodus_shares: ExodusStorageWrapper<ExodusShares<BlockNumber>>,
|
||||
exodus_groups: ExodusStorageWrapper<ExodusGroups<BlockNumber>>,
|
||||
exodus_exiles: ExodusStorageWrapper<ExodusExiles<BlockNumber>>,
|
||||
}
|
||||
|
||||
impl<BlockNumber> ExodusRecords<BlockNumber>
|
||||
where
|
||||
BlockNumber: AtLeast32BitUnsigned + Default,
|
||||
{
|
||||
pub fn get_secret_nonce(&self, exodus_session: &ExodusSession) -> Option<&[u8]> {
|
||||
self.exodus_nonces.inner
|
||||
.get(exodus_session)
|
||||
.map(|v| v.data.0.as_slice())
|
||||
}
|
||||
|
||||
pub fn get_group_record(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> Option<ExodusGroup> {
|
||||
self.exodus_groups.inner
|
||||
.get(exodus_session)
|
||||
.cloned()
|
||||
.map(|storage| storage.data)
|
||||
}
|
||||
|
||||
pub fn is_exodus_session_banned(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> bool {
|
||||
self.exodus_exiles.inner.contains_key(exodus_session)
|
||||
}
|
||||
|
||||
pub fn is_group_registered(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> bool {
|
||||
self.exodus_groups.inner.contains_key(exodus_session)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_nonce(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber
|
||||
) -> bool {
|
||||
self.exodus_nonces.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(current_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_share(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber,
|
||||
) -> bool {
|
||||
self.exodus_shares.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(¤t_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_group(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber,
|
||||
) -> bool {
|
||||
self.exodus_groups.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(¤t_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn exclude_exodus_session(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
) {
|
||||
let exodus_exile = PersistentData {
|
||||
data: ExodusExile,
|
||||
release_block: Default::default(),
|
||||
};
|
||||
|
||||
self.exodus_exiles.inner.insert(exodus_session, exodus_exile);
|
||||
self.exodus_exiles.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_nonce(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
nonce_commitment: Vec<u8>,
|
||||
) {
|
||||
let exodus_nonce = PersistentData {
|
||||
data: ExodusNonce(nonce_commitment),
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_nonces.inner.insert(exodus_session, exodus_nonce);
|
||||
self.exodus_nonces.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_share(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
) {
|
||||
let exodus_share = PersistentData {
|
||||
data: ExodusShare,
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_shares.inner.insert(exodus_session, exodus_share);
|
||||
self.exodus_shares.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_group(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
self_binding_factor: Vec<u8>,
|
||||
binding_factors_proof: Vec<ExodusHash>,
|
||||
) {
|
||||
let exodus_group = PersistentData {
|
||||
data: ExodusGroup { self_binding_factor, binding_factors_proof },
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_groups.inner.insert(exodus_session, exodus_group);
|
||||
self.exodus_groups.data_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct DkgStorage<BlockNumber, NetworkCurve>
|
||||
where
|
||||
BlockNumber: Copy + Clone,
|
||||
NetworkCurve: Copy + Clone,
|
||||
{
|
||||
block_longevity: BlockNumber,
|
||||
current_block: BlockNumber,
|
||||
pub network_curve: NetworkCurve,
|
||||
pub authority_index: AuthIndex,
|
||||
}
|
||||
|
||||
impl<B, NC> DkgStorage<B, NC>
|
||||
where
|
||||
B: Saturating + Encode + Decode + Copy + Default + PartialOrd,
|
||||
NC: Encode + Copy + Default,
|
||||
{
|
||||
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_block(mut self, current_block: B) -> Self {
|
||||
self.current_block = current_block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
|
||||
self.block_longevity = block_longevity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_release_block(&self) -> B {
|
||||
self.current_block.saturating_add(self.block_longevity)
|
||||
}
|
||||
|
||||
pub fn get_dkg_result(
|
||||
&self,
|
||||
dkg_record: DkgRecord<B>,
|
||||
) -> Result<ExodusOk<NC>, ExodusError> {
|
||||
match dkg_record.prefix_slice {
|
||||
crate::DKG_ROUND0_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(0u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND1_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(1u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND2_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(2u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND3_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(3u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND4_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(4u8, self.authority_index, self.network_curve)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !dkg_record.prefix_slice.starts_with(crate::DKG_SECRET_PREFIX) {
|
||||
return Err(ExodusError::UnknownPrefix);
|
||||
}
|
||||
|
||||
match &dkg_record.maybe_stored_data {
|
||||
Some(PersistentData { data: DkgStorageEnum::Round5 { .. }, .. }) => {
|
||||
Ok(ExodusOk::DkgRoundRegistered(5u8, self.authority_index, self.network_curve))
|
||||
},
|
||||
Some(PersistentData { data: DkgStorageEnum::Round6 { .. }, .. }) => {
|
||||
Ok(ExodusOk::DkgRoundRegistered(6u8, self.authority_index, self.network_curve))
|
||||
},
|
||||
_ => Err(ExodusError::UnknownPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_local_storage<'a>(
|
||||
&self,
|
||||
prefix_slice: &'a [u8],
|
||||
authority_index: AuthIndex,
|
||||
) -> DkgRecord<'a, B> {
|
||||
let encoded_index = authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(prefix_slice, &encoded_index, &encoded_network);
|
||||
|
||||
let maybe_stored_data = StorageValueRef::persistent(&key)
|
||||
.get::<PersistentData<B, DkgStorageEnum>>()
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
DkgRecord { prefix_slice, maybe_stored_data, current_block: self.current_block }
|
||||
}
|
||||
|
||||
pub fn prepare_signed_call_with_context<T, A, F1, F2>(
|
||||
&self,
|
||||
authority_key: &A,
|
||||
dkg_record: &mut DkgRecord<B>,
|
||||
make_data: F1,
|
||||
submit_call: F2,
|
||||
) -> Result<(), ExodusError>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
A: RuntimeAppPublic,
|
||||
F1: FnOnce(PackageContext<NC>) -> Result<DkgPackage<NC, T>, ExodusError>,
|
||||
F2: FnOnce(DkgPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
|
||||
{
|
||||
let context = PackageContext::default()
|
||||
.with_authority_index(self.authority_index)
|
||||
.with_network_curve(self.network_curve);
|
||||
|
||||
let package = make_data(context)?;
|
||||
|
||||
let signature = authority_key
|
||||
.sign(&package.encode())
|
||||
.ok_or(ExodusError::BadSignature(self.authority_index))?;
|
||||
|
||||
submit_call(package, signature)?;
|
||||
|
||||
self.try_write_local_storage(dkg_record);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_write_local_storage(
|
||||
&self,
|
||||
dkg_record: &DkgRecord<B>,
|
||||
) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(
|
||||
dkg_record.prefix_slice,
|
||||
&encoded_index,
|
||||
&encoded_network,
|
||||
);
|
||||
|
||||
match &dkg_record.maybe_stored_data {
|
||||
Some(to_store) => StorageValueRef::persistent(&key).set(&to_store),
|
||||
None => StorageValueRef::persistent(&key).clear(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clear_local_storage<'a>(
|
||||
&self,
|
||||
prefix: &'a [u8],
|
||||
) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(prefix, &encoded_index, &encoded_network);
|
||||
|
||||
StorageValueRef::persistent(&key).clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DkgRecord<'a, BlockNumber> {
|
||||
maybe_stored_data: Option<PersistentData<BlockNumber, DkgStorageEnum>>,
|
||||
current_block: BlockNumber,
|
||||
prefix_slice: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a, BlockNumber> DkgRecord<'a, BlockNumber>
|
||||
where
|
||||
BlockNumber: Default + Copy + PartialOrd,
|
||||
{
|
||||
pub fn is_waiting_period(&self) -> bool {
|
||||
self.maybe_stored_data
|
||||
.as_ref()
|
||||
.map(|data| data.release_block.gt(&self.current_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn storage_exists(&self) -> bool {
|
||||
self.maybe_stored_data.is_some()
|
||||
}
|
||||
|
||||
pub fn into_inner(&self) -> Option<&DkgStorageEnum> {
|
||||
self.maybe_stored_data.as_ref().map(|stored| &stored.data)
|
||||
}
|
||||
|
||||
pub fn into_inner_mut(&mut self) -> Option<&mut DkgStorageEnum> {
|
||||
self.maybe_stored_data.as_mut().map(|stored| &mut stored.data)
|
||||
}
|
||||
|
||||
pub fn try_update_max_signers<F>(
|
||||
&mut self,
|
||||
get_new_secret: F
|
||||
) -> Result<&Vec<u8>, ExodusError>
|
||||
where
|
||||
F: FnOnce(&[u8]) -> Result<Vec<u8>, ExodusError>,
|
||||
{
|
||||
enum RoundType<'a> {
|
||||
R1 { public_package: &'a Vec<u8> },
|
||||
R2 { public_packages: &'a BTreeMap<AuthIndex, Vec<u8>> },
|
||||
}
|
||||
|
||||
let (old_secret, round_type) = self.maybe_stored_data.as_ref()
|
||||
.map(|persistent| match &persistent.data {
|
||||
DkgStorageEnum::Round1 { secret_package, public_package } => {
|
||||
Ok((secret_package, RoundType::R1 { public_package }))
|
||||
},
|
||||
DkgStorageEnum::Round2 { secret_package, public_packages } => {
|
||||
Ok((secret_package, RoundType::R2 { public_packages }))
|
||||
},
|
||||
_ => Err(ExodusError::NothingToWrite),
|
||||
})
|
||||
.ok_or(ExodusError::NothingToWrite)??;
|
||||
|
||||
let new_secret = get_new_secret(old_secret)?;
|
||||
let new_data = match round_type {
|
||||
RoundType::R1 { public_package } => DkgStorageEnum::Round1 {
|
||||
secret_package: new_secret,
|
||||
public_package: public_package.clone(),
|
||||
},
|
||||
RoundType::R2 { public_packages } => DkgStorageEnum::Round2 {
|
||||
secret_package: new_secret,
|
||||
public_packages: public_packages.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
self.update_with_block(None, new_data)?;
|
||||
|
||||
self.maybe_stored_data.as_ref()
|
||||
.map(|persistent| match &persistent.data {
|
||||
DkgStorageEnum::Round1 { secret_package, .. } |
|
||||
DkgStorageEnum::Round2 { secret_package, .. } => Ok(secret_package),
|
||||
_ => Err(ExodusError::NothingToWrite),
|
||||
})
|
||||
.ok_or(ExodusError::NothingToWrite)?
|
||||
}
|
||||
|
||||
pub fn update_with_block(
|
||||
&mut self,
|
||||
maybe_release_block: Option<BlockNumber>,
|
||||
new_data: DkgStorageEnum,
|
||||
) -> Result<(), ExodusError> {
|
||||
use core::mem::discriminant as d;
|
||||
|
||||
let release_block = match &self.maybe_stored_data {
|
||||
Some(existing) => {
|
||||
if d(&existing.data) != d(&new_data) {
|
||||
return Err(ExodusError::NothingToWrite);
|
||||
}
|
||||
maybe_release_block.unwrap_or(existing.release_block)
|
||||
}
|
||||
None => maybe_release_block.unwrap_or_default(),
|
||||
};
|
||||
|
||||
self.maybe_stored_data = Some(PersistentData { release_block, data: new_data });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
mod exodus_request;
|
||||
mod local_storage;
|
||||
mod encryption;
|
||||
mod consensus;
|
||||
mod signature;
|
||||
mod packages;
|
||||
mod success;
|
||||
mod removal;
|
||||
mod nonce;
|
||||
mod roast;
|
||||
mod dkg;
|
||||
|
||||
pub use exodus_request::*;
|
||||
pub use local_storage::*;
|
||||
pub use encryption::*;
|
||||
pub use consensus::*;
|
||||
pub use signature::*;
|
||||
pub use packages::*;
|
||||
pub use success::*;
|
||||
pub use removal::*;
|
||||
pub use nonce::*;
|
||||
pub use roast::*;
|
||||
pub use dkg::*;
|
||||
@ -1,20 +0,0 @@
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use crate::ELEMENT_MAX_BYTES;
|
||||
|
||||
type NonceType = BoundedVec<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct ExodusSeparatedNonce {
|
||||
pub hiding: NonceType,
|
||||
pub binding: NonceType,
|
||||
}
|
||||
|
||||
impl ExodusSeparatedNonce {
|
||||
pub fn new(hiding: NonceType, binding: NonceType) -> Self {
|
||||
Self { hiding, binding }
|
||||
}
|
||||
}
|
||||
@ -1,466 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
|
||||
|
||||
pub trait PackageMetadata<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: Copy,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NetworkCurve>;
|
||||
|
||||
fn get_authority_index(&self) -> AuthIndex {
|
||||
self.context().get_authority_index()
|
||||
}
|
||||
fn get_network_curve(&self) -> NetworkCurve {
|
||||
self.context().get_network_curve()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub enum ExodusPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
NonceCommitment(ExodusNonceCommitment<NetworkCurve>),
|
||||
GroupCommitment(ExodusGroupCommitment<NetworkCurve>),
|
||||
SignatureShare(ExodusSignatureShare<NetworkCurve, T>),
|
||||
}
|
||||
|
||||
impl<NetworkCurve, T> ExodusPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub fn bytes_len(&self) -> u32 {
|
||||
let length = match self {
|
||||
Self::GroupCommitment(package) => package.group_commitment.len(),
|
||||
Self::SignatureShare(package) => {
|
||||
(package.binding_factors_proof.len() * ExodusHash::len_bytes())
|
||||
+ package.self_binding_factor.len()
|
||||
+ package.signature_share.len()
|
||||
},
|
||||
Self::NonceCommitment(package) => {
|
||||
package.hiding_commitment.len()
|
||||
+ package.binding_commitment.len()
|
||||
},
|
||||
};
|
||||
|
||||
length as u32
|
||||
}
|
||||
|
||||
pub fn get_exodus_session(&self) -> ExodusSession {
|
||||
match &self {
|
||||
Self::SignatureShare(package) => package.session,
|
||||
Self::NonceCommitment(package) => package.session,
|
||||
Self::GroupCommitment(package) => package.session,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub enum DkgPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
Round0(Round0Package<NetworkCurve>),
|
||||
Round1(Round1Package<NetworkCurve, T>),
|
||||
Round2(Round2Package<NetworkCurve, T>),
|
||||
Round3(Round3Package<NetworkCurve, T>),
|
||||
Round4(Round4Package<NetworkCurve, T>),
|
||||
Round5(Round5Package<NetworkCurve>),
|
||||
Round6(Round6Package<NetworkCurve, T>),
|
||||
}
|
||||
|
||||
impl<NetworkCurve, T> DkgPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub fn bytes_len(&self) -> u32 {
|
||||
let length = match self {
|
||||
Self::Round0(_) => ExodusHash::len_bytes(),
|
||||
Self::Round1(round1) => round1.package.len(),
|
||||
Self::Round2(round2) => round2.bundle.bitmask.len(),
|
||||
Self::Round3(round3) => round3.indices.len(),
|
||||
Self::Round4(round4) => round4.bundle.bitmask.len(),
|
||||
Self::Round5(round5) => {
|
||||
round5.verifying_key.len()
|
||||
.saturating_add(ExodusHash::len_bytes())
|
||||
},
|
||||
Self::Round6(round6) => round6.merkle_proof.len(),
|
||||
};
|
||||
|
||||
length as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct PackageContext<NetworkCurve> {
|
||||
authority_index: AuthIndex,
|
||||
network_curve: NetworkCurve
|
||||
}
|
||||
|
||||
impl<NetworkCurve> PackageContext<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: Copy,
|
||||
{
|
||||
pub fn get_authority_index(&self) -> AuthIndex {
|
||||
self.authority_index
|
||||
}
|
||||
|
||||
pub fn get_network_curve(&self) -> NetworkCurve {
|
||||
self.network_curve
|
||||
}
|
||||
|
||||
pub fn with_network_curve(mut self, network_curve: NetworkCurve) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct BundledPackages {
|
||||
pub blob: Vec<u8>,
|
||||
pub bitmask: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct EncryptionBundledPackages<T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>,
|
||||
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> EncryptionBundledPackages<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
let blob_len = self.blob.len();
|
||||
let bitmask_len = self.bitmask.len();
|
||||
|
||||
bitmask_len.saturating_add(blob_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for EncryptionBundledPackages<T> {
|
||||
type Error = ExodusError;
|
||||
|
||||
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
|
||||
let blob =
|
||||
BoundedVec::<u8, crate::EncryptedRound2BlobMaxBytes<T>>::try_from(source.blob)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Self { blob, bitmask })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct JustificationsBundledPackages<T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>,
|
||||
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> JustificationsBundledPackages<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
let blob_len = self.blob.len();
|
||||
let bitmask_len = self.bitmask.len();
|
||||
|
||||
bitmask_len.saturating_add(blob_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for JustificationsBundledPackages<T> {
|
||||
type Error = ExodusError;
|
||||
|
||||
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
|
||||
let blob =
|
||||
BoundedVec::<u8, crate::Round2BlobMaxBytes<T>>::try_from(source.blob)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Self { blob, bitmask })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ExodusNonceCommitment<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub hiding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub binding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct ExodusGroupCommitment<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub group_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub binding_factors_root: ExodusHash,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ExodusSignatureShare<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub binding_factors_proof: BoundedVec<ExodusHash, crate::BindingFactorsProof<T>>,
|
||||
pub signature_share: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
|
||||
pub self_binding_factor: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct Round0Package<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub package_hash: ExodusHash,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round1Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub package: BoundedVec<u8, crate::Round1MaxBytes<T>>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round2Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub bundle: EncryptionBundledPackages<T>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round3Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub indices: BoundedVec<u8, crate::AccusedIndicesMaxBytes<T>>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round4Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub bundle: JustificationsBundledPackages<T>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct Round5Package<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub merkle_root: ExodusHash,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round6Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub verifying_share: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub merkle_proof: BoundedVec<ExodusHash, crate::MerkleProofMaxSize<T>>,
|
||||
}
|
||||
|
||||
impl<NetworkCurve> PackageContext<NetworkCurve> {
|
||||
pub fn with_exodus_nonce_commitment(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
hiding_commitment_vec: Vec<u8>,
|
||||
binding_commitment_vec: Vec<u8>,
|
||||
) -> Result<ExodusNonceCommitment<NetworkCurve>, ExodusError> {
|
||||
let hiding_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(hiding_commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(binding_commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusNonceCommitment { context: self, hiding_commitment, binding_commitment, session })
|
||||
}
|
||||
|
||||
pub fn with_exodus_group_commitment(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
commitment_vec: Vec<u8>,
|
||||
binding_factors_root: ExodusHash,
|
||||
) -> Result<ExodusGroupCommitment<NetworkCurve>, ExodusError> {
|
||||
let group_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusGroupCommitment {
|
||||
context: self,
|
||||
group_commitment,
|
||||
binding_factors_root,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_exodus_signature_share<T: crate::pallet::Config>(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
signature_share_vec: Vec<u8>,
|
||||
binding_factors_proof_vec: Vec<ExodusHash>,
|
||||
self_binding_factor_vec: Vec<u8>,
|
||||
) -> Result<ExodusSignatureShare<NetworkCurve, T>, ExodusError> {
|
||||
let signature_share =
|
||||
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(signature_share_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_factors_proof =
|
||||
BoundedVec::<ExodusHash, crate::BindingFactorsProof<T>>::try_from(binding_factors_proof_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let self_binding_factor =
|
||||
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(self_binding_factor_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusSignatureShare {
|
||||
context: self,
|
||||
binding_factors_proof,
|
||||
self_binding_factor,
|
||||
signature_share,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_dkg_round0(
|
||||
self,
|
||||
package_hash: ExodusHash,
|
||||
) -> Round0Package<NetworkCurve> {
|
||||
Round0Package { context: self, package_hash }
|
||||
}
|
||||
|
||||
pub fn with_dkg_round1<T: crate::pallet::Config>(
|
||||
self,
|
||||
package_vec: Vec<u8>,
|
||||
) -> Result<Round1Package<NetworkCurve, T>, ExodusError> {
|
||||
let package =
|
||||
BoundedVec::<u8, crate::Round1MaxBytes<T>>::try_from(package_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round1Package { context: self, package })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round2<T: crate::pallet::Config>(
|
||||
self,
|
||||
bundle_raw: BundledPackages,
|
||||
) -> Result<Round2Package<NetworkCurve, T>, ExodusError> {
|
||||
let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?;
|
||||
Ok(Round2Package { context: self, bundle })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round3<T: crate::pallet::Config>(
|
||||
self,
|
||||
indices_vec: Vec<u8>
|
||||
) -> Result<Round3Package<NetworkCurve, T>, ExodusError> {
|
||||
let indices =
|
||||
BoundedVec::<u8, crate::AccusedIndicesMaxBytes<T>>::try_from(indices_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round3Package { context: self, indices })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round4<T: crate::pallet::Config>(
|
||||
self,
|
||||
bundle_raw: BundledPackages,
|
||||
) -> Result<Round4Package<NetworkCurve, T>, ExodusError> {
|
||||
let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?;
|
||||
Ok(Round4Package { context: self, bundle })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round5(
|
||||
self,
|
||||
verifying_key_vec: Vec<u8>,
|
||||
merkle_root: ExodusHash,
|
||||
) -> Result<Round5Package<NetworkCurve>, ExodusError> {
|
||||
let verifying_key =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_key_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round5Package { context: self, verifying_key, merkle_root })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round6<T: crate::pallet::Config>(
|
||||
self,
|
||||
verifying_share_vec: Vec<u8>,
|
||||
merkle_proof_vec: Vec<ExodusHash>,
|
||||
) -> Result<Round6Package<NetworkCurve, T>, ExodusError> {
|
||||
let verifying_share =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_share_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let merkle_proof =
|
||||
BoundedVec::<ExodusHash, crate::MerkleProofMaxSize<T>>::try_from(merkle_proof_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round6Package { context: self, verifying_share, merkle_proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl<NC, T> PackageMetadata<NC> for ExodusPackage<NC, T>
|
||||
where
|
||||
NC: Copy,
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NC> {
|
||||
match self {
|
||||
Self::NonceCommitment(package) => &package.context,
|
||||
Self::GroupCommitment(package) => &package.context,
|
||||
Self::SignatureShare(package) => &package.context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<NC, T> PackageMetadata<NC> for DkgPackage<NC, T>
|
||||
where
|
||||
NC: Copy,
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NC> {
|
||||
match self {
|
||||
Self::Round0(package) => &package.context,
|
||||
Self::Round1(package) => &package.context,
|
||||
Self::Round2(package) => &package.context,
|
||||
Self::Round3(package) => &package.context,
|
||||
Self::Round4(package) => &package.context,
|
||||
Self::Round5(package) => &package.context,
|
||||
Self::Round6(package) => &package.context,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
pub struct DkgRemovalResult {
|
||||
weight: Weight,
|
||||
cleared: bool,
|
||||
}
|
||||
|
||||
impl DkgRemovalResult {
|
||||
pub fn new<F>(try_clean_storage: F) -> Self
|
||||
where
|
||||
F: FnOnce() -> (Weight, bool)
|
||||
{
|
||||
let (weight, cleared) = try_clean_storage();
|
||||
Self { weight, cleared }
|
||||
}
|
||||
|
||||
pub fn fully_cleared(&self) -> bool {
|
||||
self.cleared
|
||||
}
|
||||
|
||||
pub fn used_weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub enum RoastStatus {
|
||||
#[default]
|
||||
NonceCommitments,
|
||||
GroupCommitments,
|
||||
PartialSignatures,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct RoastSessionState<Bitmap>
|
||||
where
|
||||
Bitmap: Default,
|
||||
{
|
||||
pub status: RoastStatus,
|
||||
pub nonce_committers: Bitmap,
|
||||
pub group_committers: Bitmap,
|
||||
pub partial_signers: Bitmap,
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
use scale_info::TypeInfo;
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
|
||||
|
||||
type ExodusNonceT = BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>;
|
||||
type ExodusSignatureT = BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>;
|
||||
type Type<NetworkId, Balance> = super::ExodusRequestType<NetworkId, Balance>;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct ExodusSignedMessage<NetworkId: Default, Balance: Default> {
|
||||
pub nonce: ExodusNonceT,
|
||||
pub signature: ExodusSignatureT,
|
||||
pub r#type: Type<NetworkId, Balance>,
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> ExodusSignedMessage<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
|
||||
Balance: UniqueSaturatedInto<u128> + Copy + Default,
|
||||
{
|
||||
pub fn new<E, S>(
|
||||
nonce_iter: E,
|
||||
signature_iter: S,
|
||||
r#type: Type<NetworkId, Balance>
|
||||
) -> Option<Self>
|
||||
where
|
||||
E: Iterator<Item = u8> + ExactSizeIterator,
|
||||
S: Iterator<Item = u8> + ExactSizeIterator,
|
||||
{
|
||||
let mut nonce = ExodusNonceT::new();
|
||||
nonce.try_extend(nonce_iter).ok()?;
|
||||
|
||||
let mut signature = ExodusSignatureT::new();
|
||||
signature.try_extend(signature_iter).ok()?;
|
||||
|
||||
Some(Self { nonce, signature, r#type })
|
||||
}
|
||||
|
||||
pub fn get_message<'a>(
|
||||
&self,
|
||||
message_buffer: &'a mut [u8; crate::MAX_MESSAGE_SIZE as usize],
|
||||
) -> Option<&'a [u8]> {
|
||||
self.r#type.get_message(message_buffer)
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
use crate::{AuthIndex, ExodusSession, DkgIndex, RoastSession};
|
||||
|
||||
pub enum ExodusOk<NetworkCurve> {
|
||||
ExodusEmptyRequests(AuthIndex, NetworkCurve),
|
||||
ExodusWaitingState(AuthIndex, NetworkCurve),
|
||||
ExodusNonceCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusGroupCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusSignedPartially(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusVerifyingShareMissed(AuthIndex, DkgIndex, NetworkCurve),
|
||||
|
||||
DkgNotPartOfRound(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundRegistered(u8, AuthIndex, NetworkCurve),
|
||||
DkgPreviousPurged(AuthIndex, NetworkCurve),
|
||||
DkgRoundsCleared(AuthIndex, NetworkCurve),
|
||||
DkgRoundAlreadyPassed(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundWaitingPeriod(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundNothingStored(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundMaxSignersNarrowed(u8, AuthIndex, AuthIndex, AuthIndex, NetworkCurve),
|
||||
}
|
||||
|
||||
impl<NetworkCurve> core::fmt::Display for ExodusOk<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: core::fmt::Display
|
||||
{
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
ExodusOk::ExodusEmptyRequests(auth_index, curve) => write!(fmt, "No EXODUS requests found by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusWaitingState(auth_index, curve) => write!(fmt, "Waiting for EXODUS requests to be processed by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusNonceCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Nonce committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusGroupCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Aggregated nonce and binding factor proof committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusSignedPartially(exodus, roast, auth_index, curve) => write!(fmt, "Partial signature created for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusVerifyingShareMissed(auth_index, dkg_index, curve) => write!(fmt, "EXODUS Round no verifying share for authority #{auth_index} based on DKG session #{dkg_index} for curve {curve}."),
|
||||
|
||||
ExodusOk::DkgNotPartOfRound(round, auth_index, curve) => write!(fmt, "DKG authority #{auth_index} for network curve {curve} is not part of Round{round}."),
|
||||
ExodusOk::DkgRoundRegistered(round, auth_index, curve) => write!(fmt, "DKG Round{round} registered by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgPreviousPurged(auth_index, curve) => write!(fmt, "Storage prepared for new DKG session by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundsCleared(auth_index, curve) => write!(fmt, "DKG Rounds cleared from storage by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundAlreadyPassed(round, auth_index, curve) => write!(fmt, "DKG Round{round} already done by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundWaitingPeriod(round, auth_index, curve) => write!(fmt, "DKG Round{round} waiting to be applied by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundNothingStored(round, auth_index, curve) => write!(fmt, "DKG nothing stored from Round{round} by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundMaxSignersNarrowed(round, auth_index, prev_max, curr_max, curve) => write!(fmt, "DKG Round{round} narrowed max_signers from {prev_max} to {curr_max} by {auth_index} for network curve {curve}."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn register_round0_package() -> Weight;
|
||||
fn register_round1_package(package_len: u32) -> Weight;
|
||||
fn register_encrypted_round2_packages(packages: u32,) -> Weight;
|
||||
fn register_round3_complaints(indices: u32) -> Weight;
|
||||
fn register_round4_justifications(packages: u32) -> Weight;
|
||||
fn register_round5_verifying_package() -> Weight;
|
||||
fn register_round6_public_share_package(packages: u32) -> Weight;
|
||||
fn register_nonce_commitment() -> Weight;
|
||||
fn register_group_commitment() -> Weight;
|
||||
fn register_signature_share() -> Weight;
|
||||
|
||||
fn register_evm_bridge_out_exodus() -> Weight;
|
||||
}
|
||||
|
||||
impl WeightInfo for () {
|
||||
fn register_round0_package() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_round1_package(_package_len: u32) -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_encrypted_round2_packages(_packages: u32,) -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_round3_complaints(_indices: u32) -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_round4_justifications(_packages: u32) -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_round5_verifying_package() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_round6_public_share_package(_packages: u32) -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_nonce_commitment() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_group_commitment() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_signature_share() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn register_evm_bridge_out_exodus() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
|
||||
use frame_support::dispatch::RawOrigin;
|
||||
use ghost_helpers::merkle_tree::{generate_tree, generate_proof};
|
||||
|
||||
#[benchmarks(where T: Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn claim() {
|
||||
let network_id = Default::default();
|
||||
let current_account: T::AccountId = frame_benchmarking::whitelisted_caller();
|
||||
|
||||
let max_proof_depth = <<T as Config>::MaxProofDepth as Get<u32>>::get();
|
||||
let minimum_donation_u128 = <<T as Config>::MinimumDonation as Get<u128>>::get();
|
||||
let minimum_donation: BalanceOf<T> = minimum_donation_u128.unique_saturated_into();
|
||||
|
||||
let dummy_balance_u128 = 420u128;
|
||||
let dummy_balance: BalanceOf<T> = dummy_balance_u128.unique_saturated_into();
|
||||
|
||||
let secret_key = libsecp256k1::SecretKey::parse(&[69u8; 32]).unwrap();
|
||||
let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
|
||||
let pubkey_raw = &public_key.serialize()[1..65];
|
||||
let pubkey_hash = SubstrateKeccakHasher::hash(pubkey_raw);
|
||||
|
||||
let mut evm_addr_bytes = [0u8; 20];
|
||||
evm_addr_bytes.copy_from_slice(&pubkey_hash.as_ref()[12..32]);
|
||||
let dummy_evm_address = EvmAddress::from(evm_addr_bytes);
|
||||
|
||||
let total_entries = 1u64 << max_proof_depth;
|
||||
let max_index = total_entries - 1;
|
||||
let raw_values = (0..total_entries)
|
||||
.map(|i| i as TokenId)
|
||||
.collect::<Vec<TokenId>>();
|
||||
|
||||
let merkle_tree = generate_tree::<SubstrateKeccakHasher, _, _, _, _>(
|
||||
max_index,
|
||||
raw_values,
|
||||
|index: TokenId| -> Result<(usize, Vec<u8>), ()> {
|
||||
let claim_package = ClaimPackage {
|
||||
balance: dummy_balance,
|
||||
merkle_proof: Default::default(),
|
||||
token_id: index,
|
||||
};
|
||||
|
||||
let claim_preimage = claim_package.get_preimage(&dummy_evm_address);
|
||||
Ok((index as usize, claim_preimage.to_vec()))
|
||||
}
|
||||
).unwrap();
|
||||
|
||||
let merkle_root = *merkle_tree.last().unwrap();
|
||||
|
||||
let member_share = MemberShare {
|
||||
initiated_network: None,
|
||||
activated_shares: Default::default(),
|
||||
locked_balance: minimum_donation,
|
||||
};
|
||||
MemberShares::<T>::insert(¤t_account, member_share);
|
||||
|
||||
let network_state = NetworkShare::new(
|
||||
dummy_balance,
|
||||
merkle_root,
|
||||
max_proof_depth,
|
||||
);
|
||||
NetworkShares::<T>::insert(&network_id, network_state);
|
||||
|
||||
let global_state = ShareState::new(dummy_balance);
|
||||
GlobalShares::<T>::put(global_state);
|
||||
|
||||
let merkle_proof = generate_proof::<SubstrateKeccakHasher, _>(
|
||||
&merkle_tree,
|
||||
total_entries - 1,
|
||||
0,
|
||||
);
|
||||
|
||||
let claim_package = ClaimPackage {
|
||||
merkle_proof: BoundedVec::try_from(merkle_proof).unwrap(),
|
||||
balance: dummy_balance,
|
||||
token_id: 0
|
||||
};
|
||||
|
||||
let preimage = claim_package.get_preimage(&dummy_evm_address);
|
||||
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
|
||||
|
||||
let message_string_bytes = Pallet::<T>::ethereum_signable_message(
|
||||
¤t_account,
|
||||
&network_id,
|
||||
&preimage_hash
|
||||
);
|
||||
|
||||
let message_hash = SubstrateKeccakHasher::hash(&message_string_bytes);
|
||||
let mut message_bytes = [0u8; 32];
|
||||
message_bytes.copy_from_slice(message_hash.as_ref());
|
||||
|
||||
let msg_to_sign = libsecp256k1::Message::parse(&message_bytes);
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&msg_to_sign, &secret_key);
|
||||
|
||||
let mut signature_raw = [0u8; 65];
|
||||
signature_raw[0..32].copy_from_slice(&sig.r.b32());
|
||||
signature_raw[32..64].copy_from_slice(&sig.s.b32());
|
||||
signature_raw[64] = recovery_id.serialize();
|
||||
|
||||
let evm_signature = EvmSignature::from(signature_raw);
|
||||
|
||||
#[extrinsic_call]
|
||||
claim(
|
||||
RawOrigin::Signed(current_account.clone()),
|
||||
network_id,
|
||||
claim_package,
|
||||
dummy_evm_address,
|
||||
evm_signature,
|
||||
);
|
||||
|
||||
assert_eq!(dummy_balance, GlobalShares::<T>::get().claimed_shares);
|
||||
assert_eq!(
|
||||
dummy_balance,
|
||||
MemberShares::<T>::get(¤t_account).activated_shares
|
||||
);
|
||||
assert_eq!(
|
||||
dummy_balance,
|
||||
NetworkShares::<T>::get(&network_id).claimed_shares(),
|
||||
);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Test,
|
||||
);
|
||||
}
|
||||
@ -1,460 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::{
|
||||
ensure, traits::{Currency, Get},
|
||||
pallet_prelude::*,
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
use sp_core::U256;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use sp_runtime::traits::{AtLeast32BitUnsigned, CheckedAdd, UniqueSaturatedInto};
|
||||
|
||||
use ghost_helpers::{
|
||||
SubstrateKeccakHasher, merkle_tree::verify_tree_proof,
|
||||
networks::{NetworkData, NetworkCurve, NetworkType, NetworkClaim},
|
||||
};
|
||||
|
||||
use ghost_traits::{
|
||||
hashing::GhostHasher,
|
||||
networks::{
|
||||
NetworkDataBasicHandler, NetworkDataInspectHandler,
|
||||
},
|
||||
};
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
mod weights;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
mod benchmarking;
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
const MAX_POSSIBLE_PROOF_DEPTH: u32 = 256;
|
||||
|
||||
type TokenId = u128;
|
||||
type EvmHash = sp_core::H256;
|
||||
type EvmAddress = sp_core::H160;
|
||||
type EvmSignature = [u8; 65];
|
||||
|
||||
#[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct ShareState<Balance>
|
||||
where
|
||||
Balance: Default + Copy + Clone,
|
||||
{
|
||||
total_shares: Balance,
|
||||
claimed_shares: Balance,
|
||||
}
|
||||
|
||||
impl<Balance> ShareState<Balance>
|
||||
where
|
||||
Balance: AtLeast32BitUnsigned + Default + Copy + Clone,
|
||||
{
|
||||
pub fn new(total_shares: Balance) -> Self {
|
||||
let mut new_state = ShareState::default();
|
||||
new_state.total_shares = total_shares;
|
||||
new_state
|
||||
}
|
||||
|
||||
pub fn try_increase_claimed(&mut self, increase_by: Balance) -> Option<Balance> {
|
||||
let new_claimed_shares = self.claimed_shares
|
||||
.saturating_add(increase_by);
|
||||
|
||||
if new_claimed_shares > self.total_shares {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.claimed_shares = new_claimed_shares;
|
||||
|
||||
Some(new_claimed_shares)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct NetworkShare<Balance>
|
||||
where
|
||||
Balance: Default + Copy + Clone,
|
||||
{
|
||||
proofs_size: u32,
|
||||
merkle_root: EvmHash,
|
||||
share_state: ShareState<Balance>
|
||||
}
|
||||
|
||||
impl<Balance> NetworkShare<Balance>
|
||||
where
|
||||
Balance: AtLeast32BitUnsigned + Default + Copy + Clone,
|
||||
{
|
||||
pub fn new(
|
||||
total_shares: Balance,
|
||||
merkle_root: EvmHash,
|
||||
proofs_size: u32
|
||||
) -> Self {
|
||||
let share_state = ShareState::new(total_shares);
|
||||
Self { merkle_root, share_state, proofs_size }
|
||||
}
|
||||
|
||||
pub fn total_shares(&self) -> Balance {
|
||||
self.share_state.total_shares
|
||||
}
|
||||
|
||||
pub fn claimed_shares(&self) -> Balance {
|
||||
self.share_state.claimed_shares
|
||||
}
|
||||
|
||||
pub fn try_increase_claimed(&mut self, increase_by: Balance) -> Option<Balance> {
|
||||
self.share_state.try_increase_claimed(increase_by)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct MemberShare<Balance, NetworkId>
|
||||
where
|
||||
Balance: AtLeast32BitUnsigned + Default + Copy,
|
||||
NetworkId: Default + Copy,
|
||||
{
|
||||
initiated_network: Option<NetworkId>,
|
||||
activated_shares: Balance,
|
||||
locked_balance: Balance,
|
||||
}
|
||||
|
||||
impl<Balance, NetworkId> MemberShare<Balance, NetworkId>
|
||||
where
|
||||
Balance: AtLeast32BitUnsigned + Default + Copy,
|
||||
NetworkId: Default + Copy,
|
||||
{
|
||||
pub fn increase_activated_shares(&mut self, extra_shares: Balance) {
|
||||
self.activated_shares = self.activated_shares
|
||||
.saturating_add(extra_shares);
|
||||
}
|
||||
|
||||
pub fn initiate_on_network(&mut self, network_id: NetworkId) {
|
||||
self.initiated_network = Some(network_id);
|
||||
}
|
||||
|
||||
pub fn lock_balance(&mut self, extra_balance: Balance) {
|
||||
self.locked_balance.saturating_add(extra_balance);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
|
||||
pub struct ClaimPackage<Balance>
|
||||
where
|
||||
Balance: Default + Copy + Clone,
|
||||
{
|
||||
pub token_id: TokenId,
|
||||
pub balance: Balance,
|
||||
pub merkle_proof: BoundedVec<EvmHash, ConstU32<MAX_POSSIBLE_PROOF_DEPTH>>,
|
||||
}
|
||||
|
||||
impl<Balance> ClaimPackage<Balance>
|
||||
where
|
||||
Balance: Default + Copy + Clone + UniqueSaturatedInto<u128>,
|
||||
{
|
||||
pub fn get_preimage(&self, evm_address: &EvmAddress) -> [u8; 96] {
|
||||
let mut token_id_bytes = [0u8; 32];
|
||||
U256::from(self.token_id as u128).to_big_endian(&mut token_id_bytes);
|
||||
|
||||
let balance_u128: u128 = self.balance.unique_saturated_into();
|
||||
let mut balance_bytes = [0u8; 32];
|
||||
U256::from(balance_u128).to_big_endian(&mut balance_bytes);
|
||||
|
||||
let mut address_bytes = [0u8; 32];
|
||||
address_bytes[12..32].copy_from_slice(evm_address.as_bytes());
|
||||
|
||||
let mut preimage = [0u8; 96];
|
||||
preimage[0..32].copy_from_slice(&token_id_bytes);
|
||||
preimage[32..64].copy_from_slice(&balance_bytes);
|
||||
preimage[64..96].copy_from_slice(&address_bytes);
|
||||
|
||||
preimage
|
||||
}
|
||||
}
|
||||
|
||||
pub type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
pub type NetworkIdOf<T> =
|
||||
<<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + core::fmt::Debug {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
type Currency: Currency<Self::AccountId>;
|
||||
|
||||
type NetworkDataHandler: NetworkDataInspectHandler<NetworkData>
|
||||
+ NetworkDataBasicHandler<NetworkCurve = NetworkCurve, NetworkType = NetworkType>;
|
||||
|
||||
#[pallet::constant]
|
||||
type MinimumDonation: Get<u128>;
|
||||
|
||||
#[pallet::constant]
|
||||
type MaxProofDepth: Get<u32>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
SharesClaimed {
|
||||
who: T::AccountId,
|
||||
balance: BalanceOf<T>,
|
||||
network_id: NetworkIdOf<T>,
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
PreimageAlreadyClaimed,
|
||||
BoundedToOtherNetwork,
|
||||
AuthorityNotGovernor,
|
||||
NetworkHasNoGenesis,
|
||||
InvalidMerkleProof,
|
||||
SharesOverflowed,
|
||||
BadSignature,
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn global_shares)]
|
||||
pub type GlobalShares<T: Config> = StorageValue<
|
||||
_,
|
||||
ShareState<BalanceOf<T>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn network_shares)]
|
||||
pub type NetworkShares<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, NetworkIdOf<T>,
|
||||
NetworkShare<BalanceOf<T>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn member_shares)]
|
||||
pub type MemberShares<T: Config> = CountedStorageMap<
|
||||
_,
|
||||
Blake2_256, T::AccountId,
|
||||
MemberShare<BalanceOf<T>, NetworkIdOf<T>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn claimed_hashes)]
|
||||
pub type ClaimedHashes<T: Config> = StorageMap<
|
||||
_,
|
||||
Blake2_256, EvmHash,
|
||||
bool,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(frame_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub network_claims: Vec<NetworkClaim<NetworkIdOf<T>, BalanceOf<T>>>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
let mut global_shares: BalanceOf<T> = Default::default();
|
||||
|
||||
self.network_claims
|
||||
.iter()
|
||||
.for_each(|c| {
|
||||
assert!(
|
||||
c.proof_size <= T::MaxProofDepth::get(),
|
||||
"CRITICAL: constant T::MaxProofDepth is not enough for network {:?}",
|
||||
c.network_id,
|
||||
);
|
||||
|
||||
let network_state = NetworkShare::new(c.amount, c.merkle_root, c.proof_size);
|
||||
NetworkShares::<T>::insert(c.network_id, network_state);
|
||||
|
||||
global_shares = global_shares.checked_add(&c.amount)
|
||||
.expect("Global total shares overflowed");
|
||||
});
|
||||
|
||||
let global_state = ShareState::new(global_shares);
|
||||
GlobalShares::<T>::put(global_state);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::claim())]
|
||||
pub fn claim(
|
||||
origin: OriginFor<T>,
|
||||
network_id: NetworkIdOf<T>,
|
||||
claim_package: ClaimPackage<BalanceOf<T>>,
|
||||
evm_address: EvmAddress,
|
||||
evm_signature: EvmSignature,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
let mut member_share = MemberShares::<T>::get(&who);
|
||||
let minimum_donation: BalanceOf<T> = T::MinimumDonation::get()
|
||||
.unique_saturated_into();
|
||||
|
||||
ensure!(
|
||||
member_share.locked_balance >= minimum_donation,
|
||||
Error::<T>::AuthorityNotGovernor,
|
||||
);
|
||||
|
||||
let correct_network_bound = member_share.initiated_network
|
||||
.map(|inner_network_id| inner_network_id == network_id)
|
||||
.unwrap_or(true);
|
||||
|
||||
ensure!(correct_network_bound, Error::<T>::BoundedToOtherNetwork);
|
||||
|
||||
let preimage = claim_package.get_preimage(&evm_address);
|
||||
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
|
||||
|
||||
ensure!(
|
||||
!ClaimedHashes::<T>::contains_key(&preimage_hash),
|
||||
Error::<T>::PreimageAlreadyClaimed
|
||||
);
|
||||
|
||||
Self::verify_signature(&who, &network_id, &preimage_hash, &evm_address, &evm_signature)?;
|
||||
Self::verify_merkle_proof(&network_id, &preimage, &claim_package)?;
|
||||
|
||||
NetworkShares::<T>::try_mutate(&network_id, |state| -> DispatchResult {
|
||||
let package_proof_size = claim_package.merkle_proof.len() as u32;
|
||||
let proof_size_match = package_proof_size == state.proofs_size;
|
||||
ensure!(proof_size_match, Error::<T>::SharesOverflowed);
|
||||
|
||||
let increased = state.try_increase_claimed(claim_package.balance);
|
||||
ensure!(increased.is_some(), Error::<T>::SharesOverflowed);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
GlobalShares::<T>::try_mutate(|state| -> DispatchResult {
|
||||
let increased = state.try_increase_claimed(claim_package.balance);
|
||||
ensure!(increased.is_some(), Error::<T>::SharesOverflowed);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
member_share.increase_activated_shares(claim_package.balance);
|
||||
member_share.initiate_on_network(network_id);
|
||||
|
||||
MemberShares::<T>::insert(&who, member_share);
|
||||
ClaimedHashes::<T>::insert(&preimage_hash, true);
|
||||
|
||||
Self::deposit_event(Event::<T>::SharesClaimed {
|
||||
balance: claim_package.balance,
|
||||
network_id,
|
||||
who,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(data.len() * 2);
|
||||
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
|
||||
for &b in data.iter() {
|
||||
push_nibble(b / 16);
|
||||
push_nibble(b % 16);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fn ethereum_signable_message(
|
||||
receiver: &T::AccountId,
|
||||
network_id: &NetworkIdOf<T>,
|
||||
preimage_hash: &EvmHash,
|
||||
) -> Vec<u8> {
|
||||
let prefix = b"GMV Claim:";
|
||||
let network_id_u64: u64 = (*network_id).unique_saturated_into();
|
||||
|
||||
let receiver_ascii_hex = Self::to_ascii_hex(&receiver.encode());
|
||||
let preimage_ascii_hex = Self::to_ascii_hex(preimage_hash.as_ref());
|
||||
let network_ascii_hex = Self::to_ascii_hex(&network_id_u64.to_be_bytes());
|
||||
|
||||
let what_length = receiver_ascii_hex.len()
|
||||
.saturating_add(preimage_ascii_hex.len())
|
||||
.saturating_add(network_ascii_hex.len())
|
||||
.saturating_add(10 + 11 + 12 + 11); // all 4 prefixes
|
||||
|
||||
let mut what = Vec::with_capacity(what_length);
|
||||
what.extend_from_slice(prefix);
|
||||
|
||||
what.extend_from_slice(b"\naccount:0x");
|
||||
what.extend_from_slice(&receiver_ascii_hex);
|
||||
|
||||
what.extend_from_slice(b"\npreimage:0x");
|
||||
what.extend_from_slice(&preimage_ascii_hex);
|
||||
|
||||
what.extend_from_slice(b"\nnetwork:0x");
|
||||
what.extend_from_slice(&network_ascii_hex);
|
||||
|
||||
let mut l = what.len();
|
||||
let mut rev = Vec::new();
|
||||
while l > 0 {
|
||||
rev.push(b'0' + (l % 10) as u8);
|
||||
l /= 10;
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(26 + rev.len() + what.len());
|
||||
v.extend_from_slice(b"\x19Ethereum Signed Message:\n");
|
||||
v.extend(rev.into_iter().rev());
|
||||
v.extend(what);
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
fn verify_signature(
|
||||
who: &T::AccountId,
|
||||
network_id: &NetworkIdOf<T>,
|
||||
preimage_hash: &EvmHash,
|
||||
evm_address: &EvmAddress,
|
||||
evm_signature: &EvmSignature,
|
||||
) -> DispatchResult {
|
||||
let message = Self::ethereum_signable_message(who, network_id, preimage_hash);
|
||||
let message_bytes = SubstrateKeccakHasher::hash(&message).to_fixed_bytes();
|
||||
|
||||
let recovered_pubkey = secp256k1_ecdsa_recover(evm_signature, &message_bytes)
|
||||
.map_err(|_| Error::<T>::BadSignature)?;
|
||||
|
||||
let recovered_pubkey_hash = SubstrateKeccakHasher::hash(&recovered_pubkey);
|
||||
let recovered_address = EvmAddress::from_slice(&recovered_pubkey_hash[12..32]);
|
||||
|
||||
ensure!(recovered_address == *evm_address, Error::<T>::BadSignature);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_merkle_proof(
|
||||
network_id: &NetworkIdOf<T>,
|
||||
preimage: &[u8],
|
||||
claim_package: &ClaimPackage<BalanceOf<T>>,
|
||||
) -> DispatchResult {
|
||||
let network_state = NetworkShares::<T>::get(network_id);
|
||||
|
||||
let is_merkle_proof_valid =
|
||||
verify_tree_proof::<SubstrateKeccakHasher, TokenId>(
|
||||
preimage,
|
||||
&claim_package.merkle_proof,
|
||||
network_state.merkle_root,
|
||||
claim_package.token_id,
|
||||
);
|
||||
|
||||
ensure!(is_merkle_proof_valid, Error::<T>::InvalidMerkleProof);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use crate as ghost_governance;
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU128},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use sp_runtime::BuildStorage;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
|
||||
type AccountId = sp_runtime::AccountId32;
|
||||
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxNetworks: u32 = 5;
|
||||
}
|
||||
|
||||
impl ghost_networks::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type NetworkId = u64;
|
||||
type RegisterOrigin = EnsureRoot<Self::AccountId>;
|
||||
type UpdateOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RemoveOrigin = EnsureRoot<Self::AccountId>;
|
||||
type MaxNetworks = MaxNetworks;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type Currency = Balances;
|
||||
type NetworkDataHandler = Networks;
|
||||
|
||||
type MinimumDonation = ConstU128<69>;
|
||||
type MaxProofDepth = ConstU32<5>;
|
||||
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Networks: ghost_networks,
|
||||
Governance: ghost_governance,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
ghost_governance::GenesisConfig::<Test> {
|
||||
network_claims: Default::default(),
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
@ -1,170 +0,0 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use mock::*;
|
||||
use super::*;
|
||||
|
||||
use sp_runtime::AccountId32;
|
||||
use frame_support::traits::Get;
|
||||
use frame_support::assert_ok;
|
||||
use libsecp256k1::{Message, SecretKey, PublicKey};
|
||||
|
||||
use ghost_helpers::merkle_tree::{generate_tree, generate_proof};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NewTestUser {
|
||||
pub substrate_account: AccountId32,
|
||||
pub evm_secret: SecretKey,
|
||||
pub evm_address: EvmAddress,
|
||||
pub allowed_balance: u64,
|
||||
pub token_id: u128,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claim_happy_path() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let network_id = 69u64;
|
||||
|
||||
let minimum_donation_u128 = <<Test as crate::Config>::MinimumDonation as Get<u128>>::get();
|
||||
let minimum_donation: BalanceOf<Test> = minimum_donation_u128.unique_saturated_into();
|
||||
|
||||
let secret_key_1 = SecretKey::parse(&[1u8; 32]).unwrap();
|
||||
let public_key_1 = PublicKey::from_secret_key(&secret_key_1);
|
||||
let pubkey_raw_1 = &public_key_1.serialize()[1..65];
|
||||
let pubkey_hash_1 = SubstrateKeccakHasher::hash(pubkey_raw_1);
|
||||
|
||||
let mut evm_addr_bytes_1 = [0u8; 20];
|
||||
evm_addr_bytes_1.copy_from_slice(&pubkey_hash_1.as_ref()[12..32]);
|
||||
|
||||
let user_1 = NewTestUser {
|
||||
substrate_account: AccountId32::new([11u8; 32]),
|
||||
evm_secret: secret_key_1,
|
||||
evm_address: EvmAddress::from(evm_addr_bytes_1),
|
||||
allowed_balance: 420u64,
|
||||
token_id: 0,
|
||||
};
|
||||
|
||||
let secret_key_2 = SecretKey::parse(&[2u8; 32]).unwrap();
|
||||
let public_key_2 = PublicKey::from_secret_key(&secret_key_2);
|
||||
let pubkey_raw_2 = &public_key_2.serialize()[1..65];
|
||||
let pubkey_hash_2 = SubstrateKeccakHasher::hash(pubkey_raw_2);
|
||||
|
||||
let mut evm_addr_bytes_2 = [0u8; 20];
|
||||
evm_addr_bytes_2.copy_from_slice(&pubkey_hash_2.as_ref()[12..32]);
|
||||
|
||||
let user_2 = NewTestUser {
|
||||
substrate_account: AccountId32::new([22u8; 32]),
|
||||
evm_secret: secret_key_2,
|
||||
evm_address: EvmAddress::from(evm_addr_bytes_2),
|
||||
allowed_balance: 1337u64,
|
||||
token_id: 1,
|
||||
};
|
||||
|
||||
let secret_key_3 = SecretKey::parse(&[3u8; 32]).unwrap();
|
||||
let public_key_3 = PublicKey::from_secret_key(&secret_key_3);
|
||||
let pubkey_raw_3 = &public_key_3.serialize()[1..65];
|
||||
let pubkey_hash_3 = SubstrateKeccakHasher::hash(pubkey_raw_3);
|
||||
|
||||
let mut evm_addr_bytes_3 = [0u8; 20];
|
||||
evm_addr_bytes_3.copy_from_slice(&pubkey_hash_3.as_ref()[12..32]);
|
||||
|
||||
let user_3 = NewTestUser {
|
||||
substrate_account: AccountId32::new([69u8; 32]),
|
||||
evm_secret: secret_key_3,
|
||||
evm_address: EvmAddress::from(evm_addr_bytes_3),
|
||||
allowed_balance: 69u64,
|
||||
token_id: 2,
|
||||
};
|
||||
|
||||
let users = vec![user_1.clone(), user_2.clone(), user_3.clone()];
|
||||
let max_index = 2u8;
|
||||
|
||||
let raw_values_complex = vec![(0u8, &user_1), (1u8, &user_2), (2u8, &user_3)];
|
||||
let merkle_tree = generate_tree::<SubstrateKeccakHasher, u8, _, _, _>(
|
||||
max_index,
|
||||
raw_values_complex,
|
||||
|
||||
|item: (u8, &NewTestUser)| -> Result<(usize, Vec<u8>), ()> {
|
||||
let (index, u) = item;
|
||||
|
||||
let tmp_package = ClaimPackage {
|
||||
balance: u.allowed_balance,
|
||||
merkle_proof: Default::default(),
|
||||
token_id: u.token_id,
|
||||
};
|
||||
|
||||
Ok((index as usize, tmp_package.get_preimage(&u.evm_address).to_vec()))
|
||||
}
|
||||
).unwrap();
|
||||
|
||||
let merkle_root = *merkle_tree.last().unwrap();
|
||||
|
||||
let network_state = NetworkShare::new(1_000_000, merkle_root, 2);
|
||||
let global_state = ShareState::new(1_000_000);
|
||||
NetworkShares::<Test>::insert(&network_id, network_state);
|
||||
GlobalShares::<Test>::put(global_state);
|
||||
|
||||
let mut global_activated = GlobalShares::<Test>::get().claimed_shares;
|
||||
let mut network_activated = NetworkShares::<Test>::get(&network_id)
|
||||
.claimed_shares();
|
||||
|
||||
for (slot_index, current_user) in users.iter().enumerate() {
|
||||
MemberShares::<Test>::insert(¤t_user.substrate_account, MemberShare {
|
||||
locked_balance: minimum_donation,
|
||||
initiated_network: None,
|
||||
activated_shares: 0,
|
||||
});
|
||||
|
||||
let proof = generate_proof::<SubstrateKeccakHasher, u8>(&merkle_tree, max_index, slot_index as u8);
|
||||
|
||||
let claim_package = ClaimPackage {
|
||||
balance: current_user.allowed_balance,
|
||||
merkle_proof: BoundedVec::try_from(proof).unwrap(),
|
||||
token_id: current_user.token_id,
|
||||
};
|
||||
|
||||
let preimage = claim_package.get_preimage(¤t_user.evm_address);
|
||||
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
|
||||
|
||||
let message_string_bytes = Governance::ethereum_signable_message(
|
||||
¤t_user.substrate_account,
|
||||
&network_id,
|
||||
&preimage_hash
|
||||
);
|
||||
|
||||
let message_hash = SubstrateKeccakHasher::hash(&message_string_bytes);
|
||||
let mut message_bytes = [0u8; 32];
|
||||
message_bytes.copy_from_slice(message_hash.as_ref());
|
||||
|
||||
let msg_to_sign = Message::parse(&message_bytes);
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&msg_to_sign, ¤t_user.evm_secret);
|
||||
|
||||
let mut signature_raw = [0u8; 65];
|
||||
signature_raw[0..32].copy_from_slice(&sig.r.b32());
|
||||
signature_raw[32..64].copy_from_slice(&sig.s.b32());
|
||||
signature_raw[64] = recovery_id.serialize();
|
||||
|
||||
let evm_signature = EvmSignature::from(signature_raw);
|
||||
|
||||
assert_ok!(Governance::claim(
|
||||
RuntimeOrigin::signed(current_user.substrate_account.clone()),
|
||||
network_id,
|
||||
claim_package,
|
||||
current_user.evm_address.clone(),
|
||||
evm_signature
|
||||
));
|
||||
|
||||
let updated_share = MemberShares::<Test>::get(¤t_user.substrate_account);
|
||||
assert_eq!(updated_share.activated_shares, current_user.allowed_balance);
|
||||
|
||||
global_activated += current_user.allowed_balance;
|
||||
network_activated += current_user.allowed_balance;
|
||||
}
|
||||
|
||||
assert_eq!(global_activated, GlobalShares::<Test>::get().claimed_shares);
|
||||
assert_eq!(
|
||||
network_activated,
|
||||
NetworkShares::<Test>::get(&network_id).claimed_shares(),
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-helpers"
|
||||
version = "0.0.5"
|
||||
description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components."
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
num-traits = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
codec = { workspace = true, features = ["max-encoded-len"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
frame-support = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
ghost-traits = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde/std",
|
||||
"num-traits/std",
|
||||
"strum/std",
|
||||
"scale-info/std",
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-std/std",
|
||||
"ghost-traits/std",
|
||||
"sp-io/std",
|
||||
"sp-core/std",
|
||||
]
|
||||
runtime-benchmarks = []
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,316 +0,0 @@
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
|
||||
fn combine_values<H, V>(left: V, right: V) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
let hash_len = H::hash_len();
|
||||
let mut combined = sp_std::vec![0u8; hash_len * 2];
|
||||
|
||||
combined[..hash_len].copy_from_slice(left.as_ref());
|
||||
combined[hash_len..].copy_from_slice(right.as_ref());
|
||||
|
||||
H::hash(&combined)
|
||||
}
|
||||
|
||||
pub fn cumulative_hash<H, S>(raw_values: S) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
raw_values
|
||||
.into_iter()
|
||||
.fold(H::empty(), |acc_hash, raw_value| {
|
||||
let hashed_value = H::hash(raw_value.as_ref());
|
||||
combine_values::<H, _>(acc_hash, hashed_value)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sequential_hash<H, S>(raw_values: S) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
let mut iter = raw_values.into_iter().peekable();
|
||||
|
||||
let Some(first_value) = iter.next() else {
|
||||
return H::empty();
|
||||
};
|
||||
if iter.peek().is_none() {
|
||||
return H::hash(first_value.as_ref());
|
||||
}
|
||||
|
||||
let current_hash = H::from_slice(first_value.as_ref());
|
||||
|
||||
iter.fold(current_hash, |acc_hash, raw_value| {
|
||||
let next_hash = H::from_slice(raw_value.as_ref());
|
||||
combine_values::<H, _>(acc_hash, next_hash)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_hash_ancestry<H, S>(
|
||||
tx_block_header_bytes: &[u8],
|
||||
ancestry_headers: S,
|
||||
trusted_top_block_hash: H::Hash,
|
||||
) -> bool
|
||||
where
|
||||
H: GhostHasher,
|
||||
H::Hash: PartialEq + Copy + AsRef<[u8]> + AsMut<[u8]>,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
ancestry_headers
|
||||
.into_iter()
|
||||
.try_fold(
|
||||
sequential_hash::<H, _>(core::iter::once(tx_block_header_bytes)),
|
||||
|acc_parent_hash, current_header| {
|
||||
let header_ref = current_header.as_ref();
|
||||
if header_ref.len() < 36 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut current_prev_hash = [0u8; 32];
|
||||
current_prev_hash.copy_from_slice(&header_ref[4..36]);
|
||||
|
||||
if current_prev_hash != acc_parent_hash.as_ref() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let current_header_iter = core::iter::once(header_ref);
|
||||
Some(sequential_hash::<H, _>(current_header_iter))
|
||||
},
|
||||
)
|
||||
.map(|mut final_le_hash| {
|
||||
final_le_hash.as_mut().reverse();
|
||||
final_le_hash == trusted_top_block_hash
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::H256;
|
||||
use sp_io::hashing::{blake2_256, sha2_256};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub struct TestBlake2Hasher;
|
||||
impl GhostHasher for TestBlake2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
H256::from(blake2_256(data))
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSha2Hasher;
|
||||
impl GhostHasher for TestSha2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
let first_pass: [u8; 32] = sha2_256(data);
|
||||
let second_pass: [u8; 32] = sha2_256(&first_pass);
|
||||
|
||||
H256::from(second_pass)
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_sequential_hash(header: &[u8; 80]) -> H256 {
|
||||
sequential_hash::<TestSha2Hasher, _>(core::iter::once(header))
|
||||
}
|
||||
|
||||
fn create_header(prev_hash: [u8; 32]) -> [u8; 80] {
|
||||
let mut header = [0u8; 80];
|
||||
header[4..36].copy_from_slice(&prev_hash);
|
||||
header
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_empty_values() {
|
||||
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
||||
let result = cumulative_hash::<TestBlake2Hasher, _>(empty_list);
|
||||
assert_eq!(H256::zero(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_determinism_and_avalanche() {
|
||||
let dataset_1 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
let dataset_2 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
let dataset_3 = sp_std::vec![vec![9, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
|
||||
let hash_1 = cumulative_hash::<TestBlake2Hasher, _>(dataset_1);
|
||||
let hash_2 = cumulative_hash::<TestBlake2Hasher, _>(dataset_2);
|
||||
let hash_3 = cumulative_hash::<TestBlake2Hasher, _>(dataset_3);
|
||||
|
||||
assert_eq!(hash_1, hash_2);
|
||||
assert_ne!(hash_1, hash_3);
|
||||
assert_ne!(hash_2, hash_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_order_sensitive() {
|
||||
let dataset_forward = sp_std::vec![sp_std::vec![1, 1, 1], sp_std::vec![2, 2, 2]];
|
||||
let dataset_reversed = sp_std::vec![sp_std::vec![2, 2, 2], sp_std::vec![1, 1, 1]];
|
||||
|
||||
let hash_forward = cumulative_hash::<TestBlake2Hasher, _>(dataset_forward);
|
||||
let hash_reversed = cumulative_hash::<TestBlake2Hasher, _>(dataset_reversed);
|
||||
|
||||
assert_ne!(hash_forward, hash_reversed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_with_slices() {
|
||||
let slice_data: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]];
|
||||
let hash_from_slice = cumulative_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
||||
assert_ne!(hash_from_slice, H256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_empty() {
|
||||
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(empty_list);
|
||||
assert_eq!(H256::zero(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_single_element() {
|
||||
let single_item = sp_std::vec![sp_std::vec![1, 2, 3]];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(single_item);
|
||||
let expected = TestBlake2Hasher::hash(&[1, 2, 3]);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_determinism_and_order() {
|
||||
let data_1 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
||||
let data_2 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
||||
let data_reversed = sp_std::vec![sp_std::vec![3], sp_std::vec![2], sp_std::vec![1]];
|
||||
|
||||
let hash_1 = sequential_hash::<TestBlake2Hasher, _>(data_1);
|
||||
let hash_2 = sequential_hash::<TestBlake2Hasher, _>(data_2);
|
||||
let hash_reversed = sequential_hash::<TestBlake2Hasher, _>(data_reversed);
|
||||
|
||||
assert_eq!(hash_1, hash_2);
|
||||
assert_ne!(hash_1, hash_reversed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_vs_cumulative_hash() {
|
||||
let dataset = sp_std::vec![sp_std::vec![1, 2, 3], sp_std::vec![4, 5, 6]];
|
||||
let seq_hash = sequential_hash::<TestBlake2Hasher, _>(dataset.clone());
|
||||
let cum_hash = cumulative_hash::<TestBlake2Hasher, _>(dataset);
|
||||
assert_ne!(seq_hash, cum_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_with_slices() {
|
||||
let slice_data: &[&[u8]] = &[&[10, 20], &[30, 40]];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
||||
assert_ne!(result, H256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_in_current_block_with_empty_ancestry() {
|
||||
let tx_header = [0x11u8; 80];
|
||||
let tx_block_le_hash = mock_sequential_hash(&tx_header);
|
||||
|
||||
let mut trusted_bytes = tx_block_le_hash.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&tx_header,
|
||||
&[],
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_single_ancestor_header() {
|
||||
let tx_header_100 = [0x22u8; 80];
|
||||
let hash_100_le = mock_sequential_hash(&tx_header_100);
|
||||
|
||||
let header_101 = create_header(hash_100_le.0);
|
||||
let hash_101_le = mock_sequential_hash(&header_101);
|
||||
|
||||
let mut trusted_bytes = hash_101_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [header_101];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&tx_header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_multiple_ancestor_headers_chain() {
|
||||
let header_100 = [0x55u8; 80];
|
||||
let hash_100_le = mock_sequential_hash(&header_100);
|
||||
|
||||
let header_101 = create_header(hash_100_le.0);
|
||||
let hash_101_le = mock_sequential_hash(&header_101);
|
||||
|
||||
let header_102 = create_header(hash_101_le.0);
|
||||
let hash_102_le = mock_sequential_hash(&header_102);
|
||||
|
||||
let header_103 = create_header(hash_102_le.0);
|
||||
let hash_103_le = mock_sequential_hash(&header_103);
|
||||
|
||||
let mut trusted_bytes = hash_103_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [header_101, header_102, header_103];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_fails_if_chain_is_broken() {
|
||||
let header_100 = [0xAAu8; 80];
|
||||
|
||||
let broken_header_101 = create_header([0xFF; 32]);
|
||||
let hash_101_le = mock_sequential_hash(&broken_header_101);
|
||||
|
||||
let mut trusted_bytes = hash_101_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [broken_header_101];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
use sp_core::H256;
|
||||
use sp_io::hashing::{blake2_256, keccak_256, sha2_256};
|
||||
|
||||
pub mod bounded_bitmap;
|
||||
pub mod hash_chain;
|
||||
pub mod merkle_tree;
|
||||
pub mod networks;
|
||||
pub mod utxo;
|
||||
|
||||
pub type AuthIndexU8 = u8;
|
||||
pub type AuthIndexU16 = u16;
|
||||
pub type AuthIndexU32 = u32;
|
||||
pub type AuthIndexU64 = u64;
|
||||
pub type AuthIndexU128 = u128;
|
||||
|
||||
pub type BitmapChunkU8 = u8;
|
||||
pub type BitmapChunkU16 = u16;
|
||||
pub type BitmapChunkU32 = u32;
|
||||
pub type BitmapChunkU64 = u64;
|
||||
pub type BitmapChunkU128 = u128;
|
||||
|
||||
pub type DkgIndexU8 = u8;
|
||||
pub type DkgIndexU16 = u16;
|
||||
pub type DkgIndexU32 = u32;
|
||||
pub type DkgIndexU64 = u64;
|
||||
pub type DkgIndexU128 = u128;
|
||||
|
||||
pub type NetworkIdU8 = u8;
|
||||
pub type NetworkIdU16 = u16;
|
||||
pub type NetworkIdU32 = u32;
|
||||
pub type NetworkIdU64 = u64;
|
||||
pub type NetworkIdU128 = u128;
|
||||
|
||||
pub fn get_byzantium_threshold<I>(value: I) -> I
|
||||
where
|
||||
I: num_traits::PrimInt,
|
||||
{
|
||||
let zero = I::zero();
|
||||
let one = I::one();
|
||||
let two = one + one;
|
||||
let three = two + one;
|
||||
|
||||
if value == zero {
|
||||
return zero;
|
||||
}
|
||||
|
||||
(value - one) / three * two + one
|
||||
}
|
||||
|
||||
pub struct SubstrateBlake2Hasher;
|
||||
impl GhostHasher for SubstrateBlake2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
H256::from(blake2_256(data))
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubstrateKeccakHasher;
|
||||
impl GhostHasher for SubstrateKeccakHasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
H256::from(keccak_256(data))
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UtxoSha2Hasher;
|
||||
impl GhostHasher for UtxoSha2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
let first_pass: [u8; 32] = sha2_256(data);
|
||||
let second_pass: [u8; 32] = sha2_256(&first_pass);
|
||||
|
||||
H256::from(second_pass)
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
}
|
||||
@ -1,571 +0,0 @@
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
use sp_arithmetic::traits::AtLeast8BitUnsigned;
|
||||
use sp_runtime::traits::UniqueSaturatedInto;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub fn generate_tree<H, I, S, F, E>(
|
||||
max_index: I,
|
||||
raw_values: S,
|
||||
generate_preimage: F,
|
||||
) -> Result<Vec<H::Hash>, E>
|
||||
where
|
||||
H: GhostHasher,
|
||||
S: IntoIterator,
|
||||
I: AtLeast8BitUnsigned + UniqueSaturatedInto<usize>,
|
||||
F: Fn(S::Item) -> Result<(usize, Vec<u8>), E>,
|
||||
{
|
||||
let num_of_leaves: usize = (max_index + I::one()).unique_saturated_into();
|
||||
let padded_leaves = num_of_leaves.next_power_of_two();
|
||||
let total_capacity = (2 * padded_leaves) - 1;
|
||||
let mut merkle_tree = sp_std::vec![H::empty(); total_capacity];
|
||||
|
||||
for item in raw_values.into_iter() {
|
||||
let (index, preimage) = generate_preimage(item)?;
|
||||
merkle_tree[index] = H::hash(&preimage);
|
||||
}
|
||||
|
||||
let mut layer_start = 0;
|
||||
let mut current_layer_len = padded_leaves;
|
||||
let mut write_ptr = padded_leaves;
|
||||
|
||||
let hash_len = H::hash_len();
|
||||
let mut combined = sp_std::vec![0u8; hash_len * 2];
|
||||
|
||||
while current_layer_len > 1 {
|
||||
for i in (0..current_layer_len).step_by(2) {
|
||||
let layer_index = layer_start + i;
|
||||
let left_bytes = merkle_tree[layer_index].as_ref();
|
||||
let right_bytes = merkle_tree[layer_index | 1].as_ref();
|
||||
|
||||
combined[..hash_len].copy_from_slice(left_bytes);
|
||||
combined[hash_len..].copy_from_slice(right_bytes);
|
||||
|
||||
merkle_tree[write_ptr] = H::hash(&combined);
|
||||
write_ptr += 1;
|
||||
}
|
||||
layer_start += current_layer_len;
|
||||
current_layer_len >>= 1;
|
||||
}
|
||||
|
||||
Ok(merkle_tree)
|
||||
}
|
||||
|
||||
pub fn generate_proof<H, I>(merkle_tree: &[H::Hash], max_index: I, index: I) -> Vec<H::Hash>
|
||||
where
|
||||
H: GhostHasher,
|
||||
I: AtLeast8BitUnsigned + UniqueSaturatedInto<usize>,
|
||||
{
|
||||
let num_of_leaves: usize = (max_index + I::one()).unique_saturated_into();
|
||||
let mut current_index: usize = index.unique_saturated_into();
|
||||
let mut current_layer_len = num_of_leaves.next_power_of_two();
|
||||
|
||||
let tree_height = current_layer_len.trailing_zeros() as usize;
|
||||
let mut proof = Vec::with_capacity(tree_height);
|
||||
|
||||
let mut layer_start = 0;
|
||||
|
||||
while current_layer_len > 1 {
|
||||
let sibling_idx_in_layer = current_index ^ 1;
|
||||
let sibling_hash = merkle_tree[layer_start + sibling_idx_in_layer];
|
||||
proof.push(sibling_hash);
|
||||
|
||||
layer_start += current_layer_len;
|
||||
current_layer_len >>= 1;
|
||||
current_index >>= 1;
|
||||
}
|
||||
|
||||
proof
|
||||
}
|
||||
|
||||
pub fn verify_tree_proof<H, I>(
|
||||
preimage: &[u8],
|
||||
merkle_proof: &[H::Hash],
|
||||
merkle_root: H::Hash,
|
||||
index: I,
|
||||
) -> bool
|
||||
where
|
||||
H: GhostHasher,
|
||||
I: PartialOrd + UniqueSaturatedInto<usize>,
|
||||
{
|
||||
let mut current_hash = H::hash(preimage);
|
||||
let mut current_index: usize = index.unique_saturated_into();
|
||||
|
||||
if current_index >= (1 << merkle_proof.len()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let hash_len = H::hash_len();
|
||||
let mut combined = sp_std::vec![0u8; hash_len * 2];
|
||||
|
||||
for sibling in merkle_proof.iter() {
|
||||
let sibling_bytes = sibling.as_ref();
|
||||
let hash_bytes = current_hash.as_ref();
|
||||
|
||||
if current_index % 2 == 0 {
|
||||
combined[..hash_len].copy_from_slice(hash_bytes);
|
||||
combined[hash_len..].copy_from_slice(sibling_bytes);
|
||||
} else {
|
||||
combined[..hash_len].copy_from_slice(sibling_bytes);
|
||||
combined[hash_len..].copy_from_slice(hash_bytes);
|
||||
}
|
||||
|
||||
current_hash = H::hash(&combined);
|
||||
current_index >>= 1;
|
||||
}
|
||||
|
||||
current_hash == merkle_root
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::H256;
|
||||
use sp_io::hashing::blake2_256;
|
||||
|
||||
pub struct TestBlake2Hasher;
|
||||
impl GhostHasher for TestBlake2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
H256::from(blake2_256(data))
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_tree_perfect_power_of_two() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
(3, sp_std::vec![4]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tree.len(), 7);
|
||||
|
||||
let h0 = TestBlake2Hasher::hash(&vec![1]);
|
||||
let h1 = TestBlake2Hasher::hash(&vec![2]);
|
||||
let h2 = TestBlake2Hasher::hash(&vec![3]);
|
||||
let h3 = TestBlake2Hasher::hash(&vec![4]);
|
||||
|
||||
let mut c1 = [0u8; 64];
|
||||
c1[..32].copy_from_slice(h0.as_ref());
|
||||
c1[32..].copy_from_slice(h1.as_ref());
|
||||
let parent_left = TestBlake2Hasher::hash(&c1);
|
||||
|
||||
let mut c2 = [0u8; 64];
|
||||
c2[..32].copy_from_slice(h2.as_ref());
|
||||
c2[32..].copy_from_slice(h3.as_ref());
|
||||
let parent_right = TestBlake2Hasher::hash(&c2);
|
||||
|
||||
let mut c_root = [0u8; 64];
|
||||
c_root[..32].copy_from_slice(parent_left.as_ref());
|
||||
c_root[32..].copy_from_slice(parent_right.as_ref());
|
||||
let expected_root = TestBlake2Hasher::hash(&c_root);
|
||||
|
||||
assert_eq!(tree.last().unwrap(), &expected_root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_tree_with_padding_hashing() {
|
||||
let max_index = 2u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tree.len(), 7);
|
||||
assert_eq!(tree[3], H256::zero());
|
||||
|
||||
let h2 = TestBlake2Hasher::hash(&vec![3]);
|
||||
let h3_empty = H256::zero();
|
||||
|
||||
let mut combined_right = [0u8; 64];
|
||||
combined_right[..32].copy_from_slice(h2.as_ref());
|
||||
combined_right[32..].copy_from_slice(h3_empty.as_ref());
|
||||
let expected_parent_right = TestBlake2Hasher::hash(&combined_right);
|
||||
|
||||
assert_eq!(tree[5], expected_parent_right);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_tree_determinism() {
|
||||
let max_index = 1u32;
|
||||
|
||||
let data_1 = sp_std::vec![(0, sp_std::vec![100]), (1, sp_std::vec![200])];
|
||||
let data_2 = sp_std::vec![(0, sp_std::vec![100]), (1, sp_std::vec![200])];
|
||||
let data_3 = sp_std::vec![(0, sp_std::vec![101]), (1, sp_std::vec![200])];
|
||||
|
||||
let tree_1 =
|
||||
generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_1, |(i, b)| {
|
||||
Ok((i, b))
|
||||
})
|
||||
.unwrap();
|
||||
let tree_2 =
|
||||
generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_2, |(i, b)| {
|
||||
Ok((i, b))
|
||||
})
|
||||
.unwrap();
|
||||
let tree_3 =
|
||||
generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_3, |(i, b)| {
|
||||
Ok((i, b))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tree_1.last().unwrap(), tree_2.last().unwrap());
|
||||
assert_ne!(tree_1.last().unwrap(), tree_3.last().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_tree_single_element() {
|
||||
let max_index = 0u32;
|
||||
let raw_data = sp_std::vec![(0, sp_std::vec![42])];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tree.len(), 1);
|
||||
assert_eq!(tree[0], TestBlake2Hasher::hash(&vec![42]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_and_verification_end_to_end() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
(3, sp_std::vec![4]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
let target_index = 2u32;
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);
|
||||
|
||||
assert_eq!(proof.len(), 2);
|
||||
|
||||
let preimage = sp_std::vec![3];
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);
|
||||
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_index_out_of_bounds() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
(3, sp_std::vec![4]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
let target_index = 2u32;
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);
|
||||
|
||||
assert_eq!(proof.len(), 2);
|
||||
|
||||
let preimage = sp_std::vec![3];
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index + 1);
|
||||
|
||||
assert!(!is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_contains_correct_siblings() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![10]),
|
||||
(1, sp_std::vec![20]),
|
||||
(2, sp_std::vec![30]),
|
||||
(3, sp_std::vec![40]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let h0 = tree[0];
|
||||
let h1 = tree[1];
|
||||
let h2 = tree[2];
|
||||
let h3 = tree[3];
|
||||
|
||||
let parent_left = tree[4]; // hash(h0 + h1)
|
||||
let parent_right = tree[5]; // hash(h2 + h3)
|
||||
|
||||
let proof_for_0 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
|
||||
assert_eq!(proof_for_0[0], h1);
|
||||
assert_eq!(proof_for_0[1], parent_right);
|
||||
|
||||
let proof_for_1 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 1);
|
||||
assert_eq!(proof_for_1[0], h0);
|
||||
assert_eq!(proof_for_1[1], parent_right);
|
||||
|
||||
let proof_for_2 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
|
||||
assert_eq!(proof_for_2[0], h3);
|
||||
assert_eq!(proof_for_2[1], parent_left);
|
||||
|
||||
let proof_for_3 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3);
|
||||
assert_eq!(proof_for_3[0], h2);
|
||||
assert_eq!(proof_for_3[1], parent_left);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_for_padded_tree_leaf() {
|
||||
let max_index = 2u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
|
||||
assert_eq!(proof[0], H256::zero());
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&sp_std::vec![3], &proof, root, 2);
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proof_for_single_element_tree() {
|
||||
let max_index = 0u32;
|
||||
let raw_data = sp_std::vec![(0, sp_std::vec![99])];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
|
||||
assert!(proof.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_tree_proof_with_h0_h3_and_random_values() {
|
||||
let max_index = 3u32;
|
||||
let leaf_0 = sp_std::vec![10u8];
|
||||
let leaf_1 = sp_std::vec![20u8];
|
||||
let leaf_2 = sp_std::vec![30u8];
|
||||
let leaf_3 = sp_std::vec![40u8];
|
||||
|
||||
let raw_data = sp_std::vec![
|
||||
(0, leaf_0.clone()),
|
||||
(1, leaf_1.clone()),
|
||||
(2, leaf_2.clone()),
|
||||
(3, leaf_3.clone())
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
let h0 = tree[0];
|
||||
let h1 = tree[1];
|
||||
let h2 = tree[2];
|
||||
let h3 = tree[3];
|
||||
|
||||
let proof_for_1 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 1);
|
||||
assert_eq!(proof_for_1[0], h0);
|
||||
|
||||
let is_valid_1 = verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_1, &proof_for_1, root, 1);
|
||||
assert!(is_valid_1);
|
||||
|
||||
let proof_for_2 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
|
||||
assert_eq!(proof_for_2[0], h3);
|
||||
|
||||
let is_valid_2 = verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_2, &proof_for_2, root, 2);
|
||||
assert!(is_valid_2);
|
||||
|
||||
let proof_for_0 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
|
||||
assert_eq!(proof_for_0[0], h1);
|
||||
assert!(verify_tree_proof::<TestBlake2Hasher, u32>(
|
||||
&leaf_0,
|
||||
&proof_for_0,
|
||||
root,
|
||||
0
|
||||
));
|
||||
|
||||
let proof_for_3 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3);
|
||||
assert_eq!(proof_for_3[0], h2);
|
||||
assert!(verify_tree_proof::<TestBlake2Hasher, u32>(
|
||||
&leaf_3,
|
||||
&proof_for_3,
|
||||
root,
|
||||
3
|
||||
));
|
||||
|
||||
let mut bad_proof = proof_for_1.clone();
|
||||
bad_proof[0] = H256::random();
|
||||
let is_valid_bad_proof =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_1, &bad_proof, root, 1);
|
||||
assert!(!is_valid_bad_proof);
|
||||
|
||||
let bad_preimage = vec![99u8];
|
||||
let is_valid_bad_preimage =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&bad_preimage, &proof_for_1, root, 1);
|
||||
assert!(!is_valid_bad_preimage);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_tree_proof_deep_tree_depth_4() {
|
||||
let max_index = 15u32;
|
||||
|
||||
let mut raw_data = sp_std::vec![];
|
||||
for i in 0..16 {
|
||||
raw_data.push((i, sp_std::vec![i as u8]));
|
||||
}
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
assert_eq!(tree.len(), 31);
|
||||
|
||||
let target_index = 11u32;
|
||||
let preimage = vec![target_index as u8];
|
||||
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);
|
||||
assert_eq!(proof.len(), 4);
|
||||
|
||||
let h10 = tree[10];
|
||||
assert_eq!(proof[0], h10);
|
||||
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);
|
||||
assert!(is_valid);
|
||||
|
||||
let is_valid_wrong_index =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, 4);
|
||||
assert!(!is_valid_wrong_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verification_index_at_upper_bound() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
(3, sp_std::vec![4]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
let target_index = 3u32;
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);
|
||||
|
||||
assert_eq!(proof.len(), 2);
|
||||
|
||||
let preimage = sp_std::vec![4];
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);
|
||||
|
||||
assert!(is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verification_fails_when_index_out_of_bounds() {
|
||||
let max_index = 3u32;
|
||||
let raw_data = sp_std::vec![
|
||||
(0, sp_std::vec![1]),
|
||||
(1, sp_std::vec![2]),
|
||||
(2, sp_std::vec![3]),
|
||||
(3, sp_std::vec![4]),
|
||||
];
|
||||
|
||||
let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
|
||||
max_index,
|
||||
raw_data,
|
||||
|(index, bytes)| Ok((index, bytes)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let root = *tree.last().unwrap();
|
||||
|
||||
let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3u32);
|
||||
|
||||
let target_index = 4u32;
|
||||
let preimage = sp_std::vec![4];
|
||||
|
||||
let is_valid =
|
||||
verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);
|
||||
|
||||
assert!(!is_valid);
|
||||
}
|
||||
}
|
||||
@ -1,335 +0,0 @@
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use sp_runtime::serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumCount, FromRepr};
|
||||
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_core::H256;
|
||||
|
||||
pub const MAX_ENDPOINT_LEN: u32 = 128;
|
||||
pub const MAX_ENDPOINTS_LEN: u32 = 10;
|
||||
pub const MAX_GATEKEEPER_LEN: u32 = 65;
|
||||
pub const MAX_SELECTOR_LEN: u32 = 4;
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
EnumIter,
|
||||
EnumCount,
|
||||
FromRepr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum NetworkType {
|
||||
#[default]
|
||||
Evm = 0,
|
||||
Utxo = 1,
|
||||
Unknown = 2,
|
||||
}
|
||||
|
||||
impl NetworkType {
|
||||
pub fn is_evm(&self) -> bool {
|
||||
matches!(self, NetworkType::Evm)
|
||||
}
|
||||
|
||||
pub fn is_utxo(&self) -> bool {
|
||||
matches!(self, NetworkType::Utxo)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for NetworkType {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let text = match self {
|
||||
NetworkType::Evm => "EVM",
|
||||
NetworkType::Utxo => "UTXO",
|
||||
NetworkType::Unknown => "Unknown",
|
||||
};
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
EnumIter,
|
||||
EnumCount,
|
||||
FromRepr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum NetworkCurve {
|
||||
#[default]
|
||||
Secp256k1 = 0,
|
||||
Ed25519 = 1,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for NetworkCurve {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let text = match self {
|
||||
NetworkCurve::Secp256k1 => "secp256k1",
|
||||
NetworkCurve::Ed25519 => "ed25519"
|
||||
};
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct NetworkInitiation<NetworkId, Balance> {
|
||||
pub id: NetworkId,
|
||||
pub amount: Balance,
|
||||
pub data: NetworkData,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NetworkInitiationBuilder<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: Default,
|
||||
Balance: Default,
|
||||
{
|
||||
pub id: Option<NetworkId>,
|
||||
pub amount: Option<Balance>,
|
||||
pub data: Option<NetworkData>,
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> NetworkInitiationBuilder<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: Default,
|
||||
Balance: Default,
|
||||
{
|
||||
pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
|
||||
self.id = Some(network_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gatekeeper_amount(mut self, amount: Balance) -> Self {
|
||||
self.amount = Some(amount);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_network_data(mut self, data: NetworkData) -> Self {
|
||||
self.data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> NetworkInitiation<NetworkId, Balance> {
|
||||
NetworkInitiation {
|
||||
id: self.id.unwrap(),
|
||||
amount: self.amount.unwrap(),
|
||||
data: self.data.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct NetworkClaim<NetworkId, Balance> {
|
||||
pub network_id: NetworkId,
|
||||
pub merkle_root: H256,
|
||||
pub amount: Balance,
|
||||
pub proof_size: u32
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> NetworkClaim<NetworkId, Balance> {
|
||||
pub fn with_amount(mut self, amount: Balance) -> Self {
|
||||
self.amount = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
|
||||
self.network_id = network_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_merkle_root(mut self, merkle_root: H256) -> Self {
|
||||
self.merkle_root = merkle_root;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_proof_size(mut self, proof_size: u32) -> Self {
|
||||
self.proof_size = proof_size;
|
||||
self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub struct NetworkData {
|
||||
pub gatekeeper: BoundedVec<u8, ConstU32<MAX_GATEKEEPER_LEN>>,
|
||||
pub selector: BoundedVec<u8, ConstU32<MAX_SELECTOR_LEN>>,
|
||||
pub default_endpoints:
|
||||
BoundedVec<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>, ConstU32<MAX_ENDPOINTS_LEN>>,
|
||||
|
||||
pub r#type: NetworkType,
|
||||
pub curve: NetworkCurve,
|
||||
|
||||
pub finality_delay: u64,
|
||||
pub rate_limit_delay: u64,
|
||||
pub block_deviation: u64,
|
||||
|
||||
pub incoming_fee: u32,
|
||||
pub outgoing_fee: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NetworkDataBuilder {
|
||||
pub gatekeeper: Vec<u8>,
|
||||
pub selector: Vec<u8>,
|
||||
pub default_endpoints: Vec<Vec<u8>>,
|
||||
|
||||
pub r#type: NetworkType,
|
||||
pub curve: NetworkCurve,
|
||||
|
||||
pub finality_delay: u64,
|
||||
pub rate_limit_delay: u64,
|
||||
pub block_deviation: u64,
|
||||
|
||||
pub incoming_fee: u32,
|
||||
pub outgoing_fee: u32,
|
||||
}
|
||||
|
||||
impl NetworkDataBuilder {
|
||||
pub fn with_gatekeeper(mut self, gatekeeper: Vec<u8>) -> Self {
|
||||
self.gatekeeper = gatekeeper;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_selector(mut self, selector: Vec<u8>) -> Self {
|
||||
self.selector = selector;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_network_type(mut self, network_type: NetworkType) -> Self {
|
||||
self.r#type = network_type;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_network_curve(mut self, curve: NetworkCurve) -> Self {
|
||||
self.curve = curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_finality_delay(mut self, finality_delay: u64) -> Self {
|
||||
self.finality_delay = finality_delay;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_rate_limit_delay(mut self, rate_limit_delay: u64) -> Self {
|
||||
self.rate_limit_delay = rate_limit_delay;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_block_deviation(mut self, block_deviation: u64) -> Self {
|
||||
self.block_deviation = block_deviation;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_incoming_fee(mut self, incoming_fee: u32) -> Self {
|
||||
self.incoming_fee = incoming_fee;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_outgoing_fee(mut self, outgoing_fee: u32) -> Self {
|
||||
self.outgoing_fee = outgoing_fee;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_default_endpoints(mut self, default_endpoints: Vec<Vec<u8>>) -> Self {
|
||||
self.default_endpoints = default_endpoints;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> NetworkData {
|
||||
let gatekeeper = BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(self.gatekeeper)
|
||||
.expect("Gatekeeper exceeds max length");
|
||||
|
||||
let selector = BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(self.selector)
|
||||
.expect("Selector exceeds max length");
|
||||
|
||||
let default_endpoints = BoundedVec::<
|
||||
BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>,
|
||||
ConstU32<MAX_ENDPOINTS_LEN>,
|
||||
>::try_from(
|
||||
self.default_endpoints
|
||||
.into_iter()
|
||||
.map(|endpoint| {
|
||||
BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(endpoint)
|
||||
.expect("Endpoint exceeds max length")
|
||||
})
|
||||
.collect::<Vec<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>>>(),
|
||||
)
|
||||
.expect("Too many endpoints");
|
||||
|
||||
NetworkData {
|
||||
gatekeeper,
|
||||
selector,
|
||||
default_endpoints,
|
||||
|
||||
r#type: self.r#type,
|
||||
curve: self.curve,
|
||||
|
||||
finality_delay: self.finality_delay,
|
||||
rate_limit_delay: self.rate_limit_delay,
|
||||
block_deviation: self.block_deviation,
|
||||
|
||||
incoming_fee: self.incoming_fee,
|
||||
outgoing_fee: self.outgoing_fee,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,745 +0,0 @@
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
const MAX_INPUTS: usize = 3;
|
||||
const MAX_OUTPUTS: usize = 4;
|
||||
const MAX_SCRIPT_LEN: usize = 34;
|
||||
const MAX_WITNESS_STACK_DEPTH: usize = 3;
|
||||
const MAX_WITNESS_ITEMS_LEN: usize = 73;
|
||||
|
||||
pub const MAX_TX_LEN: usize = 512;
|
||||
|
||||
pub struct TxIn<H: GhostHasher> {
|
||||
pub prev_txid: H::Hash,
|
||||
pub vout: u32,
|
||||
pub script_sig: Vec<u8>,
|
||||
pub sequence: u32,
|
||||
}
|
||||
|
||||
pub struct TxOut {
|
||||
pub value: u64,
|
||||
pub script_pubkey: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct Witness {
|
||||
pub stack: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub enum Transaction<H: GhostHasher> {
|
||||
Legacy {
|
||||
version: i32,
|
||||
inputs: Vec<TxIn<H>>,
|
||||
outputs: Vec<TxOut>,
|
||||
lock_time: u32,
|
||||
},
|
||||
SegWit {
|
||||
version: i32,
|
||||
inputs: Vec<TxIn<H>>,
|
||||
outputs: Vec<TxOut>,
|
||||
witnesses: Vec<Witness>,
|
||||
lock_time: u32,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Header<H: GhostHasher> {
|
||||
pub merkle_root: H::Hash,
|
||||
pub tx_count: u32,
|
||||
pub tree_hashes: Vec<H::Hash>,
|
||||
pub flags: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<H: GhostHasher> Header<H> {
|
||||
fn calc_tree_width(&self, h: u32) -> u32 {
|
||||
(self.tx_count + (1 << h) - 1) >> h
|
||||
}
|
||||
|
||||
fn traverse(
|
||||
&self,
|
||||
height: u32,
|
||||
pos: u32,
|
||||
bits_used: &mut u32,
|
||||
hash_used: &mut u32,
|
||||
matched_index: &mut Option<usize>,
|
||||
target_txid: H::Hash,
|
||||
) -> Result<H::Hash, ParseError> {
|
||||
let byte_idx = (*bits_used / 8) as usize;
|
||||
let bit_idx = (*bits_used % 8) as u8;
|
||||
|
||||
if byte_idx >= self.flags.len() {
|
||||
return Err(ParseError::InvalidVarint(VarintError::BufferTooShort));
|
||||
}
|
||||
|
||||
let parent_of_match = ((self.flags[byte_idx] >> bit_idx) & 1) == 1;
|
||||
*bits_used += 1;
|
||||
|
||||
if height == 0 || !parent_of_match {
|
||||
if *hash_used as usize >= self.tree_hashes.len() {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
let curr_hash = self.tree_hashes[*hash_used as usize];
|
||||
*hash_used += 1;
|
||||
|
||||
if height == 0 && parent_of_match && curr_hash == target_txid {
|
||||
*matched_index = Some(pos as usize);
|
||||
}
|
||||
|
||||
return Ok(curr_hash);
|
||||
}
|
||||
|
||||
let left = self.traverse(
|
||||
height - 1,
|
||||
pos * 2,
|
||||
bits_used,
|
||||
hash_used,
|
||||
matched_index,
|
||||
target_txid,
|
||||
)?;
|
||||
|
||||
let right = if pos * 2 + 1 < self.calc_tree_width(height - 1) {
|
||||
let right = self.traverse(
|
||||
height - 1,
|
||||
pos * 2 + 1,
|
||||
bits_used,
|
||||
hash_used,
|
||||
matched_index,
|
||||
target_txid,
|
||||
)?;
|
||||
|
||||
if right == left {
|
||||
return Err(ParseError::TypeConversionFailed);
|
||||
}
|
||||
|
||||
right
|
||||
} else {
|
||||
left
|
||||
};
|
||||
|
||||
let mut concat = [0u8; 64];
|
||||
concat[..32].copy_from_slice(left.as_ref());
|
||||
concat[32..].copy_from_slice(right.as_ref());
|
||||
let current_node_hash = H::hash(&concat);
|
||||
|
||||
Ok(current_node_hash)
|
||||
}
|
||||
|
||||
pub fn extract_proof(&self, target_txid: H::Hash) -> Result<H::Hash, ParseError> {
|
||||
let mut bits_used = 0u32;
|
||||
let mut hash_used = 0u32;
|
||||
|
||||
let mut matched_index = None;
|
||||
let mut max_height = 0;
|
||||
while (1 << max_height) < self.tx_count {
|
||||
max_height += 1;
|
||||
}
|
||||
|
||||
let computed_root = self.traverse(
|
||||
max_height,
|
||||
0,
|
||||
&mut bits_used,
|
||||
&mut hash_used,
|
||||
&mut matched_index,
|
||||
target_txid,
|
||||
)?;
|
||||
|
||||
if hash_used as usize != self.tree_hashes.len() {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
if (bits_used + 7) / 8 != self.flags.len() as u32 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
if matched_index.is_some() {
|
||||
Ok(computed_root)
|
||||
} else {
|
||||
Err(ParseError::TransactionNotFoundInProof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Transaction<H>
|
||||
where
|
||||
H: GhostHasher<HashBytes = [u8; 32]>,
|
||||
{
|
||||
pub fn compute_txid(&self) -> Option<H::Hash> {
|
||||
let (version, inputs, outputs, lock_time) = match self {
|
||||
Self::Legacy {
|
||||
version,
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
} => (version, inputs, outputs, lock_time),
|
||||
Self::SegWit {
|
||||
version,
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
..
|
||||
} => (version, inputs, outputs, lock_time),
|
||||
};
|
||||
|
||||
let version_bytes = version.to_le_bytes();
|
||||
let lock_time_bytes = lock_time.to_le_bytes();
|
||||
|
||||
let mut tx_buffer = [0u8; MAX_TX_LEN];
|
||||
let mut cursor = 0;
|
||||
|
||||
tx_buffer[cursor..cursor + 4].copy_from_slice(&version_bytes);
|
||||
cursor += 4;
|
||||
|
||||
cursor = Self::serialize_inputs_and_outputs(&mut tx_buffer, cursor, inputs, outputs)?;
|
||||
|
||||
if cursor + 4 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
tx_buffer[cursor..cursor + 4].copy_from_slice(&lock_time_bytes);
|
||||
cursor += 4;
|
||||
|
||||
let final_tx_bytes = &tx_buffer[..cursor];
|
||||
|
||||
Some(H::hash(&final_tx_bytes))
|
||||
}
|
||||
|
||||
fn serialize_inputs_and_outputs(
|
||||
buffer: &mut [u8; MAX_TX_LEN],
|
||||
cursor: usize,
|
||||
inputs: &[TxIn<H>],
|
||||
outputs: &[TxOut],
|
||||
) -> Option<usize> {
|
||||
let mut cursor = Self::write_varint_to_buf(buffer, cursor, inputs.len() as u64)?;
|
||||
|
||||
for input in inputs {
|
||||
if cursor + 32 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + 32].copy_from_slice(input.prev_txid.as_ref());
|
||||
cursor += 32;
|
||||
|
||||
if cursor + 4 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + 4].copy_from_slice(&input.vout.to_le_bytes());
|
||||
cursor += 4;
|
||||
|
||||
cursor = Self::write_varint_to_buf(buffer, cursor, input.script_sig.len() as u64)?;
|
||||
|
||||
if cursor + input.script_sig.len() > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + input.script_sig.len()].copy_from_slice(&input.script_sig);
|
||||
cursor += input.script_sig.len();
|
||||
|
||||
if cursor + 4 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + 4].copy_from_slice(&input.sequence.to_le_bytes());
|
||||
cursor += 4;
|
||||
}
|
||||
|
||||
cursor = Self::write_varint_to_buf(buffer, cursor, outputs.len() as u64)?;
|
||||
|
||||
for output in outputs {
|
||||
if cursor + 8 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + 8].copy_from_slice(&output.value.to_le_bytes());
|
||||
cursor += 8;
|
||||
|
||||
cursor = Self::write_varint_to_buf(buffer, cursor, output.script_pubkey.len() as u64)?;
|
||||
|
||||
if cursor + output.script_pubkey.len() > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor..cursor + output.script_pubkey.len()]
|
||||
.copy_from_slice(&output.script_pubkey);
|
||||
cursor += output.script_pubkey.len();
|
||||
}
|
||||
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
fn write_varint_to_buf(
|
||||
buffer: &mut [u8; MAX_TX_LEN],
|
||||
cursor: usize,
|
||||
value: u64,
|
||||
) -> Option<usize> {
|
||||
let mut cursor = cursor;
|
||||
|
||||
if value < 253 {
|
||||
if cursor + 1 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor] = value as u8;
|
||||
cursor += 1;
|
||||
} else if value <= 0xffff {
|
||||
if cursor + 3 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor] = 253;
|
||||
buffer[cursor + 1..cursor + 3].copy_from_slice(&(value as u16).to_le_bytes());
|
||||
cursor += 3;
|
||||
} else if value <= 0xffffffff {
|
||||
if cursor + 5 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor] = 254;
|
||||
buffer[cursor + 1..cursor + 5].copy_from_slice(&(value as u32).to_le_bytes());
|
||||
cursor += 5;
|
||||
} else {
|
||||
if cursor + 9 > MAX_TX_LEN {
|
||||
return None;
|
||||
}
|
||||
buffer[cursor] = 255;
|
||||
buffer[cursor + 1..cursor + 9].copy_from_slice(&value.to_le_bytes());
|
||||
cursor += 9;
|
||||
}
|
||||
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
pub fn validate<I, A>(
|
||||
&self,
|
||||
o_index_i: I,
|
||||
w_index_i: I,
|
||||
destination: &[u8],
|
||||
expected_amount_a: A,
|
||||
) -> Option<[u8; 32]>
|
||||
where
|
||||
I: sp_runtime::traits::UniqueSaturatedInto<usize>,
|
||||
A: sp_runtime::traits::UniqueSaturatedInto<u128>,
|
||||
{
|
||||
let o_index: usize = o_index_i.unique_saturated_into();
|
||||
let w_index: usize = w_index_i.unique_saturated_into();
|
||||
|
||||
let expected_amount_u128: u128 = expected_amount_a.unique_saturated_into();
|
||||
let expected_amount: u64 = expected_amount_u128.try_into().ok()?;
|
||||
|
||||
match self {
|
||||
Self::Legacy { outputs, .. } => {
|
||||
let output = outputs.get(o_index)?;
|
||||
|
||||
if expected_amount != output.value {
|
||||
return None;
|
||||
}
|
||||
if output.script_pubkey.len() != 57 {
|
||||
return None;
|
||||
}
|
||||
let slice = &output.script_pubkey;
|
||||
|
||||
let receiver_address = self.parse_receiver(slice)?;
|
||||
if receiver_address != destination {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut extra_data = [0u8; 32];
|
||||
extra_data.copy_from_slice(&slice[25..57]);
|
||||
|
||||
Some(extra_data)
|
||||
}
|
||||
Self::SegWit {
|
||||
witnesses, outputs, ..
|
||||
} => {
|
||||
let output = outputs.get(o_index)?;
|
||||
if expected_amount != output.value {
|
||||
return None;
|
||||
}
|
||||
|
||||
let receiver_address = self.parse_receiver(&output.script_pubkey)?;
|
||||
if receiver_address != destination {
|
||||
return None;
|
||||
}
|
||||
|
||||
let witness = witnesses.get(w_index)?;
|
||||
|
||||
let target_account: [u8; 32];
|
||||
|
||||
// NOTE: change it right after the real bridging tx
|
||||
// will take place, or find something similar
|
||||
#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
|
||||
{
|
||||
let _ = witness.stack;
|
||||
target_account = [105u8; 32]; // 0x69 in decimal
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "runtime-benchmarks", feature = "std")))]
|
||||
{
|
||||
if witness.stack.len() < 3 || witness.stack[2].len() != 32 {
|
||||
return None;
|
||||
}
|
||||
|
||||
target_account = witness.stack[2][..32].try_into().ok()?;
|
||||
}
|
||||
|
||||
Some(target_account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_receiver(&self, script: &[u8]) -> Option<[u8; 32]> {
|
||||
let mut receiver = [0u8; 32];
|
||||
|
||||
// 1. Parse Pay-to-Taproot (P2TR / SegWit v1)
|
||||
// Markers: OP_1 (0x51) + OP_PUSHBYTES_32 (0x20) + 32 bytes key
|
||||
if script.len() == 34 && script[0] == 0x51 && script[1] == 0x20 {
|
||||
receiver.copy_from_slice(&script[2..34]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
// 2. Parse Pay-to-Witness-Script-Hash (P2WSH / SegWit v0)
|
||||
// Markers: OP_0 (0x00) + OP_PUSHBYTES_32 (0x20) + 32 bytes hash script
|
||||
if script.len() == 34 && script[0] == 0x00 && script[1] == 0x20 {
|
||||
receiver.copy_from_slice(&script[2..34]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
// 3a. Parse Native SegWit 20-bytes key (P2WPKH) zero-padding
|
||||
// Markers: 22 bytes len, starts with OP_0 (0x00), next OP_PUSHBYTES_20 (0x14)
|
||||
if script.len() == 22 && script[0] == 0x00 && script[1] == 0x14 {
|
||||
receiver[..20].copy_from_slice(&script[2..22]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
// 3b. Parse Pay-to-Script-Hash (P2SH)
|
||||
// Script: OP_HASH160 (0xa9) OP_PUSHBYTES_20 (0x14) + 20 bytes hash + OP_EQUAL (0x87)
|
||||
if script.len() == 23 && script[0] == 0xa9 && script[1] == 0x14 && script[22] == 0x87 {
|
||||
receiver[..20].copy_from_slice(&script[2..22]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
// 4. Parse Legacy (P2PKH) zero-padding
|
||||
// Script: OP_DUP(0x76) OP_HASH160(0xa9) OP_PUSHBYTES_20(0x14) +
|
||||
// 20 bytes hash + OP_EQUALVERIFY(0x88) OP_CHECKSIG(0xac)
|
||||
if script.len() == 25
|
||||
&& script[0] == 0x76
|
||||
&& script[1] == 0xa9
|
||||
&& script[2] == 0x14
|
||||
&& script[23] == 0x88
|
||||
&& script[24] == 0xac
|
||||
{
|
||||
receiver[..20].copy_from_slice(&script[3..23]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
// 5. Custom one: Legacy (P2PKH) + 32 bytes
|
||||
// Script: [0..25] — basic P2PKH, [25..57] — 32 bytes of extra data
|
||||
if script.len() == 57
|
||||
&& script[0] == 0x76 // OP_DUP
|
||||
&& script[1] == 0xa9 // OP_HASH160
|
||||
&& script[2] == 0x14 // OP_PUSHBYTES_20
|
||||
&& script[23] == 0x88 // OP_EQUALVERIFY
|
||||
&& script[24] == 0xac
|
||||
// OP_CHECKSIG
|
||||
{
|
||||
receiver[..20].copy_from_slice(&script[3..23]);
|
||||
return Some(receiver);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum VarintError {
|
||||
EmptyInput,
|
||||
BufferTooShort,
|
||||
TypeConversionFailed,
|
||||
}
|
||||
|
||||
fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
|
||||
let first = *bytes.get(0).ok_or(VarintError::EmptyInput)?;
|
||||
*bytes = &bytes[1..];
|
||||
|
||||
match first {
|
||||
0xfd => {
|
||||
if bytes.len() < 2 {
|
||||
return Err(VarintError::BufferTooShort);
|
||||
}
|
||||
let val = u16::from_le_bytes(
|
||||
bytes[..2]
|
||||
.try_into()
|
||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||
);
|
||||
*bytes = &bytes[2..];
|
||||
Ok(val as usize)
|
||||
}
|
||||
0xfe => {
|
||||
if bytes.len() < 4 {
|
||||
return Err(VarintError::BufferTooShort);
|
||||
}
|
||||
let val = u32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||
);
|
||||
*bytes = &bytes[4..];
|
||||
Ok(val as usize)
|
||||
}
|
||||
0xff => {
|
||||
if bytes.len() < 8 {
|
||||
return Err(VarintError::BufferTooShort);
|
||||
}
|
||||
let val = u64::from_le_bytes(
|
||||
bytes[..8]
|
||||
.try_into()
|
||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||
);
|
||||
*bytes = &bytes[8..];
|
||||
Ok(val as usize)
|
||||
}
|
||||
_ => Ok(first as usize),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum ParseError {
|
||||
InvalidLength,
|
||||
ExceededMaxLimits,
|
||||
TypeConversionFailed,
|
||||
TransactionNotFoundInProof,
|
||||
InvalidVarint(VarintError),
|
||||
}
|
||||
|
||||
impl From<VarintError> for ParseError {
|
||||
fn from(err: VarintError) -> Self {
|
||||
ParseError::InvalidVarint(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Transaction<H> {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
if bytes.len() < 4 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let version = i32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
bytes = &bytes[4..];
|
||||
|
||||
let is_segwit = bytes.len() >= 2 && bytes[0] == 0x00 && bytes[1] == 0x01;
|
||||
if is_segwit {
|
||||
bytes = &bytes[2..];
|
||||
}
|
||||
|
||||
let input_count = read_varint(&mut bytes)?;
|
||||
if input_count > MAX_INPUTS {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
|
||||
let mut inputs_buffer: [Option<TxIn<H>>; MAX_INPUTS] = [const { None }; MAX_INPUTS];
|
||||
for idx in 0..input_count {
|
||||
if bytes.len() < 36 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let mut prev_txid = [0u8; 32];
|
||||
prev_txid.copy_from_slice(&bytes[..32]);
|
||||
let vout = u32::from_le_bytes(
|
||||
bytes[32..36]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
bytes = &bytes[36..];
|
||||
|
||||
let script_len = read_varint(&mut bytes)?;
|
||||
if script_len > MAX_SCRIPT_LEN {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
if bytes.len() < script_len {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let script_sig = bytes[..script_len].to_vec();
|
||||
bytes = &bytes[script_len..];
|
||||
|
||||
if bytes.len() < 4 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let sequence = u32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
bytes = &bytes[4..];
|
||||
|
||||
inputs_buffer[idx] = Some(TxIn {
|
||||
prev_txid: H::from_slice(&prev_txid),
|
||||
vout,
|
||||
script_sig,
|
||||
sequence,
|
||||
});
|
||||
}
|
||||
|
||||
let output_count = read_varint(&mut bytes)?;
|
||||
if output_count > MAX_OUTPUTS {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
|
||||
let mut outputs_buffer: [Option<TxOut>; MAX_OUTPUTS] = [const { None }; MAX_OUTPUTS];
|
||||
for idx in 0..output_count {
|
||||
if bytes.len() < 8 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let value = u64::from_le_bytes(
|
||||
bytes[..8]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
bytes = &bytes[8..];
|
||||
|
||||
let script_len = read_varint(&mut bytes)?;
|
||||
if script_len > MAX_SCRIPT_LEN {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
if bytes.len() < script_len {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let script_pubkey = bytes[..script_len].to_vec();
|
||||
bytes = &bytes[script_len..];
|
||||
|
||||
outputs_buffer[idx] = Some(TxOut {
|
||||
value,
|
||||
script_pubkey,
|
||||
});
|
||||
}
|
||||
|
||||
let inputs: Vec<TxIn<H>> = inputs_buffer[..input_count]
|
||||
.iter_mut()
|
||||
.filter_map(|opt| opt.take())
|
||||
.collect();
|
||||
|
||||
let outputs: Vec<TxOut> = outputs_buffer[..output_count]
|
||||
.iter_mut()
|
||||
.filter_map(|opt| opt.take())
|
||||
.collect();
|
||||
|
||||
if is_segwit {
|
||||
let mut witnesses_buffer: [Option<Witness>; MAX_INPUTS] = [const { None }; MAX_INPUTS];
|
||||
for idx in 0..input_count {
|
||||
let item_count = read_varint(&mut bytes)?;
|
||||
if item_count > MAX_WITNESS_STACK_DEPTH {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
|
||||
let mut stack_buffer: [Option<Vec<u8>>; MAX_WITNESS_STACK_DEPTH] =
|
||||
[const { None }; MAX_WITNESS_STACK_DEPTH];
|
||||
for s_idx in 0..item_count {
|
||||
let item_len = read_varint(&mut bytes)?;
|
||||
if item_len > MAX_WITNESS_ITEMS_LEN {
|
||||
return Err(ParseError::ExceededMaxLimits);
|
||||
}
|
||||
if bytes.len() < item_len {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
stack_buffer[s_idx] = Some(bytes[..item_len].to_vec());
|
||||
bytes = &bytes[item_len..];
|
||||
}
|
||||
|
||||
let stack: Vec<Vec<u8>> = stack_buffer[..item_count]
|
||||
.iter_mut()
|
||||
.filter_map(|opt| opt.take())
|
||||
.collect();
|
||||
|
||||
witnesses_buffer[idx] = Some(Witness { stack });
|
||||
}
|
||||
|
||||
let witnesses: Vec<Witness> = witnesses_buffer[..input_count]
|
||||
.iter_mut()
|
||||
.filter_map(|opt| opt.take())
|
||||
.collect();
|
||||
|
||||
if bytes.len() < 4 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let lock_time = u32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
|
||||
Ok(Transaction::SegWit {
|
||||
version,
|
||||
inputs,
|
||||
outputs,
|
||||
witnesses,
|
||||
lock_time,
|
||||
})
|
||||
} else {
|
||||
if bytes.len() < 4 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let lock_time = u32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
|
||||
Ok(Transaction::Legacy {
|
||||
version,
|
||||
inputs,
|
||||
outputs,
|
||||
lock_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Header<H> {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
if bytes.len() < 85 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
let header_bytes = &bytes[..80];
|
||||
bytes = &bytes[80..];
|
||||
|
||||
let mut merkle_root = [0u8; 32];
|
||||
merkle_root.copy_from_slice(&header_bytes[36..68]);
|
||||
|
||||
if bytes.len() < 4 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let tx_count = u32::from_le_bytes(
|
||||
bytes[..4]
|
||||
.try_into()
|
||||
.map_err(|_| ParseError::TypeConversionFailed)?,
|
||||
);
|
||||
bytes = &bytes[4..];
|
||||
|
||||
let hash_count = read_varint(&mut bytes)?;
|
||||
if bytes.len() < hash_count * 32 {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
let tree_hashes_bytes = &bytes[..hash_count * 32];
|
||||
bytes = &bytes[hash_count * 32..];
|
||||
|
||||
let tree_hashes: Vec<H::Hash> = tree_hashes_bytes
|
||||
.chunks_exact(32)
|
||||
.map(|chunk| H::from_slice(chunk))
|
||||
.collect();
|
||||
|
||||
let flags_count = read_varint(&mut bytes)?;
|
||||
if bytes.len() < flags_count {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
let flags = bytes[..flags_count].to_vec();
|
||||
bytes = &bytes[flags_count..];
|
||||
|
||||
if !bytes.is_empty() {
|
||||
return Err(ParseError::InvalidLength);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
merkle_root: H::from_slice(&merkle_root),
|
||||
tx_count,
|
||||
tree_hashes,
|
||||
flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-networks"
|
||||
version = "0.2.7"
|
||||
description = "Registry and lifecycle management for external network metadata and cryptographic profiles."
|
||||
version = "0.1.26"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -11,7 +10,6 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
codec = { workspace = true, features = ["max-encoded-len"] }
|
||||
hex = { workspace = true, features = ["alloc"] }
|
||||
num-traits = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
@ -22,7 +20,6 @@ pallet-staking = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
ghost-traits = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
primitives = { workspace = true }
|
||||
@ -44,7 +41,6 @@ std = [
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"ghost-traits/std",
|
||||
"ghost-helpers/std",
|
||||
"pallet-staking/std",
|
||||
"pallet-balances/std",
|
||||
]
|
||||
|
||||
@ -6,10 +6,9 @@ use crate::Pallet as GhostNetworks;
|
||||
use frame_benchmarking::v1::{benchmarks, BenchmarkError};
|
||||
use sp_runtime::Saturating;
|
||||
|
||||
use ghost_helpers::networks::{
|
||||
MAX_ENDPOINT_LEN, MAX_ENDPOINTS_LEN, MAX_GATEKEEPER_LEN,
|
||||
MAX_SELECTOR_LEN,
|
||||
};
|
||||
const MAX_NAME_LEN: u32 = 20;
|
||||
const MAX_ENDPOINT_LEN: u32 = 150;
|
||||
const MAX_ENDPOINT_NUMBER: u32 = 20;
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
@ -19,13 +18,18 @@ fn prepare_network<T: Config>(
|
||||
n: u32,
|
||||
m: u32,
|
||||
k: u32,
|
||||
) -> (T::NetworkId, NetworkData) {
|
||||
) -> (<T as module::Config>::NetworkId, NetworkData) {
|
||||
let chain_id: <T as module::Config>::NetworkId = Default::default();
|
||||
let chain_id = chain_id.saturating_add((n + m).into());
|
||||
|
||||
let mut gatekeeper = sp_std::vec![];
|
||||
for i in 0..n {
|
||||
gatekeeper.push((i % 256) as u8);
|
||||
let mut gatekeeper = b"0x".to_vec();
|
||||
for i in 0..40 {
|
||||
gatekeeper.push(i);
|
||||
}
|
||||
|
||||
let mut topic_name = b"0x".to_vec();
|
||||
for i in 0..64 {
|
||||
topic_name.push(i);
|
||||
}
|
||||
|
||||
let mut default_endpoints = sp_std::vec![];
|
||||
@ -33,24 +37,25 @@ fn prepare_network<T: Config>(
|
||||
default_endpoints.push(sp_std::vec![0x69; m as usize]);
|
||||
}
|
||||
|
||||
let network = ghost_helpers::networks::NetworkDataBuilder::default()
|
||||
.with_gatekeeper(gatekeeper)
|
||||
.with_default_endpoints(default_endpoints)
|
||||
.with_selector(vec![1u8; 4])
|
||||
.with_network_type(NetworkType::Evm)
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_rate_limit_delay(6)
|
||||
.with_finality_delay(69)
|
||||
.with_block_deviation(420)
|
||||
.with_incoming_fee(0)
|
||||
.with_outgoing_fee(0)
|
||||
.build();
|
||||
let network = NetworkData {
|
||||
chain_name: sp_std::vec![0x69; n as usize],
|
||||
default_endpoints,
|
||||
gatekeeper,
|
||||
topic_name,
|
||||
network_type: NetworkType::Evm,
|
||||
avg_block_speed: 12,
|
||||
finality_delay: 69,
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
incoming_fee: 0,
|
||||
outgoing_fee: 0,
|
||||
};
|
||||
|
||||
(chain_id, network)
|
||||
}
|
||||
|
||||
fn create_network<T: Config>(
|
||||
chain_id: T::NetworkId,
|
||||
chain_id: <T as module::Config>::NetworkId,
|
||||
network: NetworkData,
|
||||
) -> Result<Option<NetworkData>, BenchmarkError> {
|
||||
let prev_network = match GhostNetworks::<T>::get(&chain_id) {
|
||||
@ -59,8 +64,8 @@ fn create_network<T: Config>(
|
||||
let authority = T::RegisterOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
GhostNetworks::<T>::register_network(
|
||||
authority,
|
||||
chain_id,
|
||||
authority.clone(),
|
||||
chain_id.clone(),
|
||||
network.clone(),
|
||||
)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
@ -73,52 +78,55 @@ fn create_network<T: Config>(
|
||||
|
||||
benchmarks! {
|
||||
register_network {
|
||||
let i in 1 .. MAX_GATEKEEPER_LEN;
|
||||
let i in 1 .. MAX_NAME_LEN;
|
||||
let j in 1 .. MAX_ENDPOINT_LEN;
|
||||
let k in 1 .. MAX_ENDPOINTS_LEN;
|
||||
let k in 1 .. MAX_ENDPOINT_NUMBER;
|
||||
|
||||
let (chain_id, network) = prepare_network::<T>(i, j, k);
|
||||
let authority = T::RegisterOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), None);
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
|
||||
assert!(GhostNetworks::<T>::network_indexes().is_empty());
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, network.clone())
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkRegistered {chain_id: chain_id}.into());
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), Some(network));
|
||||
assert_last_event::<T>(Event::NetworkRegistered {
|
||||
chain_id: chain_id.clone(), network: network.clone(),
|
||||
}.into());
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
|
||||
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
|
||||
}
|
||||
|
||||
update_network_selector {
|
||||
let raw_selector = sp_std::vec![0x42; MAX_SELECTOR_LEN as usize];
|
||||
let selector = BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(raw_selector)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
update_network_name {
|
||||
let n in 1 .. MAX_NAME_LEN;
|
||||
let name = sp_std::vec![0x42; n as usize];
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, selector)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), name.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkSelectorUpdated { chain_id }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkNameUpdated {
|
||||
chain_id: chain_id.clone(), chain_name: name,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_endpoint {
|
||||
let n in 1 .. MAX_ENDPOINT_LEN;
|
||||
let index = 0u32;
|
||||
let raw_endpoint = sp_std::vec![0x42; n as usize];
|
||||
let endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(raw_endpoint)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
let index_to_update = 0u32;
|
||||
let endpoint = sp_std::vec![0x42; n as usize];
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, Some(index), Some(endpoint))
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), Some(index_to_update), Some(endpoint.clone()))
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkEndpointUpdated {chain_id, index }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkEndpointUpdated {
|
||||
chain_id: chain_id.clone(),
|
||||
index: index_to_update,
|
||||
endpoint,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_finality_delay {
|
||||
@ -126,13 +134,13 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, delay)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkFinalityDelayUpdated {
|
||||
chain_id, finality_delay: delay,
|
||||
chain_id: chain_id.clone(), finality_delay: delay,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_rate_limit_delay {
|
||||
@ -140,25 +148,27 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, rate_limit)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), rate_limit)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated {
|
||||
chain_id, rate_limit_delay: rate_limit,
|
||||
chain_id: chain_id.clone(), rate_limit_delay: rate_limit,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_block_deviation {
|
||||
let block_deviation = 1337;
|
||||
update_network_block_distance {
|
||||
let block_distance = 1337;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, block_deviation)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), block_distance)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkBlockDeviationUpdated { chain_id, block_deviation }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkBlockDistanceUpdated {
|
||||
chain_id: chain_id.clone(), block_distance,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_type {
|
||||
@ -166,42 +176,42 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, network_type.clone())
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network_type.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkTypeUpdated { chain_id, network_type }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
|
||||
}
|
||||
|
||||
update_network_curve {
|
||||
let network_curve = NetworkCurve::Ed25519;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, network_curve)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkCurveUpdated { chain_id, network_curve }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkTypeUpdated {
|
||||
chain_id: chain_id.clone(), network_type: network_type.clone(),
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_gatekeeper {
|
||||
let mut raw_gatekeeper = b"0x".to_vec();
|
||||
for i in 0..40 { raw_gatekeeper.push(i + 1); }
|
||||
|
||||
let gatekeeper =
|
||||
BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(raw_gatekeeper)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
let mut gatekeeper = b"0x".to_vec();
|
||||
for i in 0..40 { gatekeeper.push(i + 1); }
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, gatekeeper)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), gatekeeper.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkGatekeeperUpdated { chain_id }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkGatekeeperUpdated {
|
||||
chain_id: chain_id.clone(), gatekeeper,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_topic_name {
|
||||
let topic_name = b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec();
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), topic_name.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkTopicNameUpdated {
|
||||
chain_id: chain_id.clone(), topic_name,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_incoming_network_fee {
|
||||
@ -209,10 +219,12 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, incoming_fee)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), incoming_fee)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkIncomingFeeUpdated { chain_id, incoming_fee }.into());
|
||||
assert_last_event::<T>(Event::NetworkIncomingFeeUpdated {
|
||||
chain_id: chain_id.clone(), incoming_fee,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
@ -221,24 +233,42 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, outgoing_fee)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), outgoing_fee)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkOutgoingFeeUpdated { chain_id, outgoing_fee }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkOutgoingFeeUpdated {
|
||||
chain_id: chain_id.clone(), outgoing_fee,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_avg_block_speed {
|
||||
let avg_block_speed = 420;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), avg_block_speed)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkAvgBlockSpeedUpdated {
|
||||
chain_id: chain_id.clone(), avg_block_speed,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), None);
|
||||
}
|
||||
|
||||
remove_network {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::RemoveOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let maybe_network = create_network::<T>(chain_id, network)?;
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), maybe_network);
|
||||
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id)
|
||||
let _ = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
|
||||
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id.clone()]);
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkRemoved { chain_id }.into());
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), None);
|
||||
assert_last_event::<T>(Event::NetworkRemoved {
|
||||
chain_id: chain_id.clone(),
|
||||
}.into());
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
|
||||
assert!(GhostNetworks::<T>::network_indexes().is_empty());
|
||||
}
|
||||
|
||||
|
||||
@ -17,15 +17,11 @@ use sp_runtime::{
|
||||
};
|
||||
use sp_std::{convert::TryInto, prelude::*};
|
||||
|
||||
use ghost_traits::networks::{
|
||||
pub use ghost_traits::networks::{
|
||||
NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
|
||||
};
|
||||
|
||||
use ghost_helpers::networks::{
|
||||
MAX_ENDPOINT_LEN, MAX_GATEKEEPER_LEN, MAX_SELECTOR_LEN,
|
||||
NetworkData, NetworkType, NetworkCurve, NetworkInitiation,
|
||||
};
|
||||
|
||||
pub mod migrations;
|
||||
mod weights;
|
||||
|
||||
pub use crate::weights::WeightInfo;
|
||||
@ -38,9 +34,39 @@ mod mock;
|
||||
#[cfg(all(feature = "std", test))]
|
||||
mod tests;
|
||||
|
||||
const LOG_TARGET: &str = "runtime::ghost-networks";
|
||||
|
||||
pub type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub enum NetworkType {
|
||||
Evm = 0,
|
||||
Utxo = 1,
|
||||
Undefined = 2,
|
||||
}
|
||||
|
||||
impl Default for NetworkType {
|
||||
fn default() -> Self {
|
||||
NetworkType::Evm
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct NetworkData {
|
||||
pub chain_name: Vec<u8>,
|
||||
pub default_endpoints: Vec<Vec<u8>>,
|
||||
pub gatekeeper: Vec<u8>,
|
||||
pub topic_name: Vec<u8>,
|
||||
pub network_type: NetworkType,
|
||||
pub finality_delay: u64,
|
||||
pub rate_limit_delay: u64,
|
||||
pub block_distance: u64,
|
||||
pub avg_block_speed: u64,
|
||||
pub incoming_fee: u32,
|
||||
pub outgoing_fee: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct BridgeAdjustment<Balance> {
|
||||
pub bridged_out: Balance,
|
||||
@ -148,34 +174,71 @@ pub mod module {
|
||||
WrongTopicName,
|
||||
/// Could not store networks into bounded vector.
|
||||
TooManyNetworks,
|
||||
/// Selector length should always be 4.
|
||||
InvalidSelectorLen,
|
||||
/// Index is out of possible range.
|
||||
IndexOutOfBounds,
|
||||
/// No more default endpoints available to store.
|
||||
TooManyEndpoints,
|
||||
/// Network type does not exist under specified curve.
|
||||
NetworkTypeDoesNotExist,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
NetworkRegistered { chain_id: T::NetworkId },
|
||||
NetworkSelectorUpdated { chain_id: T::NetworkId },
|
||||
NetworkEndpointUpdated { index: u32, chain_id: T::NetworkId },
|
||||
NetworkEndpointRemoved { index: u32, chain_id: T::NetworkId },
|
||||
NetworkEndpointAdded { chain_id: T::NetworkId },
|
||||
NetworkFinalityDelayUpdated { chain_id: T::NetworkId, finality_delay: u64 },
|
||||
NetworkRateLimitDelayUpdated { chain_id: T::NetworkId, rate_limit_delay: u64 },
|
||||
NetworkBlockDeviationUpdated { chain_id: T::NetworkId, block_deviation: u64 },
|
||||
NetworkTypeUpdated { chain_id: T::NetworkId, network_type: NetworkType },
|
||||
NetworkCurveUpdated { chain_id: T::NetworkId, network_curve: NetworkCurve },
|
||||
NetworkGatekeeperUpdated { chain_id: T::NetworkId },
|
||||
NetworkIncomingFeeUpdated { chain_id: T::NetworkId, incoming_fee: u32 },
|
||||
NetworkOutgoingFeeUpdated { chain_id: T::NetworkId, outgoing_fee: u32 },
|
||||
NetworkAvgBlockSpeedUpdated { chain_id: T::NetworkId, avg_block_speed: u64 },
|
||||
NetworkRemoved { chain_id: T::NetworkId },
|
||||
NetworkRegistered {
|
||||
chain_id: T::NetworkId,
|
||||
network: NetworkData,
|
||||
},
|
||||
NetworkNameUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
chain_name: Vec<u8>,
|
||||
},
|
||||
NetworkEndpointUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
index: u32,
|
||||
endpoint: Vec<u8>,
|
||||
},
|
||||
NetworkEndpointRemoved {
|
||||
chain_id: T::NetworkId,
|
||||
index: u32,
|
||||
},
|
||||
NetworkEndpointAdded {
|
||||
chain_id: T::NetworkId,
|
||||
endpoint: Vec<u8>,
|
||||
},
|
||||
NetworkFinalityDelayUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
finality_delay: u64,
|
||||
},
|
||||
NetworkRateLimitDelayUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
rate_limit_delay: u64,
|
||||
},
|
||||
NetworkBlockDistanceUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
block_distance: u64,
|
||||
},
|
||||
NetworkTypeUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
network_type: NetworkType,
|
||||
},
|
||||
NetworkGatekeeperUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
gatekeeper: Vec<u8>,
|
||||
},
|
||||
NetworkTopicNameUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
topic_name: Vec<u8>,
|
||||
},
|
||||
NetworkIncomingFeeUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
incoming_fee: u32,
|
||||
},
|
||||
NetworkOutgoingFeeUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
outgoing_fee: u32,
|
||||
},
|
||||
NetworkAvgBlockSpeedUpdated {
|
||||
chain_id: T::NetworkId,
|
||||
avg_block_speed: u64,
|
||||
},
|
||||
NetworkRemoved {
|
||||
chain_id: T::NetworkId,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
@ -192,28 +255,10 @@ pub mod module {
|
||||
pub type NetworkIndexes<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::NetworkId, T::MaxNetworks>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn network_curves)]
|
||||
pub type NetworkCurves<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, NetworkCurve,
|
||||
BoundedBTreeMap<NetworkType, u32, T::MaxNetworks>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn network_curves_vec)]
|
||||
pub type NetworkCurvesVec<T: Config> =
|
||||
StorageValue<_, BoundedVec<NetworkCurve, T::MaxNetworks>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn networks)]
|
||||
pub type Networks<T: Config> = StorageMap<
|
||||
_,
|
||||
Twox64Concat, T::NetworkId,
|
||||
NetworkData,
|
||||
OptionQuery,
|
||||
>;
|
||||
pub type Networks<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::NetworkId, NetworkData, OptionQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn gatekeeper_amount)]
|
||||
@ -222,26 +267,29 @@ pub mod module {
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub networks: Vec<NetworkInitiation<T::NetworkId, BalanceOf<T>>>,
|
||||
pub networks: Vec<(T::NetworkId, Vec<u8>, BalanceOf<T>)>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
Self { networks: sp_std::vec![] }
|
||||
Self { networks: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
self.networks
|
||||
.iter()
|
||||
.for_each(|network| {
|
||||
Pallet::<T>::do_register_network(network.id, network.data.clone())
|
||||
.expect("Error registering network");
|
||||
GatekeeperAmount::<T>::insert(network.id, network.amount);
|
||||
});
|
||||
|
||||
if !self.networks.is_empty() {
|
||||
self.networks
|
||||
.iter()
|
||||
.for_each(|(chain_id, network_metadata, gatekeeper_amount)| {
|
||||
let network = NetworkData::decode(&mut &network_metadata[..])
|
||||
.expect("Error decoding NetworkData");
|
||||
Pallet::<T>::do_register_network(chain_id.clone(), network)
|
||||
.expect("Error registering network");
|
||||
GatekeeperAmount::<T>::insert(chain_id, gatekeeper_amount);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,13 +302,13 @@ pub mod module {
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::register_network(
|
||||
network.gatekeeper.len() as u32,
|
||||
network.selector.len() as u32,
|
||||
network.chain_name.len() as u32,
|
||||
network.default_endpoints
|
||||
.iter()
|
||||
.map(|endpoint| endpoint.len())
|
||||
.max()
|
||||
.unwrap_or_default() as u32,
|
||||
network.default_endpoints.len() as u32,
|
||||
))]
|
||||
pub fn register_network(
|
||||
origin: OriginFor<T>,
|
||||
@ -272,14 +320,16 @@ pub mod module {
|
||||
}
|
||||
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::update_network_selector())]
|
||||
pub fn update_network_selector(
|
||||
#[pallet::weight(T::WeightInfo::update_network_name(
|
||||
chain_name.len() as u32,
|
||||
))]
|
||||
pub fn update_network_name(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
selector: BoundedVec<u8, ConstU32<MAX_SELECTOR_LEN>>,
|
||||
chain_name: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_nework_selector(chain_id, selector)
|
||||
Self::do_update_network_name(chain_id, chain_name)
|
||||
}
|
||||
|
||||
#[pallet::call_index(2)]
|
||||
@ -293,7 +343,7 @@ pub mod module {
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
maybe_index: Option<u32>,
|
||||
maybe_endpoint: Option<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>>,
|
||||
maybe_endpoint: Option<Vec<u8>>,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_network_endpoint(chain_id, maybe_index, maybe_endpoint)
|
||||
@ -322,14 +372,14 @@ pub mod module {
|
||||
}
|
||||
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(T::WeightInfo::update_network_block_deviation())]
|
||||
pub fn update_network_block_deviation(
|
||||
#[pallet::weight(T::WeightInfo::update_network_block_distance())]
|
||||
pub fn update_network_block_distance(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
block_deviation: u64,
|
||||
block_distance: u64,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_network_block_deviation(chain_id, block_deviation)
|
||||
Self::do_update_network_block_distance(chain_id, block_distance)
|
||||
}
|
||||
|
||||
#[pallet::call_index(6)]
|
||||
@ -344,27 +394,27 @@ pub mod module {
|
||||
}
|
||||
|
||||
#[pallet::call_index(7)]
|
||||
#[pallet::weight(T::WeightInfo::update_network_curve())]
|
||||
pub fn update_network_curve(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
network_curve: NetworkCurve,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_network_curve(chain_id, network_curve)
|
||||
}
|
||||
|
||||
#[pallet::call_index(8)]
|
||||
#[pallet::weight(T::WeightInfo::update_network_gatekeeper())]
|
||||
pub fn update_network_gatekeeper(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
gatekeeper: BoundedVec<u8, ConstU32<MAX_GATEKEEPER_LEN>>,
|
||||
gatekeeper: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_network_gatekeeper(chain_id, gatekeeper)
|
||||
}
|
||||
|
||||
#[pallet::call_index(8)]
|
||||
#[pallet::weight(T::WeightInfo::update_network_topic_name())]
|
||||
pub fn update_network_topic_name(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
topic_name: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_network_topic_name(chain_id, topic_name)
|
||||
}
|
||||
|
||||
#[pallet::call_index(9)]
|
||||
#[pallet::weight(T::WeightInfo::update_incoming_network_fee())]
|
||||
pub fn update_incoming_network_fee(
|
||||
@ -388,6 +438,17 @@ pub mod module {
|
||||
}
|
||||
|
||||
#[pallet::call_index(11)]
|
||||
#[pallet::weight(T::WeightInfo::update_avg_block_speed())]
|
||||
pub fn update_avg_block_speed(
|
||||
origin: OriginFor<T>,
|
||||
chain_id: T::NetworkId,
|
||||
avg_block_speed: u64,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin_or_root(origin)?;
|
||||
Self::do_update_avg_block_speed(chain_id, avg_block_speed)
|
||||
}
|
||||
|
||||
#[pallet::call_index(12)]
|
||||
#[pallet::weight(T::WeightInfo::remove_network())]
|
||||
pub fn remove_network(origin: OriginFor<T>, chain_id: T::NetworkId) -> DispatchResult {
|
||||
T::RemoveOrigin::ensure_origin_or_root(origin)?;
|
||||
@ -398,105 +459,53 @@ pub mod module {
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register a new network.
|
||||
pub fn do_register_network(
|
||||
chain_id: T::NetworkId,
|
||||
network: NetworkData,
|
||||
) -> DispatchResult {
|
||||
let network_curve = network.curve;
|
||||
let network_type = network.r#type;
|
||||
pub fn do_register_network(chain_id: T::NetworkId, network: NetworkData) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
ensure!(
|
||||
maybe_network.is_none(),
|
||||
Error::<T>::NetworkAlreadyRegistered
|
||||
);
|
||||
*maybe_network = Some(network.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
ensure!(
|
||||
!Networks::<T>::contains_key(&chain_id),
|
||||
Error::<T>::NetworkAlreadyRegistered,
|
||||
);
|
||||
|
||||
let mut modified_indexes = NetworkIndexes::<T>::get();
|
||||
modified_indexes.try_push(chain_id.clone())
|
||||
.map_err(|_| Error::<T>::TooManyNetworks)?;
|
||||
|
||||
let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);
|
||||
if let Some(counter) = modified_curves_map.get_mut(&network_type) {
|
||||
*counter = counter.saturating_add(1);
|
||||
} else {
|
||||
modified_curves_map.try_insert(network_type, 1u32)
|
||||
NetworkIndexes::<T>::try_mutate(|ids| -> DispatchResult {
|
||||
ids.try_push(chain_id)
|
||||
.map_err(|_| Error::<T>::TooManyNetworks)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
|
||||
if !modified_curves_vec.contains(&network_curve) {
|
||||
modified_curves_vec.try_push(network_curve)
|
||||
.map_err(|_| Error::<T>::TooManyNetworks)?;
|
||||
}
|
||||
|
||||
Networks::<T>::insert(&chain_id, network);
|
||||
NetworkIndexes::<T>::put(modified_indexes);
|
||||
NetworkCurves::<T>::insert(network_curve, modified_curves_map);
|
||||
NetworkCurvesVec::<T>::put(modified_curves_vec);
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkRegistered { chain_id });
|
||||
Self::deposit_event(Event::<T>::NetworkRegistered { chain_id, network });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove existent network.
|
||||
pub fn do_remove_network(chain_id: T::NetworkId) -> DispatchResult {
|
||||
let network = Networks::<T>::get(&chain_id)
|
||||
.ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
|
||||
let network_curve = network.curve;
|
||||
let network_type = network.r#type;
|
||||
|
||||
let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);
|
||||
|
||||
let counter = modified_curves_map
|
||||
.get_mut(&network_type)
|
||||
.ok_or(Error::<T>::NetworkTypeDoesNotExist)?;
|
||||
|
||||
*counter = counter.saturating_sub(1);
|
||||
|
||||
let mut should_remove_curve_from_vec = false;
|
||||
if *counter == 0 {
|
||||
modified_curves_map.remove(&network_type);
|
||||
if modified_curves_map.is_empty() {
|
||||
should_remove_curve_from_vec = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut modified_indexes = NetworkIndexes::<T>::get();
|
||||
modified_indexes.retain(|id| id != &chain_id);
|
||||
|
||||
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
|
||||
if should_remove_curve_from_vec {
|
||||
modified_curves_vec.retain(|curve| curve != &network_curve);
|
||||
}
|
||||
|
||||
Networks::<T>::remove(&chain_id);
|
||||
NetworkIndexes::<T>::put(modified_indexes);
|
||||
NetworkCurvesVec::<T>::put(modified_curves_vec);
|
||||
|
||||
if modified_curves_map.is_empty() {
|
||||
NetworkCurves::<T>::remove(network_curve);
|
||||
} else {
|
||||
NetworkCurves::<T>::insert(network_curve, modified_curves_map);
|
||||
}
|
||||
NetworkIndexes::<T>::mutate(|ids| ids.retain(|id| id != &chain_id));
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
*maybe_network = None;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update existent network name.
|
||||
pub fn do_update_nework_selector(
|
||||
chain_id: T::NetworkId,
|
||||
selector: BoundedVec<u8, ConstU32<MAX_SELECTOR_LEN>>,
|
||||
) -> DispatchResult {
|
||||
pub fn do_update_network_name(chain_id: T::NetworkId, chain_name: Vec<u8>) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
ensure!(selector.len() == 4, Error::<T>::InvalidSelectorLen);
|
||||
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
network.selector = selector;
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.chain_name = chain_name.clone();
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkSelectorUpdated { chain_id });
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkNameUpdated {
|
||||
chain_id,
|
||||
chain_name,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -504,31 +513,38 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn do_update_network_endpoint(
|
||||
chain_id: T::NetworkId,
|
||||
maybe_index: Option<u32>,
|
||||
maybe_endpoint: Option<BoundedVec<u8, ConstU32<MAX_ENDPOINT_LEN>>>,
|
||||
maybe_endpoint: Option<Vec<u8>>,
|
||||
) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let updated_network = maybe_network.as_mut().unwrap();
|
||||
match (maybe_index, maybe_endpoint) {
|
||||
(Some(index), Some(endpoint)) => {
|
||||
let idx = index as usize;
|
||||
ensure!(idx < network.default_endpoints.len(), Error::<T>::IndexOutOfBounds);
|
||||
let _ = core::mem::replace(&mut network.default_endpoints[idx], endpoint);
|
||||
Self::deposit_event(Event::<T>::NetworkEndpointUpdated { chain_id, index });
|
||||
if let Some(previous_endpoint) =
|
||||
updated_network.default_endpoints.get_mut(index as usize)
|
||||
{
|
||||
*previous_endpoint = endpoint.clone();
|
||||
Self::deposit_event(Event::<T>::NetworkEndpointUpdated {
|
||||
chain_id,
|
||||
index,
|
||||
endpoint,
|
||||
});
|
||||
}
|
||||
}
|
||||
(None, Some(endpoint)) => {
|
||||
network.default_endpoints.try_push(endpoint).map_err(|_| Error::<T>::TooManyEndpoints)?;
|
||||
Self::deposit_event(Event::<T>::NetworkEndpointAdded { chain_id });
|
||||
updated_network.default_endpoints.push(endpoint.clone());
|
||||
Self::deposit_event(Event::<T>::NetworkEndpointAdded { chain_id, endpoint });
|
||||
}
|
||||
(Some(index), None) => {
|
||||
let idx = index as usize;
|
||||
ensure!(idx < network.default_endpoints.len(), Error::<T>::IndexOutOfBounds);
|
||||
network.default_endpoints.remove(idx);
|
||||
updated_network.default_endpoints.remove(index as usize);
|
||||
Self::deposit_event(Event::<T>::NetworkEndpointRemoved { chain_id, index });
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
*maybe_network = Some(updated_network.clone());
|
||||
Ok(())
|
||||
})
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update existent network default finality delay.
|
||||
@ -569,22 +585,22 @@ impl<T: Config> Pallet<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update existent network default block deviation between blocks.
|
||||
pub fn do_update_network_block_deviation(
|
||||
/// Update existent network default max distance between blocks.
|
||||
pub fn do_update_network_block_distance(
|
||||
chain_id: T::NetworkId,
|
||||
block_deviation: u64,
|
||||
block_distance: u64,
|
||||
) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
network.block_deviation = block_deviation;
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.block_distance = block_distance;
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkBlockDeviationUpdated {
|
||||
Self::deposit_event(Event::<T>::NetworkBlockDistanceUpdated {
|
||||
chain_id,
|
||||
block_deviation,
|
||||
block_distance,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -594,8 +610,10 @@ impl<T: Config> Pallet<T> {
|
||||
network_type: NetworkType,
|
||||
) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
network.r#type = network_type;
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.network_type = network_type;
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::NetworkTypeUpdated {
|
||||
@ -605,86 +623,49 @@ impl<T: Config> Pallet<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update underlying network curve.
|
||||
pub fn do_update_network_curve(
|
||||
chain_id: T::NetworkId,
|
||||
network_curve: NetworkCurve,
|
||||
) -> DispatchResult {
|
||||
let mut network = Networks::<T>::get(&chain_id)
|
||||
.ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
|
||||
let prev_curve = network.curve;
|
||||
let network_type = network.r#type;
|
||||
|
||||
if prev_curve == network_curve {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut modified_prev_map = NetworkCurves::<T>::get(prev_curve);
|
||||
let counter = modified_prev_map
|
||||
.get_mut(&network_type)
|
||||
.ok_or(Error::<T>::NetworkTypeDoesNotExist)?;
|
||||
|
||||
*counter = counter.saturating_sub(1);
|
||||
|
||||
let mut should_remove_prev_from_vec = false;
|
||||
if *counter == 0 {
|
||||
modified_prev_map.remove(&network_type);
|
||||
if modified_prev_map.is_empty() {
|
||||
should_remove_prev_from_vec = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut modified_curr_map = NetworkCurves::<T>::get(network_curve);
|
||||
if let Some(counter) = modified_curr_map.get_mut(&network_type) {
|
||||
*counter = counter.saturating_add(1);
|
||||
} else {
|
||||
modified_curr_map.try_insert(network_type, 1u32)
|
||||
.map_err(|_| Error::<T>::TooManyNetworks)?;
|
||||
}
|
||||
|
||||
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
|
||||
|
||||
if should_remove_prev_from_vec {
|
||||
modified_curves_vec.retain(|curve| curve != &prev_curve);
|
||||
}
|
||||
|
||||
if !modified_curves_vec.contains(&network_curve) {
|
||||
modified_curves_vec.try_push(network_curve)
|
||||
.map_err(|_| Error::<T>::TooManyNetworks)?;
|
||||
}
|
||||
|
||||
network.curve = network_curve;
|
||||
|
||||
Networks::<T>::insert(&chain_id, network);
|
||||
NetworkCurves::<T>::insert(network_curve, modified_curr_map);
|
||||
NetworkCurvesVec::<T>::put(modified_curves_vec);
|
||||
|
||||
if modified_prev_map.is_empty() {
|
||||
NetworkCurves::<T>::remove(prev_curve);
|
||||
} else {
|
||||
NetworkCurves::<T>::insert(prev_curve, modified_prev_map);
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::<T>::NetworkCurveUpdated {
|
||||
chain_id,
|
||||
network_curve,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update existent network gatekeeper.
|
||||
pub fn do_update_network_gatekeeper(
|
||||
chain_id: T::NetworkId,
|
||||
gatekeeper: BoundedVec<u8, ConstU32<MAX_GATEKEEPER_LEN>>,
|
||||
gatekeeper: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
ensure!(
|
||||
gatekeeper.len() == 42 && gatekeeper[0] == 48 && gatekeeper[1] == 120,
|
||||
Error::<T>::WrongGatekeeperAddress
|
||||
);
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
|
||||
network.gatekeeper = gatekeeper;
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.gatekeeper = gatekeeper.clone();
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated { chain_id });
|
||||
Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated {
|
||||
chain_id,
|
||||
gatekeeper,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update existent network gatekeeper's topic name.
|
||||
pub fn do_update_network_topic_name(
|
||||
chain_id: T::NetworkId,
|
||||
topic_name: Vec<u8>,
|
||||
) -> DispatchResult {
|
||||
ensure!(
|
||||
topic_name.len() == 66 && topic_name[0] == 48 && topic_name[1] == 120,
|
||||
Error::<T>::WrongTopicName
|
||||
);
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.topic_name = topic_name.clone();
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::NetworkTopicNameUpdated {
|
||||
chain_id,
|
||||
topic_name,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -723,12 +704,28 @@ impl<T: Config> Pallet<T> {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn do_update_avg_block_speed(
|
||||
chain_id: T::NetworkId,
|
||||
avg_block_speed: u64,
|
||||
) -> DispatchResult {
|
||||
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
|
||||
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
|
||||
let net = maybe_network.as_mut().unwrap();
|
||||
net.avg_block_speed = avg_block_speed;
|
||||
*maybe_network = Some(net.clone());
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::<T>::NetworkAvgBlockSpeedUpdated {
|
||||
chain_id,
|
||||
avg_block_speed,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
|
||||
type NetworkId = T::NetworkId;
|
||||
type NetworkType = NetworkType;
|
||||
type NetworkCurve = NetworkCurve;
|
||||
}
|
||||
|
||||
impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
|
||||
@ -742,10 +739,6 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
|
||||
Networks::<T>::contains_key(n)
|
||||
}
|
||||
|
||||
fn curve_exists(curve: &Self::NetworkCurve) -> bool {
|
||||
NetworkCurves::<T>::contains_key(curve)
|
||||
}
|
||||
|
||||
fn get(n: &Self::NetworkId) -> Option<NetworkData> {
|
||||
Networks::<T>::get(n)
|
||||
}
|
||||
@ -770,18 +763,6 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
|
||||
fn iter_indexes() -> impl Iterator<Item = Self::NetworkId> {
|
||||
NetworkIndexes::<T>::get().into_iter()
|
||||
}
|
||||
|
||||
fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve> {
|
||||
NetworkCurvesVec::<T>::get().into_iter()
|
||||
}
|
||||
|
||||
fn iter_types_by_curve(
|
||||
curve: &Self::NetworkCurve
|
||||
) -> impl Iterator<Item = Self::NetworkType> {
|
||||
NetworkCurves::<T>::get(curve)
|
||||
.into_iter()
|
||||
.map(|(network_type, _)| network_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
|
||||
|
||||
9
pallets/networks/src/migrations/mod.rs
Normal file
9
pallets/networks/src/migrations/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub mod v1;
|
||||
|
||||
pub type MigrateV0ToV1<T> = frame_support::migrations::VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
v1::StoreNetworkIdsIntoBoundedVec<T>,
|
||||
crate::Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
59
pallets/networks/src/migrations/v1.rs
Normal file
59
pallets/networks/src/migrations/v1.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use frame_support::{
|
||||
migration::clear_storage_prefix,
|
||||
traits::{Get, PalletInfoAccess, UncheckedOnRuntimeUpgrade},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
use crate::{BoundedVec, Config, NetworkIndexes, Networks, Pallet, Vec, LOG_TARGET};
|
||||
|
||||
pub struct StoreNetworkIdsIntoBoundedVec<T>(PhantomData<T>);
|
||||
impl<T: Config> UncheckedOnRuntimeUpgrade for StoreNetworkIdsIntoBoundedVec<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
|
||||
let network_ids: Vec<T::NetworkId> = Networks::<T>::iter_keys().collect();
|
||||
let networks_count = network_ids.len();
|
||||
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads(networks_count as u64));
|
||||
|
||||
let clear_results = clear_storage_prefix(
|
||||
Pallet::<T>::name().as_bytes(),
|
||||
"NullifyNeeded".as_bytes(),
|
||||
&[],
|
||||
Some(1),
|
||||
None,
|
||||
);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"⛓️ NullifyNeeded storage succesfully removed: Loops (reads): {}, Unique removed (writes): {}",
|
||||
clear_results.loops,
|
||||
clear_results.unique,
|
||||
);
|
||||
|
||||
weight = weight.saturating_add(T::DbWeight::get().reads(clear_results.loops as u64));
|
||||
weight = weight.saturating_add(T::DbWeight::get().writes(clear_results.unique as u64));
|
||||
|
||||
let writes = BoundedVec::<T::NetworkId, T::MaxNetworks>::try_from(network_ids)
|
||||
.inspect_err(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"⛓️ Network ids to bounded_vec migration failed: {:?}",
|
||||
err,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.map(|bounded_networks| {
|
||||
NetworkIndexes::<T>::put(bounded_networks);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"⛓️ Network ids to bounded_vec migration success: {} networks moved",
|
||||
networks_count,
|
||||
);
|
||||
1u64
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
weight.saturating_add(T::DbWeight::get().writes(writes))
|
||||
}
|
||||
}
|
||||
@ -7,34 +7,30 @@ use mock::{
|
||||
use pallet_staking::EraPayout;
|
||||
use sp_runtime::DispatchError;
|
||||
|
||||
const CURRENT_CURVE: NetworkCurve = NetworkCurve::Secp256k1;
|
||||
|
||||
fn prepare_network_data() -> (u32, NetworkData) {
|
||||
(
|
||||
1u32,
|
||||
ghost_helpers::networks::NetworkDataBuilder::default()
|
||||
.with_gatekeeper(
|
||||
hex::decode("02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9")
|
||||
.expect("Invalid public key"),
|
||||
)
|
||||
.with_default_endpoints(vec![
|
||||
b"https://some-mainnet.infura.io/v3/demo-key".to_vec(),
|
||||
b"https://rpc4ever.info/api".to_vec(),
|
||||
])
|
||||
.with_selector(vec![1u8; 4])
|
||||
.with_network_type(NetworkType::Evm)
|
||||
.with_network_curve(CURRENT_CURVE)
|
||||
.with_rate_limit_delay(6)
|
||||
.with_finality_delay(69)
|
||||
.with_block_deviation(420)
|
||||
.with_incoming_fee(0)
|
||||
.with_outgoing_fee(0)
|
||||
.build(),
|
||||
NetworkData {
|
||||
chain_name: "Ethereum".into(),
|
||||
default_endpoints: vec![
|
||||
"https:://some-endpoint.my-server.com/v1/my-super-secret-key".into(),
|
||||
"https:://another-endpoint.my-server.com/v1/my-super-secret-key".into(),
|
||||
],
|
||||
finality_delay: 69,
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
avg_block_speed: 12_000,
|
||||
network_type: NetworkType::Evm,
|
||||
gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
|
||||
topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678"
|
||||
.to_vec(),
|
||||
incoming_fee: 0,
|
||||
outgoing_fee: 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn register_and_check_network(chain_id: u32, network: NetworkData) {
|
||||
let network_curve = network.curve;
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
@ -42,8 +38,6 @@ fn register_and_check_network(chain_id: u32, network: NetworkData) {
|
||||
));
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
|
||||
assert!(NetworkCurves::<Test>::contains_key(&network_curve));
|
||||
assert!(NetworkCurvesVec::<Test>::get().contains(&network_curve));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -52,21 +46,19 @@ fn could_add_network_from_authority() {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
network.clone(),
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkRegistered { chain_id },
|
||||
crate::Event::NetworkRegistered {
|
||||
chain_id,
|
||||
network: network.clone(),
|
||||
},
|
||||
));
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
|
||||
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
});
|
||||
}
|
||||
|
||||
@ -76,9 +68,6 @@ fn could_not_add_network_from_random_account() {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
|
||||
assert_err!(
|
||||
GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RandomAccount::get()),
|
||||
@ -105,29 +94,28 @@ fn could_not_add_network_from_random_account() {
|
||||
);
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_update_network_selector_from_authority_account() {
|
||||
fn could_update_network_name_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_selector =
|
||||
BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(vec![69, 69, 69, 69])
|
||||
.unwrap();
|
||||
let new_name = b"Polygon".to_vec();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_selector(
|
||||
assert_ok!(GhostNetworks::update_network_name(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
new_selector.clone()
|
||||
new_name.clone()
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkSelectorUpdated { chain_id },
|
||||
crate::Event::NetworkNameUpdated {
|
||||
chain_id,
|
||||
chain_name: new_name.clone(),
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.selector = new_selector;
|
||||
final_network.chain_name = new_name;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
|
||||
assert_ne!(network, final_network);
|
||||
});
|
||||
@ -136,10 +124,8 @@ fn could_update_network_selector_from_authority_account() {
|
||||
#[test]
|
||||
fn could_add_network_endpoint_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let raw_endpoint =
|
||||
BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let raw_endpoint: Vec<u8> =
|
||||
"https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_endpoint(
|
||||
@ -149,7 +135,10 @@ fn could_add_network_endpoint_from_authority_account() {
|
||||
Some(raw_endpoint.clone()),
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkEndpointAdded { chain_id },
|
||||
crate::Event::NetworkEndpointAdded {
|
||||
chain_id,
|
||||
endpoint: raw_endpoint.clone(),
|
||||
},
|
||||
));
|
||||
let current_network = Networks::<Test>::get(chain_id).unwrap();
|
||||
assert_eq!(
|
||||
@ -205,10 +194,8 @@ fn could_remove_network_endpoint_from_authority_account() {
|
||||
fn could_update_network_endpoint_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let index_to_update = 0u32;
|
||||
let raw_endpoint =
|
||||
BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let raw_endpoint: Vec<u8> =
|
||||
"https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_endpoint(
|
||||
@ -218,7 +205,11 @@ fn could_update_network_endpoint_from_authority_account() {
|
||||
Some(raw_endpoint.clone()),
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkEndpointUpdated { chain_id, index: index_to_update },
|
||||
crate::Event::NetworkEndpointUpdated {
|
||||
chain_id,
|
||||
index: index_to_update,
|
||||
endpoint: raw_endpoint.clone(),
|
||||
},
|
||||
));
|
||||
let previous_endpoints_len = network.default_endpoints.len();
|
||||
let mut final_network = network.clone();
|
||||
@ -294,24 +285,24 @@ fn could_update_network_rate_limit_delay_from_authority_account() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_update_network_block_deviation_from_authority_account() {
|
||||
fn could_update_network_block_distance_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_block_deviation = 1337;
|
||||
let new_block_distance = 1337;
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_block_deviation(
|
||||
assert_ok!(GhostNetworks::update_network_block_distance(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
new_block_deviation
|
||||
new_block_distance
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkBlockDeviationUpdated {
|
||||
crate::Event::NetworkBlockDistanceUpdated {
|
||||
chain_id,
|
||||
block_deviation: new_block_deviation,
|
||||
block_distance: new_block_distance,
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.block_deviation = new_block_deviation;
|
||||
final_network.block_distance = new_block_distance;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
|
||||
assert_ne!(network, final_network);
|
||||
});
|
||||
@ -335,43 +326,7 @@ fn could_update_network_type_from_authority_account() {
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.r#type = new_type;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
|
||||
assert_ne!(network, final_network);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_update_network_curve_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_curve = NetworkCurve::Ed25519;
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&new_curve));
|
||||
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&new_curve));
|
||||
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
|
||||
assert_ok!(GhostNetworks::update_network_curve(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
new_curve.clone()
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkCurveUpdated {
|
||||
chain_id,
|
||||
network_curve: new_curve.clone(),
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.r#curve = new_curve;
|
||||
|
||||
assert!(NetworkCurves::<Test>::contains_key(&new_curve));
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(NetworkCurvesVec::<Test>::get().contains(&new_curve));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
|
||||
final_network.network_type = new_type;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
|
||||
assert_ne!(network, final_network);
|
||||
});
|
||||
@ -380,10 +335,7 @@ fn could_update_network_curve_from_authority_account() {
|
||||
#[test]
|
||||
fn could_update_network_gatekeeper_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_gatekeeper = BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
|
||||
hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
|
||||
.expect("Invalid public key"),
|
||||
).unwrap();
|
||||
let new_gatekeeper = b"0x9876543219876543219876543219876543219876".to_vec();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_gatekeeper(
|
||||
@ -392,7 +344,10 @@ fn could_update_network_gatekeeper_from_authority_account() {
|
||||
new_gatekeeper.clone()
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkGatekeeperUpdated { chain_id },
|
||||
crate::Event::NetworkGatekeeperUpdated {
|
||||
chain_id,
|
||||
gatekeeper: new_gatekeeper.clone(),
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.gatekeeper = new_gatekeeper;
|
||||
@ -401,6 +356,31 @@ fn could_update_network_gatekeeper_from_authority_account() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_update_network_topic_name_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_topic_name =
|
||||
b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_ok!(GhostNetworks::update_network_topic_name(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
new_topic_name.clone()
|
||||
));
|
||||
System::assert_last_event(RuntimeEvent::GhostNetworks(
|
||||
crate::Event::NetworkTopicNameUpdated {
|
||||
chain_id,
|
||||
topic_name: new_topic_name.clone(),
|
||||
},
|
||||
));
|
||||
let mut final_network = network.clone();
|
||||
final_network.topic_name = new_topic_name;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
|
||||
assert_ne!(network, final_network);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_update_incoming_network_fee_from_authority_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
@ -449,13 +429,45 @@ fn could_update_outgoing_network_fee_from_authority_account() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_network_name_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_name(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
"Binance".into()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_name(
|
||||
RuntimeOrigin::signed(RemoverAccount::get()),
|
||||
chain_id,
|
||||
"Binance".into()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_name(
|
||||
RuntimeOrigin::signed(RandomAccount::get()),
|
||||
chain_id,
|
||||
"Binance".into()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_network_endpoint_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let index_to_update = 0u32;
|
||||
let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let raw_endpoint: Vec<u8> =
|
||||
"https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
@ -492,9 +504,8 @@ fn could_not_update_network_endpoint_from_random_account() {
|
||||
#[test]
|
||||
fn could_not_add_network_endpoint_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let raw_endpoint: Vec<u8> =
|
||||
"https:://new-endpoint.my-server.com/v1/my-super-secret-key".into();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
@ -637,29 +648,29 @@ fn could_not_update_network_rate_limit_delay_from_random_account() {
|
||||
fn could_not_update_network_release_delay_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
let block_deviation = 1337;
|
||||
let block_distance = 1337;
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_block_deviation(
|
||||
GhostNetworks::update_network_block_distance(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
block_deviation
|
||||
block_distance
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_block_deviation(
|
||||
GhostNetworks::update_network_block_distance(
|
||||
RuntimeOrigin::signed(RemoverAccount::get()),
|
||||
chain_id,
|
||||
block_deviation
|
||||
block_distance
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_block_deviation(
|
||||
GhostNetworks::update_network_block_distance(
|
||||
RuntimeOrigin::signed(RandomAccount::get()),
|
||||
chain_id,
|
||||
block_deviation
|
||||
block_distance
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
@ -701,31 +712,31 @@ fn could_not_update_network_type_from_random_account() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_network_curve_from_random_account() {
|
||||
fn could_not_update_network_gatekeeper_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_curve(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
NetworkCurve::Ed25519,
|
||||
b"0x9876543219876543219876543219876543219876".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_curve(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
RuntimeOrigin::signed(RemoverAccount::get()),
|
||||
chain_id,
|
||||
NetworkCurve::Ed25519,
|
||||
b"0x9876543219876543219876543219876543219876".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_curve(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
RuntimeOrigin::signed(RandomAccount::get()),
|
||||
chain_id,
|
||||
NetworkCurve::Ed25519,
|
||||
b"0x9876543219876543219876543219876543219876".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
@ -734,35 +745,31 @@ fn could_not_update_network_curve_from_random_account() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_network_gatekeeper_from_random_account() {
|
||||
fn could_not_update_network_topic_name_from_random_account() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_gatekeeper = BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
|
||||
hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
|
||||
.expect("Invalid public key"),
|
||||
).unwrap();
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
register_and_check_network(chain_id, network.clone());
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
GhostNetworks::update_network_topic_name(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
new_gatekeeper.clone(),
|
||||
b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
GhostNetworks::update_network_topic_name(
|
||||
RuntimeOrigin::signed(RemoverAccount::get()),
|
||||
chain_id,
|
||||
new_gatekeeper.clone(),
|
||||
b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
GhostNetworks::update_network_topic_name(
|
||||
RuntimeOrigin::signed(RandomAccount::get()),
|
||||
chain_id,
|
||||
new_gatekeeper,
|
||||
b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
|
||||
),
|
||||
DispatchError::BadOrigin
|
||||
);
|
||||
@ -836,12 +843,26 @@ fn could_not_update_network_outgoing_fee_from_random_account() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_name_for_non_existent_network() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_name(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
"Binance".into()
|
||||
),
|
||||
crate::Error::<Test>::NetworkDoesNotExist
|
||||
);
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_endpoint_for_non_existent_network() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
@ -849,7 +870,7 @@ fn could_not_update_endpoint_for_non_existent_network() {
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
Some(0u32),
|
||||
Some(raw_endpoint),
|
||||
Some("https:://new-endpoint.my-server.com/v1/my-super-secret-key".into()),
|
||||
),
|
||||
crate::Error::<Test>::NetworkDoesNotExist
|
||||
);
|
||||
@ -860,9 +881,6 @@ fn could_not_update_endpoint_for_non_existent_network() {
|
||||
#[test]
|
||||
fn could_not_add_endpoint_for_non_existent_network() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let raw_endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(
|
||||
b"https:://new-endpoint.my-server.com/v1/my-super-secret-key".to_vec(),
|
||||
).unwrap();
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
@ -870,7 +888,7 @@ fn could_not_add_endpoint_for_non_existent_network() {
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
None,
|
||||
Some(raw_endpoint),
|
||||
Some("https:://new-endpoint.my-server.com/v1/my-super-secret-key".into()),
|
||||
),
|
||||
crate::Error::<Test>::NetworkDoesNotExist
|
||||
);
|
||||
@ -936,7 +954,7 @@ fn could_not_update_release_delay_for_non_existent_network() {
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_block_deviation(
|
||||
GhostNetworks::update_network_block_distance(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
1337
|
||||
@ -965,15 +983,15 @@ fn could_not_update_type_for_non_existent_network() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_curve_for_non_existent_network() {
|
||||
fn could_not_update_gatekeeper_for_non_existent_network() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_curve(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
NetworkCurve::Ed25519,
|
||||
b"0x9876543219876543219876543219876543219876".to_vec()
|
||||
),
|
||||
crate::Error::<Test>::NetworkDoesNotExist
|
||||
);
|
||||
@ -982,19 +1000,15 @@ fn could_not_update_curve_for_non_existent_network() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn could_not_update_gatekeeper_for_non_existent_network() {
|
||||
fn could_not_update_topic_name_for_non_existent_network() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let new_gatekeeper = BoundedVec::<u8, ConstU32<MAX_GATEKEEPER_LEN>>::try_from(
|
||||
hex::decode("04b2c7e3d9f9f8a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0")
|
||||
.expect("Invalid public key"),
|
||||
).unwrap();
|
||||
let chain_id: u32 = 1;
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_err!(
|
||||
GhostNetworks::update_network_gatekeeper(
|
||||
GhostNetworks::update_network_topic_name(
|
||||
RuntimeOrigin::signed(UpdaterAccount::get()),
|
||||
chain_id,
|
||||
new_gatekeeper,
|
||||
b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec()
|
||||
),
|
||||
crate::Error::<Test>::NetworkDoesNotExist
|
||||
);
|
||||
@ -1047,8 +1061,6 @@ fn could_remove_network_from_authority_account() {
|
||||
));
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1074,8 +1086,6 @@ fn could_not_remove_network_from_random_account() {
|
||||
);
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
|
||||
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1090,8 +1100,6 @@ fn could_not_remove_non_existent_network() {
|
||||
);
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
|
||||
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1764,6 +1772,52 @@ fn check_bridged_inflation_curve_for_big_commissions() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_from_v0_to_v1_works() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
let other_chain_id = chain_id.saturating_add(1);
|
||||
|
||||
let removed_pallet_name = <Pallet<Test> as PalletInfoAccess>::name();
|
||||
let removed_storage = "NullifyNeeded";
|
||||
|
||||
let key = [
|
||||
sp_io::hashing::twox_128(removed_pallet_name.as_bytes()).to_vec(),
|
||||
sp_io::hashing::twox_128(removed_storage.as_bytes()).to_vec(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
frame_support::storage::unhashed::put_raw(&key, &true.encode());
|
||||
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
network.clone(),
|
||||
));
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
other_chain_id,
|
||||
network.clone(),
|
||||
));
|
||||
NetworkIndexes::<Test>::kill();
|
||||
|
||||
assert!(frame_support::storage::unhashed::exists(&key));
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone()));
|
||||
assert_eq!(Networks::<Test>::get(other_chain_id), Some(network));
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
|
||||
type Migrate = crate::migrations::MigrateV0ToV1<Test>;
|
||||
<Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();
|
||||
|
||||
assert!(!frame_support::storage::unhashed::exists(&key));
|
||||
assert_eq!(
|
||||
NetworkIndexes::<Test>::get(),
|
||||
vec![chain_id, other_chain_id]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_on_max_networks_overflow() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
@ -1789,3 +1843,34 @@ fn error_on_max_networks_overflow() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_from_v0_to_v1_does_not_run_twice() {
|
||||
ExtBuilder::build().execute_with(|| {
|
||||
StorageVersion::new(1).put::<GhostNetworks>();
|
||||
|
||||
let (chain_id, network) = prepare_network_data();
|
||||
let other_chain_id = chain_id.saturating_add(1);
|
||||
|
||||
assert_eq!(Networks::<Test>::get(chain_id), None);
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
chain_id,
|
||||
network.clone(),
|
||||
));
|
||||
assert_ok!(GhostNetworks::register_network(
|
||||
RuntimeOrigin::signed(RegistererAccount::get()),
|
||||
other_chain_id,
|
||||
network.clone(),
|
||||
));
|
||||
NetworkIndexes::<Test>::kill();
|
||||
|
||||
assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone()));
|
||||
assert_eq!(Networks::<Test>::get(other_chain_id), Some(network));
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
|
||||
type Migrate = crate::migrations::MigrateV0ToV1<Test>;
|
||||
<Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();
|
||||
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{
|
||||
traits::Get,
|
||||
traits::Get,
|
||||
weights::{
|
||||
Weight, constants::RocksDbWeight
|
||||
}
|
||||
@ -49,13 +49,12 @@ use sp_std::marker::PhantomData;
|
||||
/// Weight functions needed for ghost_networks
|
||||
pub trait WeightInfo {
|
||||
fn register_network(i: u32, j: u32, k: u32, ) -> Weight;
|
||||
fn update_network_selector() -> Weight;
|
||||
fn update_network_name(n: u32, ) -> Weight;
|
||||
fn update_network_endpoint(n: u32, ) -> Weight;
|
||||
fn update_network_finality_delay() -> Weight;
|
||||
fn update_network_rate_limit_delay() -> Weight;
|
||||
fn update_network_block_deviation() -> Weight;
|
||||
fn update_network_block_distance() -> Weight;
|
||||
fn update_network_type() -> Weight;
|
||||
fn update_network_curve() -> Weight;
|
||||
fn update_network_gatekeeper() -> Weight;
|
||||
fn update_network_topic_name() -> Weight;
|
||||
fn update_incoming_network_fee() -> Weight;
|
||||
@ -89,7 +88,7 @@ impl WeightInfo for () {
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `n` is `[1, 20]`.
|
||||
fn update_network_selector() -> Weight {
|
||||
fn update_network_name(_n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `339`
|
||||
// Estimated: `3804`
|
||||
@ -140,7 +139,7 @@ impl WeightInfo for () {
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_block_deviation() -> Weight {
|
||||
fn update_network_block_distance() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `339`
|
||||
// Estimated: `3804`
|
||||
@ -162,9 +161,6 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
fn update_network_curve() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_gatekeeper() -> Weight {
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools"
|
||||
version = "25.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of FRAME nomination pools pallet from Parity"
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { workspace = true, features = ["derive"] }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
|
||||
# FRAME
|
||||
frame-support = { workspace = true, default-features = false }
|
||||
frame-system = { workspace = true, default-features = false }
|
||||
sp-runtime = { workspace = true, default-features = false }
|
||||
sp-std = { workspace = true, default-features = false }
|
||||
sp-staking = { workspace = true, default-features = false }
|
||||
sp-core = { workspace = true, default-features = false }
|
||||
sp-io = { workspace = true, default-features = false }
|
||||
log = { workspace = true }
|
||||
|
||||
# Optional: use for testing and/or fuzzing
|
||||
pallet-balances = { workspace = true, optional = true, default-features = false }
|
||||
sp-tracing = { workspace = true, optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { workspace = true }
|
||||
sp-tracing = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
fuzzing = ["pallet-balances", "sp-tracing"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"pallet-balances?/std",
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
"sp-tracing?/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-balances?/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"pallet-balances?/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
@ -1,74 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-benchmarking"
|
||||
version = "26.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of FRAME nomination pools pallet benchmarking"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { workspace = true, default-features = false, features = ["derive"] }
|
||||
scale-info = { workspace = true, default-features = false, features = ["derive"] }
|
||||
|
||||
# FRAME
|
||||
frame-benchmarking = { workspace = true, default-features = false }
|
||||
frame-election-provider-support = { workspace = true, default-features = false }
|
||||
frame-support = { workspace = true, default-features = false }
|
||||
frame-system = { workspace = true, default-features = false }
|
||||
pallet-bags-list = { workspace = true, default-features = false }
|
||||
pallet-staking = { workspace = true, default-features = false }
|
||||
ghost-nomination-pools = { workspace = true, default-features = false }
|
||||
|
||||
# Substrate Primitives
|
||||
sp-runtime = { workspace = true, default-features = false }
|
||||
sp-runtime-interface = { workspace = true, default-features = false }
|
||||
sp-staking = { workspace = true, default-features = false }
|
||||
sp-std = { workspace = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { workspace = true, default-features = false }
|
||||
pallet-timestamp = { workspace = true }
|
||||
pallet-staking-reward-curve = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-election-provider-support/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"pallet-bags-list/std",
|
||||
"pallet-balances/std",
|
||||
"ghost-nomination-pools/std",
|
||||
"pallet-staking/std",
|
||||
"pallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime-interface/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-election-provider-support/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-bags-list/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"ghost-nomination-pools/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-staking/runtime-benchmarks",
|
||||
]
|
||||
@ -1,846 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
use frame_benchmarking::v1::{account, whitelist_account};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::{
|
||||
assert_ok, ensure,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate, Unbalanced},
|
||||
Get,
|
||||
},
|
||||
};
|
||||
use frame_system::RawOrigin as RuntimeOrigin;
|
||||
use ghost_nomination_pools::{
|
||||
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
|
||||
Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
|
||||
MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
|
||||
Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
||||
};
|
||||
use pallet_staking::MaxNominationsOf;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, StakingInterface};
|
||||
use sp_std::{vec, vec::Vec};
|
||||
// `frame_benchmarking::benchmarks!` macro needs this
|
||||
use ghost_nomination_pools::Call;
|
||||
|
||||
type CurrencyOf<T> = <T as ghost_nomination_pools::Config>::Currency;
|
||||
|
||||
const USER_SEED: u32 = 0;
|
||||
const MAX_SPANS: u32 = 100;
|
||||
|
||||
pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
pub trait Config:
|
||||
ghost_nomination_pools::Config
|
||||
+ pallet_staking::Config
|
||||
+ pallet_bags_list::Config<VoterBagsListInstance>
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn create_funded_user_with_balance<T: ghost_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
) -> T::AccountId {
|
||||
let user = account(string, n, USER_SEED);
|
||||
T::Currency::set_balance(&user, balance);
|
||||
user
|
||||
}
|
||||
|
||||
// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free
|
||||
// balance.
|
||||
fn create_pool_account<T: ghost_nomination_pools::Config>(
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
commission: Option<Perbill>,
|
||||
) -> (T::AccountId, T::AccountId) {
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let pool_creator: T::AccountId =
|
||||
create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
|
||||
let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
|
||||
|
||||
Pools::<T>::create(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
balance,
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(c) = commission {
|
||||
let pool_id = ghost_nomination_pools::LastPoolId::<T>::get();
|
||||
Pools::<T>::set_commission(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
pool_id,
|
||||
Some((c, pool_creator.clone())),
|
||||
)
|
||||
.expect("pool just created, commission can be set by root; qed");
|
||||
}
|
||||
|
||||
let pool_account = ghost_nomination_pools::BondedPools::<T>::iter()
|
||||
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
|
||||
.map(|(pool_id, _)| Pools::<T>::create_bonded_account(pool_id))
|
||||
.expect("pool_creator created a pool above");
|
||||
|
||||
(pool_creator, pool_account)
|
||||
}
|
||||
|
||||
fn vote_to_balance<T: ghost_nomination_pools::Config>(
|
||||
vote: u64,
|
||||
) -> Result<BalanceOf<T>, &'static str> {
|
||||
vote.try_into().map_err(|_| "could not convert u64 to Balance")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct ListScenario<T: ghost_nomination_pools::Config> {
|
||||
/// Stash/Controller that is expected to be moved.
|
||||
origin1: T::AccountId,
|
||||
creator1: T::AccountId,
|
||||
dest_weight: BalanceOf<T>,
|
||||
origin1_member: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> ListScenario<T> {
|
||||
/// An expensive scenario for bags-list implementation:
|
||||
///
|
||||
/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
|
||||
/// itself will need to be read and written to update its head. The node pointed to by r.next
|
||||
/// will need to be read and written as it will need to have its prev pointer updated. Note
|
||||
/// that there are two other worst case scenarios for bag removal: 1) the node is a tail and
|
||||
/// 2) the node is a middle node with prev and next; all scenarios end up with the same number
|
||||
/// of storage reads and writes.
|
||||
///
|
||||
/// - the destination bag has at least one node, which will need its next pointer updated.
|
||||
pub(crate) fn new(
|
||||
origin_weight: BalanceOf<T>,
|
||||
is_increase: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
|
||||
|
||||
ensure!(
|
||||
ghost_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
|
||||
"must allow at least three pools for benchmarks"
|
||||
);
|
||||
|
||||
// Burn the entire issuance.
|
||||
CurrencyOf::<T>::set_total_issuance(Zero::zero());
|
||||
|
||||
// Create accounts with the origin weight
|
||||
let (pool_creator1, pool_origin1) =
|
||||
create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin1,
|
||||
// NOTE: these don't really need to be validators.
|
||||
vec![account("random_validator", 0, USER_SEED)],
|
||||
)?;
|
||||
|
||||
let (_, pool_origin2) =
|
||||
create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin2,
|
||||
vec![account("random_validator", 0, USER_SEED)].clone(),
|
||||
)?;
|
||||
|
||||
// Find a destination weight that will trigger the worst case scenario
|
||||
let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
|
||||
&pool_origin1,
|
||||
is_increase,
|
||||
);
|
||||
|
||||
let dest_weight: BalanceOf<T> =
|
||||
dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
|
||||
|
||||
// Create an account with the worst case destination weight
|
||||
let (_, pool_dest1) =
|
||||
create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
|
||||
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
|
||||
|
||||
Ok(ListScenario {
|
||||
origin1: pool_origin1,
|
||||
creator1: pool_creator1,
|
||||
dest_weight,
|
||||
origin1_member: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
|
||||
let amount = MinJoinBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
// Max `amount` with minimum thresholds for account balance and joining a pool
|
||||
// to ensure 1) the user can be created and 2) can join the pool
|
||||
.max(amount);
|
||||
|
||||
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
|
||||
self.origin1_member = Some(joiner.clone());
|
||||
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
|
||||
|
||||
let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
|
||||
|
||||
// Unbond `amount` from the underlying pool account so when the member joins
|
||||
// we will maintain `current_bonded`.
|
||||
T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`.");
|
||||
|
||||
// Account pool points for the unbonded balance.
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
maybe_pool.as_mut().map(|pool| pool.points -= amount)
|
||||
});
|
||||
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
|
||||
|
||||
// check that the vote weight is still the same as the original bonded
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
|
||||
|
||||
// check the member was added correctly
|
||||
let member = PoolMembers::<T>::get(&joiner).unwrap();
|
||||
assert_eq!(member.points, amount);
|
||||
assert_eq!(member.pool_id, 1);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
let max_additional = scenario.dest_weight - origin_weight;
|
||||
let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
|
||||
|
||||
let joiner: T::AccountId
|
||||
= create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
|
||||
|
||||
whitelist_account!(joiner);
|
||||
}: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1)
|
||||
verify {
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = scenario.dest_weight - origin_weight;
|
||||
|
||||
// creator of the src pool will bond-extra, bumping itself to dest bag.
|
||||
|
||||
}: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra))
|
||||
verify {
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_other {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
// set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// transfer exactly `extra` to the depositor of the src pool (1),
|
||||
let reward_account1 = Pools::<T>::create_reward_account(1);
|
||||
assert!(extra >= CurrencyOf::<T>::minimum_balance());
|
||||
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
|
||||
|
||||
}: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards)
|
||||
verify {
|
||||
// commission of 50% deducted here.
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight / 2u32.into()
|
||||
);
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
|
||||
// Send funds to the reward account of the pool
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
|
||||
// behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// Sanity check
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unbond {
|
||||
// The weight the nominator will start at. The value used here is expected to be
|
||||
// significantly higher than the first position in a list (e.g. the first bag threshold).
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, false)?;
|
||||
let amount = origin_weight - scenario.dest_weight;
|
||||
|
||||
let scenario = scenario.add_joiner(amount);
|
||||
let member_id = scenario.origin1_member.unwrap().clone();
|
||||
let member_id_lookup = T::Lookup::unlookup(member_id.clone());
|
||||
let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
|
||||
whitelist_account!(member_id);
|
||||
}: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points)
|
||||
verify {
|
||||
let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap();
|
||||
// We at least went down to the destination bag
|
||||
assert!(bonded_after <= scenario.dest_weight);
|
||||
let member = PoolMembers::<T>::get(
|
||||
&member_id
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
|
||||
vec![0 + T::Staking::bonding_duration()]
|
||||
);
|
||||
assert_eq!(
|
||||
member.unbonding_eras.values().cloned().collect::<Vec<_>>(),
|
||||
vec![all_points]
|
||||
);
|
||||
}
|
||||
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
// Set the current era
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Add `s` count of slashing spans to storage.
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(pool_account);
|
||||
}: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s)
|
||||
verify {
|
||||
// The joiners funds didn't change
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(joiner);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s)
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into()
|
||||
);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// We set the pool to the destroying state so the depositor can leave
|
||||
BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = PoolState::Destroying;
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Unbond the creator
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
// Simulate some rewards so we can check if the rewards storage is cleaned up. We check this
|
||||
// here to ensure the complete flow for destroying a pool works - the reward pool account
|
||||
// should never exist by time the depositor withdraws so we test that it gets cleaned
|
||||
// up when unbonding.
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
Zero::zero()
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&pool_account),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Some last checks that storage items we expect to get cleaned up are present
|
||||
assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(BondedPools::<T>::contains_key(&1));
|
||||
assert!(SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(RewardPools::<T>::contains_key(&1));
|
||||
assert!(PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s)
|
||||
verify {
|
||||
// Pool removal worked
|
||||
assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(!BondedPools::<T>::contains_key(&1));
|
||||
assert!(!SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(!RewardPools::<T>::contains_key(&1));
|
||||
assert!(!PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&pool_account));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
// Funds where transferred back correctly
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
// gets bond back + rewards collecting when unbonding
|
||||
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
|
||||
);
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// Give the depositor some balance to bond
|
||||
CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
|
||||
|
||||
// Make sure no Pools exist at a pre-condition for our verify checks
|
||||
assert_eq!(RewardPools::<T>::count(), 0);
|
||||
assert_eq!(BondedPools::<T>::count(), 0);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: _(
|
||||
RuntimeOrigin::Signed(depositor.clone()),
|
||||
min_create_bond,
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup
|
||||
)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
nominate {
|
||||
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
// actual validators
|
||||
let validators: Vec<_> = (0..n)
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
|
||||
});
|
||||
|
||||
let caller = account("caller", 0, USER_SEED);
|
||||
whitelist_account!(caller);
|
||||
}:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying)
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 1 .. <T as ghost_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone())
|
||||
verify {
|
||||
assert_eq!(Metadata::<T>::get(&1), metadata);
|
||||
}
|
||||
|
||||
set_configs {
|
||||
}:_(
|
||||
RuntimeOrigin::Root,
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(Perbill::max_value())
|
||||
) verify {
|
||||
assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
|
||||
}
|
||||
|
||||
update_roles {
|
||||
let first_id = ghost_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
RuntimeOrigin::Signed(root.clone()),
|
||||
first_id,
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone())
|
||||
) verify {
|
||||
assert_eq!(
|
||||
ghost_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
|
||||
ghost_nomination_pools::PoolRoles {
|
||||
depositor: root,
|
||||
nominator: Some(random.clone()),
|
||||
bouncer: Some(random.clone()),
|
||||
root: Some(random),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
assert_ok!(T::Staking::nominate(&pool_account, validators));
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
|
||||
verify {
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
|
||||
}
|
||||
|
||||
set_commission {
|
||||
// Create a pool - do not set a commission yet.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
// set a max commission
|
||||
Pools::<T>::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap();
|
||||
// set a change rate
|
||||
Pools::<T>::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into(),
|
||||
}).unwrap();
|
||||
// set a claim permission to an account.
|
||||
Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(depositor.clone()))
|
||||
).unwrap();
|
||||
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(20), depositor.clone())),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into()
|
||||
}),
|
||||
throttle_from: Some(1u32.into()),
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_max {
|
||||
// Create a pool, setting a commission that will update when max commission is set.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50)));
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(50), depositor)),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: None,
|
||||
throttle_from: Some(0u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_change_rate {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
})
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
}),
|
||||
throttle_from: Some(1_u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_claim_permission {
|
||||
// Create a pool.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: None,
|
||||
throttle_from: None,
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_claim_permission {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Join pool
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
}:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned)
|
||||
verify {
|
||||
assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
|
||||
}
|
||||
|
||||
claim_commission {
|
||||
let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// member claims a payout to make some commission available.
|
||||
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
|
||||
// set a claim permission to an account.
|
||||
let _ = Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(claimer))
|
||||
);
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
adjust_pool_deposit {
|
||||
// Create a pool
|
||||
let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
|
||||
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_err());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1)
|
||||
verify {
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
@ -1,205 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::VoterBagsListInstance;
|
||||
use frame_election_provider_support::VoteWeight;
|
||||
use frame_support::{derive_impl, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
BuildStorage, FixedU128, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u128;
|
||||
type Nonce = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = Nonce;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: Balance = 10;
|
||||
}
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
}
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = ();
|
||||
type RewardRemainder = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = ConstU32<3>;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type MaxExposurePageSize = ConstU32<64>;
|
||||
type ElectionProvider =
|
||||
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||
type MaxControllersInDeprecationBatch = ConstU32<100>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type EventListeners = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
pub const MaxPointsToBalance: u8 = 10;
|
||||
}
|
||||
|
||||
impl ghost_nomination_pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = FixedU128;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxPointsToBalance = MaxPointsToBalance;
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
}
|
||||
|
||||
impl crate::Config for Runtime {}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Timestamp: pallet_timestamp,
|
||||
Balances: pallet_balances,
|
||||
Staking: pallet_staking,
|
||||
VoterList: pallet_bags_list::<Instance1>,
|
||||
Pools: ghost_nomination_pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = ghost_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(3),
|
||||
max_members: Some(3 * 3),
|
||||
global_max_commission: Some(Perbill::from_percent(50)),
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
sp_io::TestExternalities::from(storage)
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-fuzzer"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of Fuzzer for fixed point arithmetic primitives."
|
||||
documentation = "https://docs.rs/sp-arithmetic-fuzzer"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = "0.5.54"
|
||||
|
||||
ghost-nomination-pools = { workspace = true, features = ["fuzzing"] }
|
||||
|
||||
frame-system = { workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
|
||||
sp-runtime = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-tracing = { workspace = true }
|
||||
|
||||
rand = { workspace = true, features = ["small_rng"] }
|
||||
log = { workspace = true, default-features = true }
|
||||
|
||||
[[bin]]
|
||||
name = "call"
|
||||
path = "src/call.rs"
|
||||
@ -1,354 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Running
|
||||
//! Running this fuzzer can be done with `cargo hfuzz run call`. `honggfuzz` CLI
|
||||
//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
|
||||
//!
|
||||
//! # Debugging a panic
|
||||
//! Once a panic is found, it can be debugged with
|
||||
//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/call/*.fuzz`.
|
||||
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, GetCallName, UnfilteredDispatchable},
|
||||
};
|
||||
use honggfuzz::fuzz;
|
||||
use pallet_nomination_pools::{
|
||||
log,
|
||||
mock::*,
|
||||
pallet as pools,
|
||||
pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers},
|
||||
BondExtra, BondedPool, GlobalMaxCommission, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool,
|
||||
MaxPools, MinCreateBond, MinJoinBond, PoolId,
|
||||
};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use sp_runtime::{assert_eq_error_rate, Perbill, Perquintill};
|
||||
|
||||
const ERA: BlockNumber = 1000;
|
||||
const MAX_ED_MULTIPLE: Balance = 10_000;
|
||||
const MIN_ED_MULTIPLE: Balance = 10;
|
||||
|
||||
// not quite elegant, just to make it available in random_signed_origin.
|
||||
const REWARD_AGENT_ACCOUNT: AccountId = 42;
|
||||
|
||||
/// Grab random accounts, either known ones, or new ones.
|
||||
fn random_signed_origin<R: Rng>(rng: &mut R) -> (RuntimeOrigin, AccountId) {
|
||||
let count = PoolMembers::<T>::count();
|
||||
if rng.gen::<bool>() && count > 0 {
|
||||
// take an existing account.
|
||||
let skip = rng.gen_range(0..count as usize);
|
||||
|
||||
// this is tricky: the account might be our reward agent, which we never want to be
|
||||
// randomly chosen here. Try another one, or, if it is only our agent, return a random
|
||||
// one nonetheless.
|
||||
let candidate = PoolMembers::<T>::iter_keys().skip(skip).take(1).next().unwrap();
|
||||
let acc =
|
||||
if candidate == REWARD_AGENT_ACCOUNT { rng.gen::<AccountId>() } else { candidate };
|
||||
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
} else {
|
||||
// create a new account
|
||||
let acc = rng.gen::<AccountId>();
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_ed_multiple<R: Rng>(rng: &mut R) -> Balance {
|
||||
let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE);
|
||||
ExistentialDeposit::get() * multiple
|
||||
}
|
||||
|
||||
fn fund_account<R: Rng>(rng: &mut R, account: &AccountId) {
|
||||
let target_amount = random_ed_multiple(rng);
|
||||
if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) {
|
||||
let _ = Balances::deposit_creating(account, top_up);
|
||||
}
|
||||
assert!(Balances::free_balance(account) >= target_amount);
|
||||
}
|
||||
|
||||
fn random_existing_pool<R: Rng>(mut rng: &mut R) -> Option<PoolId> {
|
||||
BondedPools::<T>::iter_keys().collect::<Vec<_>>().choose(&mut rng).map(|x| *x)
|
||||
}
|
||||
|
||||
fn random_call<R: Rng>(mut rng: &mut R) -> (pools::Call<T>, RuntimeOrigin) {
|
||||
let op = rng.gen::<usize>();
|
||||
let mut op_count = <pools::Call<T> as GetCallName>::get_call_names().len();
|
||||
// Exclude set_state, set_metadata, set_configs, update_roles and chill.
|
||||
op_count -= 5;
|
||||
|
||||
match op % op_count {
|
||||
0 => {
|
||||
// join
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::join { amount, pool_id }, origin)
|
||||
},
|
||||
1 => {
|
||||
// bond_extra
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let extra = if rng.gen::<bool>() {
|
||||
BondExtra::Rewards
|
||||
} else {
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
BondExtra::FreeBalance(amount)
|
||||
};
|
||||
(PoolsCall::<T>::bond_extra { extra }, origin)
|
||||
},
|
||||
2 => {
|
||||
// claim_payout
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::claim_payout {}, origin)
|
||||
},
|
||||
3 => {
|
||||
// unbond
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::unbond { member_account: who, unbonding_points: amount }, origin)
|
||||
},
|
||||
4 => {
|
||||
// pool_withdraw_unbonded
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin)
|
||||
},
|
||||
5 => {
|
||||
// withdraw_unbonded
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
(
|
||||
PoolsCall::<T>::withdraw_unbonded { member_account: who, num_slashing_spans: 0 },
|
||||
origin,
|
||||
)
|
||||
},
|
||||
6 => {
|
||||
// create
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let root = who;
|
||||
let bouncer = who;
|
||||
let nominator = who;
|
||||
(PoolsCall::<T>::create { amount, root, bouncer, nominator }, origin)
|
||||
},
|
||||
7 => {
|
||||
// nominate
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let validators = Default::default();
|
||||
(PoolsCall::<T>::nominate { pool_id, validators }, origin)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RewardAgent {
|
||||
who: AccountId,
|
||||
pool_id: Option<PoolId>,
|
||||
expected_reward: Balance,
|
||||
}
|
||||
|
||||
// TODO: inject some slashes into the game.
|
||||
impl RewardAgent {
|
||||
fn new(who: AccountId) -> Self {
|
||||
Self { who, ..Default::default() }
|
||||
}
|
||||
|
||||
fn join(&mut self) {
|
||||
if self.pool_id.is_some() {
|
||||
return
|
||||
}
|
||||
let pool_id = LastPoolId::<T>::get();
|
||||
let amount = 10 * ExistentialDeposit::get();
|
||||
let origin = RuntimeOrigin::signed(self.who);
|
||||
let _ = Balances::deposit_creating(&self.who, 10 * amount);
|
||||
self.pool_id = Some(pool_id);
|
||||
log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount);
|
||||
assert_ok!(PoolsCall::join::<T> { amount, pool_id }.dispatch_bypass_filter(origin));
|
||||
}
|
||||
|
||||
fn claim_payout(&mut self) {
|
||||
// 10 era later, we claim our payout. We expect our income to be roughly what we
|
||||
// calculated.
|
||||
if !PoolMembers::<T>::contains_key(&self.who) {
|
||||
log!(warn, "reward agent is not in the pool yet, cannot claim");
|
||||
return
|
||||
}
|
||||
let pre = Balances::free_balance(&42);
|
||||
let origin = RuntimeOrigin::signed(42);
|
||||
assert_ok!(PoolsCall::<T>::claim_payout {}.dispatch_bypass_filter(origin));
|
||||
let post = Balances::free_balance(&42);
|
||||
|
||||
let income = post - pre;
|
||||
log::info!(
|
||||
target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}",
|
||||
income,
|
||||
self.expected_reward,
|
||||
);
|
||||
assert_eq_error_rate!(income, self.expected_reward, 10);
|
||||
self.expected_reward = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reward_agent = RewardAgent::new(REWARD_AGENT_ACCOUNT);
|
||||
sp_tracing::try_init_simple();
|
||||
let mut ext = sp_io::TestExternalities::new_empty();
|
||||
let mut events_histogram = Vec::<(PoolsEvents<T>, u32)>::default();
|
||||
let mut iteration = 0 as BlockNumber;
|
||||
let mut ok = 0;
|
||||
let mut err = 0;
|
||||
|
||||
let dot: Balance = (10 as Balance).pow(10);
|
||||
ExistentialDeposit::set(dot);
|
||||
BondingDuration::set(8);
|
||||
|
||||
ext.execute_with(|| {
|
||||
MaxPoolMembers::<T>::set(Some(10_000));
|
||||
MaxPoolMembersPerPool::<T>::set(Some(1000));
|
||||
MaxPools::<T>::set(Some(1_000));
|
||||
GlobalMaxCommission::<T>::set(Some(Perbill::from_percent(25)));
|
||||
|
||||
MinCreateBond::<T>::set(10 * ExistentialDeposit::get());
|
||||
MinJoinBond::<T>::set(5 * ExistentialDeposit::get());
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
loop {
|
||||
fuzz!(|seed: [u8; 32]| {
|
||||
use ::rand::{rngs::SmallRng, SeedableRng};
|
||||
let mut rng = SmallRng::from_seed(seed);
|
||||
|
||||
ext.execute_with(|| {
|
||||
let (call, origin) = random_call(&mut rng);
|
||||
let outcome = call.clone().dispatch_bypass_filter(origin.clone());
|
||||
iteration += 1;
|
||||
match outcome {
|
||||
Ok(_) => ok += 1,
|
||||
Err(_) => err += 1,
|
||||
};
|
||||
|
||||
log!(
|
||||
trace,
|
||||
"iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err",
|
||||
iteration,
|
||||
call,
|
||||
origin,
|
||||
outcome,
|
||||
ok,
|
||||
err,
|
||||
);
|
||||
|
||||
// possibly join the reward_agent
|
||||
if iteration > ERA / 2 && BondedPools::<T>::count() > 0 {
|
||||
reward_agent.join();
|
||||
}
|
||||
// and possibly roughly every 4 era, trigger payout for the agent. Doing this more
|
||||
// frequent is also harmless.
|
||||
if rng.gen_range(0..(4 * ERA)) == 0 {
|
||||
reward_agent.claim_payout();
|
||||
}
|
||||
|
||||
// execute sanity checks at a fixed interval, possibly on every block.
|
||||
if iteration %
|
||||
(std::env::var("SANITY_CHECK_INTERVAL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok()))
|
||||
.unwrap_or(1) == 0
|
||||
{
|
||||
log!(info, "running sanity checks at {}", iteration);
|
||||
Pools::do_try_state(u8::MAX).unwrap();
|
||||
}
|
||||
|
||||
// collect and reset events.
|
||||
System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| {
|
||||
if let pallet_nomination_pools::mock::RuntimeEvent::Pools(inner) = e {
|
||||
Some(inner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(|e| {
|
||||
if let Some((_, c)) = events_histogram
|
||||
.iter_mut()
|
||||
.find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e))
|
||||
{
|
||||
*c += 1;
|
||||
} else {
|
||||
events_histogram.push((e, 1))
|
||||
}
|
||||
});
|
||||
System::reset_events();
|
||||
|
||||
// trigger an era change, and check the status of the reward agent.
|
||||
if iteration % ERA == 0 {
|
||||
CurrentEra::mutate(|c| *c += 1);
|
||||
BondedPools::<T>::iter().for_each(|(id, _)| {
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
let _ =
|
||||
Balances::deposit_creating(&Pools::create_reward_account(id), amount);
|
||||
// if we just paid out the reward agent, let's calculate how much we expect
|
||||
// our reward agent to have earned.
|
||||
if reward_agent.pool_id.map_or(false, |mid| mid == id) {
|
||||
let all_points = BondedPool::<T>::get(id).map(|p| p.points).unwrap();
|
||||
let member_points =
|
||||
PoolMembers::<T>::get(reward_agent.who).map(|m| m.points).unwrap();
|
||||
let agent_share = Perquintill::from_rational(member_points, all_points);
|
||||
log::info!(
|
||||
target: "reward-agent",
|
||||
"🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}",
|
||||
amount,
|
||||
agent_share,
|
||||
agent_share * amount,
|
||||
);
|
||||
reward_agent.expected_reward += agent_share * amount;
|
||||
}
|
||||
});
|
||||
|
||||
log!(
|
||||
info,
|
||||
"iteration {}, {} pools, {} members, {} ok {} err, events = {:?}",
|
||||
iteration,
|
||||
BondedPools::<T>::count(),
|
||||
PoolMembers::<T>::count(),
|
||||
ok,
|
||||
err,
|
||||
events_histogram
|
||||
.iter()
|
||||
.map(|(x, c)| (
|
||||
format!("{:?}", x)
|
||||
.split(" ")
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap(),
|
||||
c,
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-runtime-api"
|
||||
version = "23.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of Runtime API for nomination-pools FRAME pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true, default-features = false, features = ["derive"] }
|
||||
sp-api = { workspace = true, default-features = false }
|
||||
sp-std = { workspace = true, default-features = false }
|
||||
ghost-nomination-pools = { workspace = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "ghost-nomination-pools/std", "sp-api/std", "sp-std/std"]
|
||||
@ -1,3 +0,0 @@
|
||||
Runtime API definition for nomination-pools pallet.
|
||||
|
||||
License: Apache-2.0
|
||||
@ -1,42 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Runtime API definition for nomination-pools pallet.
|
||||
//! Currently supports only one rpc endpoint.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Codec;
|
||||
use ghost_nomination_pools::PoolId;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// Runtime api for accessing information about nomination pools.
|
||||
pub trait NominationPoolsApi<AccountId, Balance>
|
||||
where
|
||||
AccountId: Codec,
|
||||
Balance: Codec,
|
||||
{
|
||||
/// Returns the pending rewards for the member that the AccountId was given for.
|
||||
fn pending_rewards(who: AccountId) -> Balance;
|
||||
|
||||
/// Returns the equivalent balance of `points` for a given pool.
|
||||
fn points_to_balance(pool_id: PoolId, points: Balance) -> Balance;
|
||||
|
||||
/// Returns the equivalent points of `new_funds` for a given pool.
|
||||
fn balance_to_points(pool_id: PoolId, new_funds: Balance) -> Balance;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,574 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate::{self as pools};
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::fungible::Mutate,
|
||||
PalletId,
|
||||
};
|
||||
use frame_system::{EnsureSignedBy, RawOrigin};
|
||||
use sp_runtime::{BuildStorage, FixedU128};
|
||||
use sp_staking::{OnStakingUpdate, Stake};
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
pub type AccountId = u128;
|
||||
pub type Balance = u128;
|
||||
pub type RewardCounter = FixedU128;
|
||||
// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests
|
||||
// as well, e.g. `StorageItem::<T>::get()`.
|
||||
pub type T = Runtime;
|
||||
pub type Currency = <T as Config>::Currency;
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_bonded_account() -> AccountId {
|
||||
Pools::create_bonded_account(1)
|
||||
}
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_reward_account() -> AccountId {
|
||||
Pools::create_reward_account(1)
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MinJoinBondConfig: Balance = 2;
|
||||
pub static CurrentEra: EraIndex = 0;
|
||||
pub static BondingDuration: EraIndex = 3;
|
||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
// map from a user to a vec of eras and amounts being unlocked in each era.
|
||||
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub static MaxUnbonding: u32 = 8;
|
||||
pub static StakingMinBond: Balance = 10;
|
||||
pub storage Nominations: Option<Vec<AccountId>> = None;
|
||||
}
|
||||
pub struct StakingMock;
|
||||
|
||||
impl StakingMock {
|
||||
pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.insert(who, bonded);
|
||||
BondedBalanceMap::set(&x)
|
||||
}
|
||||
/// Mimics a slash towards a pool specified by `pool_id`.
|
||||
/// This reduces the bonded balance of a pool by `amount` and calls [`Pools::on_slash`] to
|
||||
/// enact changes in the nomination-pool pallet.
|
||||
///
|
||||
/// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for
|
||||
/// `slashed_unlocking`.
|
||||
pub fn slash_by(pool_id: PoolId, amount: Balance) {
|
||||
let acc = Pools::create_bonded_account(pool_id);
|
||||
let bonded = BondedBalanceMap::get();
|
||||
let pre_total = bonded.get(&acc).unwrap();
|
||||
Self::set_bonded_balance(acc, pre_total - amount);
|
||||
Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_staking::StakingInterface for StakingMock {
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
type CurrencyToVote = ();
|
||||
|
||||
fn minimum_nominator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
fn minimum_validator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
|
||||
fn desired_validator_count() -> u32 {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn current_era() -> EraIndex {
|
||||
CurrentEra::get()
|
||||
}
|
||||
|
||||
fn bonding_duration() -> EraIndex {
|
||||
BondingDuration::get()
|
||||
}
|
||||
|
||||
fn status(
|
||||
_: &Self::AccountId,
|
||||
) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
|
||||
Nominations::get()
|
||||
.map(|noms| sp_staking::StakerStatus::Nominator(noms))
|
||||
.ok_or(DispatchError::Other("NotStash"))
|
||||
}
|
||||
|
||||
fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.get_mut(who).map(|v| *v += extra);
|
||||
BondedBalanceMap::set(&x);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
|
||||
BondedBalanceMap::set(&x);
|
||||
|
||||
let era = Self::current_era();
|
||||
let unlocking_at = era + Self::bonding_duration();
|
||||
let mut y = UnbondingBalanceMap::get();
|
||||
y.entry(*who).or_insert(Default::default()).push((unlocking_at, amount));
|
||||
UnbondingBalanceMap::set(&y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_payee(_stash: &Self::AccountId, _reward_acc: &Self::AccountId) -> DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
||||
let mut unbonding_map = UnbondingBalanceMap::get();
|
||||
|
||||
// closure to calculate the current unlocking funds across all eras/accounts.
|
||||
let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance {
|
||||
pair.iter()
|
||||
.try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount))
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?;
|
||||
let unlocking_before = unlocking(&staker_map);
|
||||
|
||||
let current_era = Self::current_era();
|
||||
|
||||
staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
|
||||
|
||||
// if there was a withdrawal, notify the pallet.
|
||||
Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map)));
|
||||
|
||||
UnbondingBalanceMap::set(&unbonding_map);
|
||||
Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty())
|
||||
}
|
||||
|
||||
fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult {
|
||||
StakingMock::set_bonded_balance(*stash, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nominate(_: &Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
|
||||
Nominations::set(&Some(nominations));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(_: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
|
||||
Nominations::get()
|
||||
}
|
||||
|
||||
fn stash_by_ctrl(_controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
|
||||
match (UnbondingBalanceMap::get().get(who), BondedBalanceMap::get().get(who).copied()) {
|
||||
(None, None) => Err(DispatchError::Other("balance not found")),
|
||||
(Some(v), None) => Ok(Stake {
|
||||
total: v.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)),
|
||||
active: 0,
|
||||
}),
|
||||
(None, Some(v)) => Ok(Stake { total: v, active: v }),
|
||||
(Some(a), Some(b)) => Ok(Stake {
|
||||
total: a.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)) + b,
|
||||
active: b,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn election_ongoing() -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn force_unstake(_who: Self::AccountId) -> sp_runtime::DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add_era_stakers(
|
||||
_current_era: &EraIndex,
|
||||
_stash: &Self::AccountId,
|
||||
_exposures: Vec<(Self::AccountId, Self::Balance)>,
|
||||
) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn set_current_era(_era: EraIndex) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn max_exposure_page_size() -> sp_staking::Page {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn slash_reward_fraction() -> Perbill {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type SS58Prefix = ();
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type DbWeight = ();
|
||||
type BlockLength = ();
|
||||
type BlockWeights = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = frame_support::traits::ConstU32<1024>;
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<U256, Balance> for U256ToBalance {
|
||||
fn convert(n: U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 2;
|
||||
pub static MaxMetadataLen: u32 = 2;
|
||||
pub static CheckLevel: u8 = 255;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const Admin: u128 = 42;
|
||||
}
|
||||
|
||||
impl pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = RewardCounter;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = StakingMock;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxMetadataLen = MaxMetadataLen;
|
||||
type MaxUnbonding = MaxUnbonding;
|
||||
type MaxPointsToBalance = frame_support::traits::ConstU8<10>;
|
||||
type AdminOrigin = EnsureSignedBy<Admin, AccountId>;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Pools: pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub struct ExtBuilder {
|
||||
members: Vec<(AccountId, Balance)>,
|
||||
max_members: Option<u32>,
|
||||
max_members_per_pool: Option<u32>,
|
||||
global_max_commission: Option<Perbill>,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
members: Default::default(),
|
||||
max_members: Some(4),
|
||||
max_members_per_pool: Some(3),
|
||||
global_max_commission: Some(Perbill::from_percent(90)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "fuzzing", allow(dead_code))]
|
||||
impl ExtBuilder {
|
||||
// Add members to pool 0.
|
||||
pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self {
|
||||
self.members = members;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ed(self, ed: Balance) -> Self {
|
||||
ExistentialDeposit::set(ed);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_bond(self, min: Balance) -> Self {
|
||||
StakingMinBond::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_join_bond(self, min: Balance) -> Self {
|
||||
MinJoinBondConfig::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_check(self, level: u8) -> Self {
|
||||
CheckLevel::set(level);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members_per_pool(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members_per_pool = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn global_max_commission(mut self, commission: Option<Perbill>) -> Self {
|
||||
self.global_max_commission = commission;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage =
|
||||
frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
|
||||
let _ = crate::GenesisConfig::<Runtime> {
|
||||
min_join_bond: MinJoinBondConfig::get(),
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(2),
|
||||
max_members_per_pool: self.max_members_per_pool,
|
||||
max_members: self.max_members,
|
||||
global_max_commission: self.global_max_commission,
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// make a pool
|
||||
let amount_to_bond = Pools::depositor_min_bond();
|
||||
Currency::set_balance(&10, amount_to_bond * 5);
|
||||
assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
|
||||
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
|
||||
let last_pool = LastPoolId::<Runtime>::get();
|
||||
for (account_id, bonded) in self.members {
|
||||
<Runtime as Config>::Currency::set_balance(&account_id, bonded * 2);
|
||||
assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool));
|
||||
}
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn build_and_execute(self, test: impl FnOnce()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
Pools::do_try_state(CheckLevel::get()).unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) {
|
||||
BondedPools::<Runtime>::try_mutate(pool_id, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = state;
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
storage PoolsEvents: u32 = 0;
|
||||
storage BalancesEvents: u32 = 0;
|
||||
}
|
||||
|
||||
/// Helper to run a specified amount of blocks.
|
||||
pub fn run_blocks(n: u64) {
|
||||
let current_block = System::block_number();
|
||||
run_to_block(n + current_block);
|
||||
}
|
||||
|
||||
/// Helper to run to a specific block.
|
||||
pub fn run_to_block(n: u64) {
|
||||
let current_block = System::block_number();
|
||||
assert!(n > current_block);
|
||||
while System::block_number() < n {
|
||||
Pools::on_finalize(System::block_number());
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
Pools::on_initialize(System::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
/// All events of this pallet.
|
||||
pub fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = PoolsEvents::get();
|
||||
PoolsEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// All events of the `Balances` pallet.
|
||||
pub fn balances_events_since_last_call() -> Vec<pallet_balances::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = BalancesEvents::get();
|
||||
BalancesEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// Same as `fully_unbond`, in permissioned setting.
|
||||
pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult {
|
||||
let points = PoolMembers::<Runtime>::get(member)
|
||||
.map(|d| d.active_points())
|
||||
.unwrap_or_default();
|
||||
Pools::unbond(RuntimeOrigin::signed(member), member, points)
|
||||
}
|
||||
|
||||
pub fn pending_rewards_for_delegator(delegator: AccountId) -> Balance {
|
||||
let member = PoolMembers::<T>::get(delegator).unwrap();
|
||||
let bonded_pool = BondedPools::<T>::get(member.pool_id).unwrap();
|
||||
let reward_pool = RewardPools::<T>::get(member.pool_id).unwrap();
|
||||
|
||||
assert!(!bonded_pool.points.is_zero());
|
||||
|
||||
let commission = bonded_pool.commission.current();
|
||||
let current_rc = reward_pool
|
||||
.current_reward_counter(member.pool_id, bonded_pool.points, commission)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
member.pending_rewards(current_rc).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum RewardImbalance {
|
||||
// There is no reward deficit.
|
||||
Surplus(Balance),
|
||||
// There is a reward deficit.
|
||||
Deficit(Balance),
|
||||
}
|
||||
|
||||
pub fn pool_pending_rewards(pool: PoolId) -> Result<BalanceOf<T>, sp_runtime::DispatchError> {
|
||||
let bonded_pool = BondedPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
let reward_pool = RewardPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
|
||||
let current_rc = if !bonded_pool.points.is_zero() {
|
||||
let commission = bonded_pool.commission.current();
|
||||
reward_pool.current_reward_counter(pool, bonded_pool.points, commission)?.0
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
Ok(PoolMembers::<T>::iter()
|
||||
.filter(|(_, d)| d.pool_id == pool)
|
||||
.map(|(_, d)| d.pending_rewards(current_rc).unwrap_or_default())
|
||||
.fold(0u32.into(), |acc: BalanceOf<T>, x| acc.saturating_add(x)))
|
||||
}
|
||||
|
||||
pub fn reward_imbalance(pool: PoolId) -> RewardImbalance {
|
||||
let pending_rewards = pool_pending_rewards(pool).expect("pool should exist");
|
||||
let current_balance = RewardPool::<Runtime>::current_balance(pool);
|
||||
|
||||
if pending_rewards > current_balance {
|
||||
RewardImbalance::Deficit(pending_rewards - current_balance)
|
||||
} else {
|
||||
RewardImbalance::Surplus(current_balance - pending_rewards)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn u256_to_balance_convert_works() {
|
||||
assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero());
|
||||
assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn u256_to_balance_convert_panics_correctly() {
|
||||
U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_to_u256_convert_works() {
|
||||
assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero());
|
||||
assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
[package]
|
||||
name = "pallet-nomination-pools-test-staking"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "FRAME nomination pools pallet tests with the staking pallet"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dev-dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] }
|
||||
scale-info = { version = "2.11.1", features = ["derive"] }
|
||||
|
||||
sp-runtime = { path = "../../../primitives/runtime" }
|
||||
sp-io = { path = "../../../primitives/io" }
|
||||
sp-std = { path = "../../../primitives/std" }
|
||||
sp-staking = { path = "../../../primitives/staking" }
|
||||
sp-core = { path = "../../../primitives/core" }
|
||||
|
||||
frame-system = { path = "../../system" }
|
||||
frame-support = { path = "../../support" }
|
||||
frame-election-provider-support = { path = "../../election-provider-support" }
|
||||
|
||||
pallet-timestamp = { path = "../../timestamp" }
|
||||
pallet-balances = { path = "../../balances" }
|
||||
pallet-staking = { path = "../../staking" }
|
||||
pallet-bags-list = { path = "../../bags-list" }
|
||||
pallet-staking-reward-curve = { path = "../../staking/reward-curve" }
|
||||
pallet-nomination-pools = { path = ".." }
|
||||
|
||||
sp-tracing = { path = "../../../primitives/tracing" }
|
||||
log = { workspace = true, default-features = true }
|
||||
@ -1,823 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod mock;
|
||||
|
||||
use frame_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use mock::*;
|
||||
use pallet_nomination_pools::{
|
||||
BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember,
|
||||
PoolMembers, PoolState,
|
||||
};
|
||||
use pallet_staking::{
|
||||
CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination,
|
||||
};
|
||||
use sp_runtime::{bounded_btree_map, traits::Zero};
|
||||
|
||||
#[test]
|
||||
fn pool_lifecycle_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
// have the pool nominate.
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// pool goes into destroying
|
||||
assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
|
||||
|
||||
// depositor cannot unbond yet.
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// now the members want to unbond.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 },
|
||||
]
|
||||
);
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
for e in 1..BondingDuration::get() {
|
||||
CurrentEra::<Runtime>::set(Some(e));
|
||||
assert_noop!(
|
||||
Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0),
|
||||
PoolsError::<Runtime>::CannotWithdrawAny
|
||||
);
|
||||
}
|
||||
|
||||
// members are now unlocked.
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// but members can now withdraw.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
assert!(PoolMembers::<Runtime>::get(20).is_none());
|
||||
assert!(PoolMembers::<Runtime>::get(21).is_none());
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 },
|
||||
]
|
||||
);
|
||||
|
||||
// as soon as all members have left, the depositor can try to unbond, but since the
|
||||
// min-nominator intention is set, they must chill first.
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
pallet_staking::Error::<Runtime>::InsufficientBond
|
||||
);
|
||||
|
||||
assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Chilled { stash: POOL1_BONDED },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }]
|
||||
);
|
||||
|
||||
// waiting another bonding duration:
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1));
|
||||
|
||||
// pools is fully destroyed now.
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_chill_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
// have the pool nominate.
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// in case depositor does not have more than `MinNominatorBond` staked, we can end up in
|
||||
// situation where a member unbonding would cause pool balance to drop below
|
||||
// `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is
|
||||
// increased after the pool is created.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
RuntimeOrigin::root(),
|
||||
pallet_staking::ConfigOp::Set(55), // minimum nominator bond
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
));
|
||||
|
||||
// members can unbond as long as total stake of the pool is above min nominator bond
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
|
||||
|
||||
// this member cannot unbond since it will cause `pool stake < MinNominatorBond`
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(21), 21, 10),
|
||||
StakingError::<Runtime>::InsufficientBond,
|
||||
);
|
||||
|
||||
// members can call `chill` permissionlessly now
|
||||
assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1));
|
||||
|
||||
// now another member can unbond.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
|
||||
|
||||
// nominator can not resume nomination until depositor have enough stake
|
||||
assert_noop!(
|
||||
Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// other members joining pool does not affect the depositor's ability to resume nomination
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1));
|
||||
|
||||
assert_noop!(
|
||||
Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// depositor can bond extra stake
|
||||
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
|
||||
|
||||
// `chill` can not be called permissionlessly anymore
|
||||
assert_noop!(
|
||||
Pools::chill(RuntimeOrigin::signed(20), 1),
|
||||
PoolsError::<Runtime>::NotNominator,
|
||||
);
|
||||
|
||||
// now nominator can resume nomination
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
// skip to make the unbonding period end.
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
|
||||
|
||||
// members can now withdraw.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Chilled { stash: POOL1_BONDED },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra
|
||||
StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Payee::<Runtime>::get(POOL1_BONDED),
|
||||
Some(RewardDestination::Account(POOL1_REWARD))
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// now let's progress a bit.
|
||||
CurrentEra::<Runtime>::set(Some(1));
|
||||
|
||||
// 20 / 80 of the total funds are unlocked, and safe from any further slash.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(2));
|
||||
|
||||
// note: depositor cannot fully unbond at this point.
|
||||
// these funds will still get slashed.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
]
|
||||
);
|
||||
|
||||
// At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and
|
||||
// another 30 are active and vulnerable to slash. Let's slash half of them.
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
30,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
2, // slash era 2, affects chunks at era 5 onwards.
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 },
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(3));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(21).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
last_recorded_reward_counter: Zero::zero(),
|
||||
// the 10 points unlocked just now correspond to 5 points in the unbond pool.
|
||||
unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }]
|
||||
);
|
||||
|
||||
// now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free.
|
||||
CurrentEra::<Runtime>::set(Some(6));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 20 had unbonded 10 safely, and 10 got slashed by half.
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
// 21 unbonded all of it after the slash
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
// a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }]
|
||||
);
|
||||
|
||||
// now, finally, we can unbond the depositor further than their current limit.
|
||||
assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(9));
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
last_recorded_reward_counter: Zero::zero(),
|
||||
unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
|
||||
}
|
||||
);
|
||||
// withdraw the depositor, they should lose 12 balance in total due to slash.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_proportional() {
|
||||
// a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that
|
||||
// happened in era 100 should only affect the latter two.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<T>::get(), 1);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true },
|
||||
PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// now let's progress a lot.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
|
||||
// and unbond
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 21,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 128
|
||||
}]
|
||||
);
|
||||
|
||||
CurrentEra::<T>::set(Some(101));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 22,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 129
|
||||
}]
|
||||
);
|
||||
|
||||
// Apply a slash that happened in era 100. This is typically applied with a delay.
|
||||
// Of the total 100, 50 is slashed.
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
50,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// This era got slashed 12.5, which rounded up to 13.
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 },
|
||||
// This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more
|
||||
// slashed, and 12 is all the remaining slash
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 },
|
||||
// Bonded pool got slashed for 25, remaining 15 in it.
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_non_proportional_only_bonded_pool() {
|
||||
// A typical example where a pool member unbonds in era 99, and they can get away with a slash
|
||||
// that happened in era 100, as long as the pool has enough active bond to cover the slash. If
|
||||
// everything else in the slashing/staking system works, this should always be the case.
|
||||
// Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk
|
||||
// if it runs out of chunks that it thinks should be affected by the slash.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
|
||||
);
|
||||
|
||||
// progress and unbond.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
// slash for 30. This will be deducted only from the bonded pool.
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
30,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_non_proportional_bonded_pool_and_chunks() {
|
||||
// An uncommon example where even though some funds are unlocked such that they should not be
|
||||
// affected by a slash, we still slash out of them. This should not happen at all. If a
|
||||
// nomination has unbonded, from the next era onwards, their exposure will drop, so if an era
|
||||
// happens in that era, then their share of that slash should naturally be less, such that only
|
||||
// their active ledger stake is enough to compensate it.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
|
||||
);
|
||||
|
||||
// progress and unbond.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
// slash 50. This will be deducted only from the bonded pool and one of the unbonding pools.
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
50,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// out of 20, 10 was taken.
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 },
|
||||
// out of 40, all was taken.
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -1,273 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use frame_election_provider_support::VoteWeight;
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl,
|
||||
pallet_prelude::*,
|
||||
parameter_types,
|
||||
traits::{ConstU64, ConstU8},
|
||||
PalletId,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
BuildStorage, FixedU128, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u128;
|
||||
type Nonce = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
pub(crate) type T = Runtime;
|
||||
|
||||
pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128;
|
||||
pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = Nonce;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub static BondingDuration: u32 = 3;
|
||||
}
|
||||
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = ();
|
||||
type RewardRemainder = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type MaxExposurePageSize = ConstU32<64>;
|
||||
type ElectionProvider =
|
||||
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type MaxControllersInDeprecationBatch = ConstU32<100>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type EventListeners = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
impl pallet_nomination_pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = FixedU128;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type MaxPointsToBalance = ConstU8<10>;
|
||||
type PalletId = PoolsPalletId;
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Timestamp: pallet_timestamp,
|
||||
Balances: pallet_balances,
|
||||
Staking: pallet_staking,
|
||||
VoterList: pallet_bags_list::<Instance1>,
|
||||
Pools: pallet_nomination_pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = pallet_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(5),
|
||||
max_members: Some(3 * 5),
|
||||
global_max_commission: Some(Perbill::from_percent(90)),
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// set some limit for nominations.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
RuntimeOrigin::root(),
|
||||
pallet_staking::ConfigOp::Set(10), // minimum nominator bond
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
));
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static ObservedEventsPools: usize = 0;
|
||||
static ObservedEventsStaking: usize = 0;
|
||||
static ObservedEventsBalances: usize = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsPools::get();
|
||||
ObservedEventsPools::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsStaking::get();
|
||||
ObservedEventsStaking::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ghost-slow-clap"
|
||||
version = "0.4.29"
|
||||
description = "DEPRECATED: Applause protocol for the EVM bridge."
|
||||
version = "0.4.30"
|
||||
description = "Applause protocol for the EVM bridge"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@ -20,7 +20,6 @@ pub fn prepare_evm_network<T: Config>(network_id: NetworkIdOf<T>) {
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
network_type: ghost_networks::NetworkType::Evm,
|
||||
network_curve: ghost_networks::NetworkCurve::Secp256k1,
|
||||
gatekeeper: b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec(),
|
||||
topic_name: b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec(),
|
||||
incoming_fee: 0,
|
||||
|
||||
@ -41,6 +41,7 @@ use ghost_networks::{
|
||||
NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
|
||||
NetworkType,
|
||||
};
|
||||
use ghost_traits::evictor::StakingEvictor;
|
||||
use ghost_traits::exposure::ExposureListener;
|
||||
|
||||
pub mod migrations;
|
||||
@ -50,7 +51,7 @@ mod benchmarking;
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
pub mod bitmap;
|
||||
mod bitmap;
|
||||
mod deserialisations;
|
||||
mod evm_types;
|
||||
|
||||
@ -309,6 +310,7 @@ pub mod pallet {
|
||||
>;
|
||||
type DisabledValidators: DisabledValidators;
|
||||
type ExposureListener: ExposureListener<BalanceOf<Self>, Self::AccountId>;
|
||||
type StakingEvictor: StakingEvictor<Self::AccountId>;
|
||||
|
||||
#[pallet::constant]
|
||||
type MaxAuthorities: Get<u32>;
|
||||
@ -1455,7 +1457,15 @@ impl<T: Config> Pallet<T> {
|
||||
let authority_index = index as AuthIndex;
|
||||
|
||||
if offence_bitmap.exists(&authority_index) {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
||||
weight.saturating_accrue(T::DbWeight::get().reads(2));
|
||||
|
||||
if let Some(account_id) = T::ExposureListener::get_account_by_index(index) {
|
||||
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
||||
if T::StakingEvictor::try_evict_validator(&account_id) {
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(2));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(full_id) = <T::ValidatorSet as ValidatorSetWithIdentification<
|
||||
T::AccountId,
|
||||
>>::IdentificationOf::convert(id.clone())
|
||||
@ -1637,7 +1647,7 @@ impl<Offender: Clone> Offence<Offender> for SlowClapOffence<Offender> {
|
||||
missed_percent.saturating_mul(Perbill::from_rational(1, offenders_count))
|
||||
}
|
||||
OffenceType::CommitmentOffence => offenders_count
|
||||
.checked_sub(Perbill::from_percent(9).mul_ceil(self.validator_set_count))
|
||||
.checked_sub(Perbill::from_percent(9).mul_floor(self.validator_set_count))
|
||||
.map(|threshold| {
|
||||
Perbill::from_rational(threshold.saturating_mul(4), self.validator_set_count)
|
||||
.square()
|
||||
|
||||
@ -5,6 +5,7 @@ use frame_support::{
|
||||
traits::{ConstU32, ConstU64},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use ghost_traits::evictor::StakingEvictor;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
use sp_runtime::{
|
||||
curve::PiecewiseLinear,
|
||||
@ -36,6 +37,7 @@ frame_support::construct_runtime!(
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub static EvicatedValidators: Vec<u64> = vec![];
|
||||
pub static FixedValidators: Vec<u64> = vec![0, 1, 2, 3];
|
||||
}
|
||||
|
||||
@ -172,6 +174,25 @@ impl pallet_balances::Config for Runtime {
|
||||
|
||||
type Balance = u64;
|
||||
|
||||
pub struct TestStakingEvictor;
|
||||
impl StakingEvictor<u64> for TestStakingEvictor {
|
||||
fn try_evict_validator(who: &u64) -> bool {
|
||||
if !FixedValidators::get().contains(who) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut evicted_validators = EvicatedValidators::get();
|
||||
match evicted_validators.iter().position(|x| x == who) {
|
||||
Some(_) => false,
|
||||
None => {
|
||||
evicted_validators.push(*who);
|
||||
EvicatedValidators::set(evicted_validators);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestExposureListener;
|
||||
impl ExposureListener<Balance, u64> for TestExposureListener
|
||||
where
|
||||
@ -217,6 +238,7 @@ impl Config for Runtime {
|
||||
type ReportUnresponsiveness = OffenceHandler;
|
||||
type DisabledValidators = Session;
|
||||
type ExposureListener = TestExposureListener;
|
||||
type StakingEvictor = TestStakingEvictor;
|
||||
|
||||
type MaxAuthorities = ConstU32<5>;
|
||||
type ApplauseThreshold = ConstU32<500_000_000>;
|
||||
|
||||
@ -1691,6 +1691,40 @@ fn migration_from_v2_to_v3_works_fine() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validators_are_evicted_during_offence() {
|
||||
let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let session_index = advance_session_and_get_index();
|
||||
prepare_evm_network(None, None);
|
||||
|
||||
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
|
||||
System::set_block_number(5 * EpochDuration::get() / 2);
|
||||
let last_stored_block = 69;
|
||||
|
||||
for i in 0..3 {
|
||||
assert_ok!(do_block_commitment(
|
||||
session_index,
|
||||
network_id,
|
||||
i,
|
||||
last_stored_block,
|
||||
));
|
||||
}
|
||||
|
||||
assert_eq!(EvicatedValidators::get().len(), 0);
|
||||
let current_block = SlowClap::current_block_number();
|
||||
|
||||
SlowClap::on_initialize(current_block);
|
||||
System::assert_has_event(RuntimeEvent::SlowClap(
|
||||
crate::Event::SomeAuthoritiesDelayed {
|
||||
delayed: vec![(3, 3)],
|
||||
},
|
||||
));
|
||||
assert_eq!(EvicatedValidators::get().len(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
fn assert_clapped_amount(
|
||||
session_index: &SessionIndex,
|
||||
unique_hash: &H256,
|
||||
@ -1725,7 +1759,6 @@ fn prepare_evm_network(
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
network_type: ghost_networks::NetworkType::Evm,
|
||||
network_curve: ghost_networks::NetworkCurve::Secp256k1,
|
||||
gatekeeper: get_gatekeeper(),
|
||||
topic_name: get_topic_name(),
|
||||
incoming_fee: maybe_incoming_commission.unwrap_or_default(),
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-traits"
|
||||
version = "0.4.2"
|
||||
description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more."
|
||||
version = "0.3.33"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -9,24 +8,14 @@ homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
num-traits = { workspace = true }
|
||||
frost-core = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"frame-support/std",
|
||||
"num-traits/std",
|
||||
"sp-arithmetic/std",
|
||||
"rand_chacha/std",
|
||||
"frost-core/std",
|
||||
"sp-runtime/std",
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
use sp_arithmetic::traits::AtLeast8BitUnsigned;
|
||||
use sp_runtime::traits::UniqueSaturatedInto;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub trait BoundedBitmapMetadata {
|
||||
fn total_bits_capacity() -> u32;
|
||||
fn chunk_size() -> u32;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapWriter {
|
||||
fn insert<N: Copy + TryInto<usize>>(&mut self, i: N) -> Option<N>;
|
||||
fn remove<N: Copy + TryInto<usize>>(&mut self, i: N) -> Option<N>;
|
||||
fn truncate(&mut self);
|
||||
fn nullify(&mut self);
|
||||
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapReader {
|
||||
fn active_bits_count(&self) -> u32;
|
||||
fn highest_bit<N>(&self) -> Option<N>
|
||||
where
|
||||
usize: UniqueSaturatedInto<N>;
|
||||
fn count_ones<N>(&self) -> N
|
||||
where
|
||||
u32: UniqueSaturatedInto<N>;
|
||||
fn count_zeros<N>(&self) -> N
|
||||
where
|
||||
u32: UniqueSaturatedInto<N>;
|
||||
fn is_empty(&self) -> bool;
|
||||
fn contains<N>(&self, index: N) -> bool
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapGenerator {
|
||||
fn empty<N>(n: N) -> Self
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
fn all_ones<N>(n: N) -> Self
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
fn empty_from(bitmap: &Self) -> Self;
|
||||
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
|
||||
where
|
||||
Outer: AtLeast8BitUnsigned + num_traits::PrimInt;
|
||||
fn from_vec<Outer>(values: &Vec<Outer>) -> Self
|
||||
where
|
||||
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapIterable {
|
||||
type IntoIter<'a, ItemType>: Iterator<Item = ItemType> + 'a
|
||||
where
|
||||
Self: 'a,
|
||||
ItemType: 'static,
|
||||
u32: UniqueSaturatedInto<ItemType>;
|
||||
|
||||
fn iter<ItemType>(&self) -> Self::IntoIter<'_, ItemType>
|
||||
where
|
||||
ItemType: 'static,
|
||||
u32: UniqueSaturatedInto<ItemType>;
|
||||
}
|
||||
3
pallets/traits/src/evictor.rs
Normal file
3
pallets/traits/src/evictor.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub trait StakingEvictor<AccountId> {
|
||||
fn try_evict_validator(who: &AccountId) -> bool;
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
use frost_core::{Ciphersuite, Group, Identifier};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use sp_std::{
|
||||
collections::btree_map::BTreeMap,
|
||||
marker::PhantomData,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
pub trait IdentifierConverter<A, E> {
|
||||
fn non_zero_index(index: A) -> Result<A, E>;
|
||||
fn convert_identifier_to_index(bytes: &[u8]) -> Result<A, E>;
|
||||
}
|
||||
|
||||
pub trait FlexibleRoundOptimizedSchnorrThresholdSignature<A, E>: MerkleTreeBuilder<A, E> {
|
||||
type Packages<'a>;
|
||||
type NoncePackages<'a>;
|
||||
|
||||
fn header_bytes_len(&self) -> usize;
|
||||
fn element_bytes_len(&self) -> usize;
|
||||
fn scalar_bytes_len(&self) -> usize;
|
||||
|
||||
fn verify_signature_share(
|
||||
&self,
|
||||
i: A,
|
||||
is: impl Iterator<Item = A>,
|
||||
ss: &[u8],
|
||||
bf: &[u8],
|
||||
nh: &[u8],
|
||||
bh: &[u8],
|
||||
gc: &[u8],
|
||||
vs: &[u8],
|
||||
vk: &[u8],
|
||||
m: &[u8],
|
||||
) -> Result<(), E>;
|
||||
fn is_signature_valid(&self, vk: &[u8], r: &[u8], z: &[u8], m: &[u8]) -> Result<(), E>;
|
||||
fn accumulate_signature_scalar(&self, ss: &[u8], sh: &[u8]) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn generate_nonce_commitment<R>(&self, sk: &[u8], r: R) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), E>
|
||||
where
|
||||
R: CryptoRng + RngCore;
|
||||
|
||||
fn generate_group_commitment(
|
||||
&self,
|
||||
a: A,
|
||||
sc: &Self::NoncePackages<'_>,
|
||||
vk: &[u8],
|
||||
m: &[u8],
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Vec<Self::Hash>, Self::Hash), E>;
|
||||
fn init_signing_package(&self, n: &Self::NoncePackages<'_>, m: &[u8]) -> Result<Vec<u8>, E>;
|
||||
fn partial_sign_message(&self, sp: &[u8], sn: &[u8], ss: &[u8]) -> Result<Vec<u8>, E>;
|
||||
}
|
||||
|
||||
pub trait EllipticCurveDiffieHellman<A, C, D, K, E> {
|
||||
fn ecdh_encrypt_package<R>(&self, c: &C, aad: &[u8], m: &[u8], r: R) -> Result<D, E>
|
||||
where
|
||||
R: RngCore + CryptoRng;
|
||||
|
||||
fn ecdh_decrypt_package(&self, c: &C, d: &D, aad: &[u8]) -> Result<Vec<u8>, E>;
|
||||
fn ecdh_prepare_additional_info(&self, si: A, ri: A, di: K, _m: PhantomData<D>) -> Vec<u8>;
|
||||
fn ecdh_get_cipher_from_keys(&self, rp: &[u8], sp: &[u8], i: &[u8], _m: PhantomData<D>) -> Result<C, E>;
|
||||
}
|
||||
|
||||
pub trait DistributedKeyGeneration<A, E>: MerkleTreeBuilder<A, E> {
|
||||
type RoundPackages;
|
||||
type Part1Result;
|
||||
type Part2Result;
|
||||
type Part3Result;
|
||||
|
||||
fn dkg_narrow_round_max_signers(&self, s: &[u8], m: A, r: u8) -> Result<Vec<u8>, E>;
|
||||
fn dkg_verify_proof_of_knowledge(&self, i: A, r1: &[u8]) -> Result<A, E>;
|
||||
fn dkg_part1<R>(&self, i: A, max: A, min: A, rng: R) -> Self::Part1Result
|
||||
where
|
||||
R: RngCore + CryptoRng;
|
||||
|
||||
fn dkg_part2(&self, r1_s: &[u8], r1_pm: &Self::RoundPackages) -> Self::Part2Result;
|
||||
fn dkg_verify_private_package(&self, i: A, r1: &[u8], r2: &[u8]) -> Result<(), E>;
|
||||
|
||||
fn dkg_part3(
|
||||
&self,
|
||||
i: A,
|
||||
r2_s: &[u8],
|
||||
r1_pm: &Self::RoundPackages,
|
||||
r2_pm: &Self::RoundPackages,
|
||||
) -> Self::Part3Result;
|
||||
|
||||
fn dkg_derive_verifying_share(&self, sk: &[u8]) -> Result<Vec<u8>, E>;
|
||||
fn dkg_decompress_key(&self, vk: &[u8]) -> Result<Vec<u8>, E>;
|
||||
}
|
||||
|
||||
pub trait MerkleTreeBuilder<A, E>: IdentifierConverter<A, E> {
|
||||
type Hash;
|
||||
|
||||
fn build_merkle_tree<C, V, F>(
|
||||
verifying_shares: &BTreeMap<Identifier<C>, V>,
|
||||
serialize_fn: F,
|
||||
) -> Result<Vec<Self::Hash>, E>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn generate_merkle_proof_from_tree<C, V, F>(
|
||||
verifying_shares: &BTreeMap<Identifier<C>, V>,
|
||||
merkle_tree: &[Self::Hash],
|
||||
authority_index: A,
|
||||
serialize_fn: F,
|
||||
) -> Result<(Vec<u8>, Vec<Self::Hash>), E>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn verify_merkle_proof(
|
||||
&self,
|
||||
verifying_share: &[u8],
|
||||
merkle_proof: &[Self::Hash],
|
||||
merkle_root: Self::Hash,
|
||||
authority_index: A,
|
||||
) -> Result<(), E>;
|
||||
}
|
||||
10
pallets/traits/src/exposure.rs
Normal file
10
pallets/traits/src/exposure.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use sp_runtime::traits::AtLeast32BitUnsigned;
|
||||
|
||||
pub trait ExposureListener<Balance: AtLeast32BitUnsigned, AccountId> {
|
||||
fn get_accounts_by_indexes(
|
||||
indexes: impl Iterator<Item = usize>,
|
||||
) -> sp_std::prelude::Vec<AccountId>;
|
||||
fn get_account_by_index(index: usize) -> Option<AccountId>;
|
||||
fn get_total_exposure() -> Balance;
|
||||
fn get_validator_exposure(index: &AccountId) -> Balance;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub trait GhostHasher {
|
||||
type Hash: PartialEq + Copy + AsRef<[u8]> + AsMut<[u8]>;
|
||||
type HashBytes: AsRef<[u8]> + AsMut<[u8]> + TryFrom<Vec<u8>> + Default;
|
||||
|
||||
fn from_slice(bytes: &[u8]) -> Self::Hash {
|
||||
let mut target_hash = Self::empty();
|
||||
|
||||
let hash_buffer = target_hash.as_mut();
|
||||
let copy_len = bytes.len().min(hash_buffer.len());
|
||||
|
||||
hash_buffer[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
|
||||
target_hash
|
||||
}
|
||||
|
||||
fn hash_to_bytes(hash: Self::Hash) -> Self::HashBytes {
|
||||
let mut bytes = Self::HashBytes::default();
|
||||
bytes.as_mut().copy_from_slice(hash.as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn hash(data: &[u8]) -> Self::Hash;
|
||||
fn empty() -> Self::Hash;
|
||||
fn hash_len() -> usize;
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod bounded_bitmap;
|
||||
pub mod exodus;
|
||||
pub mod hashing;
|
||||
pub mod evictor;
|
||||
pub mod exposure;
|
||||
pub mod networks;
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
use crate::converter::IdentifierConverter;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_core::H256;
|
||||
|
||||
pub trait MerkleTree<A, E>: IdentifierConverter<A, E> {
|
||||
type VerifyingShares;
|
||||
type Hash;
|
||||
|
||||
fn merkle_build_tree(vs: &Self::VerifyingShares) -> Result<Vec<Self::Hash>, E>;
|
||||
fn merkle_generate_proof_from_tree(vsm: &Self::VerifyingShares, t: &[Self::Hash], i: A) -> Result<(Vec<u8>, Vec<H256>), E>;
|
||||
fn merkle_verify_proof(&self, vs: &[u8], p: &[Self::Hash], r: Self::Hash, i: A) -> Result<(), E>;
|
||||
}
|
||||
@ -1,26 +1,10 @@
|
||||
use frame_support::{pallet_prelude::*, storage::PrefixIterator};
|
||||
use sp_std::vec::Vec;
|
||||
use sp_runtime::{
|
||||
offchain::http::PendingRequest,
|
||||
traits::{AtLeast32BitUnsigned, Member},
|
||||
DispatchResult,
|
||||
};
|
||||
|
||||
pub trait NetworkDataBasicHandler {
|
||||
type NetworkCurve: Parameter
|
||||
+ Member
|
||||
+ Default
|
||||
+ Copy
|
||||
+ TypeInfo
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
type NetworkType: Parameter
|
||||
+ Member
|
||||
+ Default
|
||||
+ Copy
|
||||
+ TypeInfo
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
type NetworkId: Parameter
|
||||
+ Member
|
||||
+ AtLeast32BitUnsigned
|
||||
@ -34,13 +18,10 @@ pub trait NetworkDataBasicHandler {
|
||||
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
|
||||
fn count() -> u32;
|
||||
fn contains_key(n: &Self::NetworkId) -> bool;
|
||||
fn curve_exists(c: &Self::NetworkCurve) -> bool;
|
||||
fn network_for_block(b: impl Into<usize>) -> Option<(Self::NetworkId, Network)>;
|
||||
fn get(n: &Self::NetworkId) -> Option<Network>;
|
||||
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
|
||||
fn iter_indexes() -> impl Iterator<Item = Self::NetworkId>;
|
||||
fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve>;
|
||||
fn iter_types_by_curve(n: &Self::NetworkCurve) -> impl Iterator<Item = Self::NetworkType>;
|
||||
}
|
||||
|
||||
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {
|
||||
@ -60,27 +41,3 @@ pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<
|
||||
fn accumulate_incoming_imbalance(amount: &Balance) -> Result<Balance, ()>;
|
||||
fn accumulate_commission(commission: &Balance) -> Result<Balance, ()>;
|
||||
}
|
||||
|
||||
pub trait NetworkRpcResolver<W, B, H, I, S> {
|
||||
fn get_block_request_body(&self, id: I) -> Vec<u8>;
|
||||
fn get_hash_request_body(
|
||||
&self,
|
||||
id: I,
|
||||
block: B,
|
||||
session: S,
|
||||
contract_ref: &[u8],
|
||||
selector_ref: &[u8],
|
||||
) -> Vec<u8>;
|
||||
fn parse_block_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: I,
|
||||
) -> Vec<B>;
|
||||
fn parse_hash_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: I,
|
||||
) -> Vec<(H, S)>;
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-types"
|
||||
version = "0.0.1"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
@ -1 +0,0 @@
|
||||
pub
|
||||
@ -1,79 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-weaver"
|
||||
version = "0.0.0"
|
||||
description = "Weaving a secure cryptographic tapestry across different external chains."
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["alloc"] }
|
||||
|
||||
codec = { workspace = true, features = ["max-encoded-len"] }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
|
||||
frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
pallet-balances = { workspace = true, optional = true }
|
||||
|
||||
sp-keystore = { workspace = true, optional = true }
|
||||
sp-application-crypto = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
ghost-networks = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
ghost-traits = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-keystore = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"log/std",
|
||||
"hex/std",
|
||||
"codec/std",
|
||||
"num-traits/std",
|
||||
"scale-info/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"frame-benchmarking?/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-runtime/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-keystore?/std",
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"ghost-networks/std",
|
||||
"ghost-helpers/std",
|
||||
"ghost-traits/std",
|
||||
"pallet-balances/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"ghost-networks/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"ghost-helpers/runtime-benchmarks",
|
||||
"sp-keystore"
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"ghost-networks/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
]
|
||||
@ -1,415 +0,0 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::vec;
|
||||
|
||||
use ghost_helpers::networks::NetworkDataBuilder;
|
||||
|
||||
fn prepare_pallet<T: Config>(
|
||||
authorities_len: u32
|
||||
) -> (NetworkIdOf<T>, T::AuthorityId, AuthIndex) {
|
||||
let authority = T::AuthorityId::generate_pair(None);
|
||||
let authorities = vec![authority.clone(); authorities_len as usize];
|
||||
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
|
||||
.expect("more than the maximum number of keys provided");
|
||||
|
||||
Authorities::<T>::put(bounded_authorities);
|
||||
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
let network_data = NetworkDataBuilder::default()
|
||||
.with_network_type(NetworkType::Utxo)
|
||||
.with_gatekeeper(
|
||||
// Transaction: https://blockstream.info/tx/c41999f86269d5a22f39e18530b065854f447ef64a48be4013c4a9faa657f418
|
||||
hex::decode("c6e4e0f822c31540b4b05903ce3c67b6cff8e6d4000000000000000000000000")
|
||||
.expect("Bytes32 from UTXO should be valid"),
|
||||
)
|
||||
.build();
|
||||
|
||||
T::NetworkDataHandler::register(network_id, network_data)
|
||||
.expect("network should be valid for insertion");
|
||||
|
||||
(network_id, authority, 0)
|
||||
}
|
||||
|
||||
#[benchmarks(where T: Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn extend_warp(
|
||||
a: Linear<1, { T::MaxAuthorities::get() as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let (network_id, authority, authority_index) = prepare_pallet::<T>(a);
|
||||
|
||||
TapestryDrafts::<T>::remove(&network_id);
|
||||
|
||||
let block_attestation = AttestationContext::default()
|
||||
.with_network_id(network_id)
|
||||
.with_authority_index(authority_index)
|
||||
.build_block_attestation(420);
|
||||
|
||||
let signature = authority
|
||||
.sign(&block_attestation.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, block_attestation, signature);
|
||||
|
||||
let current_attestations = NetworkAttestations::<T>::get(&network_id);
|
||||
assert_eq!(current_attestations.len(), a as usize);
|
||||
assert_eq!(current_attestations[authority_index as usize], Some(420));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn weave_weft(
|
||||
a: Linear<1, { T::MaxAuthorities::get() as u32 }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let weaving_session = 420;
|
||||
let next_session = weaving_session + 1;
|
||||
let root_hash = H256::repeat_byte(0x69);
|
||||
|
||||
let (network_id, authority, authority_index) = prepare_pallet::<T>(a);
|
||||
|
||||
let tapestry_key = (network_id, weaving_session, next_session, root_hash);
|
||||
TapestryStrands::<T>::mutate(&tapestry_key, |tapestry| {
|
||||
(0..a).for_each(|i| {
|
||||
let index = i as AuthIndex;
|
||||
if index == authority_index { return; }
|
||||
tapestry.insert(index);
|
||||
});
|
||||
});
|
||||
|
||||
let current_block = T::BlockNumberProvider::current_block_number();
|
||||
let dummy_draft = TapestryDraftBuilder::default()
|
||||
.with_current_block(current_block)
|
||||
.with_block_delay(T::WeavingDelay::get())
|
||||
.with_median_external(1337 as ExternalBlockNumber)
|
||||
.build();
|
||||
|
||||
TapestryDrafts::<T>::insert(&network_id, dummy_draft);
|
||||
|
||||
let hash_attestation = AttestationContext::default()
|
||||
.with_network_id(network_id)
|
||||
.with_weaving_session(weaving_session)
|
||||
.with_authority_index(authority_index)
|
||||
.build_hash_attestation(root_hash, next_session);
|
||||
|
||||
let signature = authority
|
||||
.sign(&hash_attestation.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
let tapestry_strand_key = (network_id, weaving_session, next_session, root_hash);
|
||||
|
||||
let weaving_state = WeavingStates::<T>::get(&network_id);
|
||||
assert_eq!(weaving_state.hash, H256::default());
|
||||
assert_eq!(weaving_state.next_session, 0);
|
||||
assert_eq!(weaving_state.count, 0);
|
||||
|
||||
assert!(!TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
|
||||
assert_eq!(
|
||||
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
|
||||
a - 1
|
||||
);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, hash_attestation, signature);
|
||||
|
||||
let new_weaving_state = WeavingStates::<T>::get(&network_id);
|
||||
assert_eq!(new_weaving_state.hash, root_hash);
|
||||
assert_eq!(new_weaving_state.next_session, next_session);
|
||||
assert_eq!(new_weaving_state.count, a as AuthIndex);
|
||||
|
||||
assert!(TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
|
||||
assert_eq!(
|
||||
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
|
||||
a
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn pull_utxo_thread(
|
||||
a: Linear<1, { T::MaxAuthorities::get() as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let session = 69;
|
||||
let (network_id, _, _) = prepare_pallet::<T>(1);
|
||||
|
||||
// https://blockstream.info/tx/6fdcb55eaeddae87fdbbd3e6fd8c2b80c1e7e7c1214e1a6e35c06a70e5f21fba?expand
|
||||
// NOTE: this is the best transaction that matches the max sizes I found,
|
||||
// would be nice to find exact edge case
|
||||
|
||||
let root_string =
|
||||
hex::decode("00000000000000000000662f626dbc3c06161a324d879fadcd0293d8e0884a1d")
|
||||
.expect("Bitcoin Root hex should be valid");
|
||||
let expected_root = H256::from_slice(&root_string);
|
||||
|
||||
LoomStates::<T>::insert(&network_id, &session, expected_root);
|
||||
|
||||
let receiver_32: [u8; 32] =
|
||||
hex::decode("6969696969696969696969696969696969696969696969696969696969696969")
|
||||
.expect("Bytes32 from UTXO should be valid")
|
||||
.try_into()
|
||||
.expect("Receiver address must be exactly 32 bytes");
|
||||
let receiver_account = sp_runtime::AccountId32::from(receiver_32);
|
||||
|
||||
let transfer_amount: BalanceOf<T> = 140_693u64.unique_saturated_into();
|
||||
|
||||
let payload = ThreadPayload::new(12, transfer_amount, receiver_account);
|
||||
let utxo_thread_proof = UtxoThreadProof::new(
|
||||
1,
|
||||
0,
|
||||
transfer_amount,
|
||||
payload,
|
||||
hex::decode("008008201643a05d24fe1f015bf472fdedd8ef39dd8247568d3901000000000000000000569ac8f0aed0c5b632dd1dcc031f3d826a170e13cd8176430a2873a33d4694c2501f6a6ad43a02178dbac0d66b1600000e88be94dbcedc3f32bce789eb4512c68b39b91b54c4f2913e84baf7bae21bdc466d954b10876c1e8ad6591461027236c9a471883f96c028a7d1f08deb833f56fa18f457a6faa9c41340be484af67e444f8565b03085e1392fa2d56962f89919c4fcfff99fc6e92be067d01028fb54e8dc1e8425f0ef6bf1cc64220a2ba22163666eb01f99648e8c2af1bb1a0e1ba297f0244a09aa6a6ff87092312b02e8d75aef61e5c08f5b59184a45855e987c5ea114e5da61e55337bfae99f3a2e9601e72ae6036633dc97631609813a30d1185f98e7d6effc4a0330db0394da388c0058e9a730a62d8c679c35bf8d270ca8b95cec7d848729cdfd94a284f060f353b379bfc36310ac78306183a056a04156efd7e77661ce7ba50a4ef9f9e6b3681dfb39d8d0161ad0d2ec46834b5f270b67aa63a8a5b69287e8606b8a438785f8a5af66f84a2641438ba4a5fa22029227e9c2ddb6f23867b112c027d2fc2cf031a46c874701768b5c4983959b98d98736a2ed5ab0dd064ee24e49a3a7dac63e8b857ad4c8b5aa0cf22a7eac3180f3a54630b486de928ecf590e807d2e821818e2fe7e86610187d6673a6f95c639af80a7a7710b0088d44057ca61bdf5f4d5ba68d46b88e4304ffb70000").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("020000000001029ee4235efd4a3cb183d4c311ce858eeee7748263a28afd3d861bac1cb4a8d368010000000001000000d7c5869bfffaa1877d0ecd37e46dc70c232a16f7955e635d57f926d1b3e640bf0100000000ffffffff02ff490b0000000000160014f635b17877709ba3c5acbe153f38a47a733f5b169525020000000000160014c6e4e0f822c31540b4b05903ce3c67b6cff8e6d40247304402200f2f3763fad59d38b4e101b9675450c0a54d45fe17e2b1babde0d0ce6051ea5b0220643e71d66752c0f1bd30dc0897305da77f223d14c781e86063ac181ad7fb90b6012102aad4cca3dab6427bc754a5a123de32cd037e7177fade9f72d83088b00066cffc0247304402200c8f1ff8230813e5e4ab8ec802c614bc8705c407c5958d64dd79cc8422d85a6202203ea832d581c404200b5974fd1a11abfe1ebac213842083cb02eba3f253c8c2aa012102d70df1d4d58701adab82b4816e0640c27403d1fab859ca9f47cb6eccca3dd96200000000").expect("Bytes32 from UTXO should be valid"),
|
||||
vec![
|
||||
hex::decode("00a01520c1d508977a3787523a0f959e14a8df763dc87927c7910000000000000000000036303f2bcf94f752a1d4b0641d1adbc2d80aa89628feefd7f837a74792e26ab8bb276a6ad43a0217bece2e64").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0400e020702ff95ecc1b8575cff205bfc24592c87ba09280bd84000000000000000000008ca9c40d5ab762f160661635cade50fd14571dcf5fdf8c463b617caf9b07dc5a3b296a6ad43a02171340d661").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000000345457ca4006ddc376db24b7a37db04dfa0b46c1d99267010000000000000000009409d64eb06a6dd83ae2937f70d07222d75505a07f12e756b61a27189851e3bbfe2c6a6ad43a0217a06575b5").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("004007220e37af053c56a86899e1219107937fd5e83ff61aeb7800000000000000000000c0588f8b7a697ff9f367ec4a69a9998ef8f2552f808b5025b4b8f604d0a145c6123e6a6ad43a0217a662289d").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0ff3feeae586d6f7215f48e857ed7fcf823b27117f2805fe000000000000000000000dcaa345e44c85be86da35ab0f25e2f089f04f66c1e3a3881a8455b1e42ba2500ad3e6a6ad43a0217b3810985").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00600520214f05f95e93d1d46c768a910fe0a15da3de556c2a7a01000000000000000000a129b7d281efd307700bff535137324aa0c5decc9829243bb7b29f1cafcbbd6c114b6a6ad43a0217cd209b45").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000df2d608f226bda092f6d8da18e92e3cbb41d5c99c7ddbf20010000000000000000009848dfe4f23c9211df920b37d571b2a4363afac3efb83d7cf4249b6491a6c8dfff4c6a6ad43a02174646c9ff").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000020c69aef55529fcbf6d18aea8a701177317483d63c32d1000000000000000000000c3551d009eb18b1cc5da8420c5e1268c1de482127623b11f74743d46f3f34ebf84e6a6ad43a021796234549").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800820d50e734c76e83a3d2658d1977034372b68362927c06b010000000000000000007b701acfc56d4ef6086eda92cde2c809d6960dca65ec9e617d53e0761f620bb72e506a6ad43a0217416f2f2b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00009f23d562db91d625b878e92f5d410c04775ed88006a18a0300000000000000000000a7b1bbad02565047b4428e2ef0cf3acbd0714c77934e44abc367451b85c3e56be3556a6ad43a0217c3b1aebd").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000003e8c4ec35b8fd1f07d6ff39de67c1ade69a25df69d84030100000000000000000033d4dab1c4958ae6d9cb853278362cbf74d25c1c3c7150889883c7fbdfa5004506566a6ad43a021751a2160f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a00020dfde782b9446c17ae40a86dcb2e308d4c729a46c162500000000000000000000cadaf134229eba7775042b3d8689553ba93d0ea929f411d3a1a01693b923b1bf22586a6ad43a02172393ef65").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800c204ec04712a453ed72818b60cb9f74931cc5a59bbcac0d0100000000000000000059eb7a758ff3809a2a5010a3e849201314378120b02b6386490c8b11ad56be308d5c6a6ad43a02177bc26d48").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00801935e8c63aa1065f46a7d31bc5063be04108bdd3b18dd326020000000000000000009a3cfbb7b79d1d51f11ebd0a28ed2bf29e53d36ccefddff12322ca47d00665076c5d6a6ad43a0217dc5d92d8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000003644663bcc00a79e38e833e4a16805ae72cc373cb7e51c01000000000000000000a33b3c0378b2a2d213aac062c7cd8b2c2b3a9c11aaeb4824a037863e7bd5fe7701636a6ad43a02173b36822e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002a4a1da985bd4aa15be31a03fd3efbe77ad0ebabc392ff0100000000000000000077eb14d0e30b97aa2baff829fa7754de88a359e6a3711300f4e2e0c1ad1435829d6b6a6ad43a0217c8ef1999").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000024a4efb58211a7c804ef24ee11f5f9f9db2509be9bc5da010000000000000000001cc7ac2fe2e38bfd4582367690b0521e71c30f0083f68565fd948ad4ee124fb91b6c6a6ad43a0217851e16c0").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0060e729537b191715a6f594c55e85574253c84580909a2fa72200000000000000000000fa2959118ac66e3569081e4645f87f9560742290b52ee54f7886b6fa5f83aa3d016d6a6ad43a021708a2ad64").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3f6fe5befcf9e4c77cf03673076ff822740622f75cd74e010000000000000000008da0cd1fb34cfd4e58b5d3f1e20f6104e491320d5dc109fe16a455e3d385adb90d6f6a6ad43a02175c26ad30").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00204120cd2dba55f0867cff62ee5bab27b0ba85ac58c869f55c00000000000000000000a685eaec339dbb6e4fa52dacd2752291d5bb55dceb0b8b359e5ecbc0b2d1f884506f6a6ad43a0217de2f0c8c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e01420a16b828d829748aa9602930d3ef1d4c983b86b39a20101000000000000000000d6e8aecebb3748113be25a6522f47e10e63c57fe95a09f35052fdabcc8a68ad819736a6ad43a021729b35edd").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("008067269f4c1aaf3129211de19714c6a5bb9c5481ea34f044da00000000000000000000734b07e26a80d3345fa74d1450a1b68d95e0714c163dc7b2b3eb9a0aef8b50c934756a6ad43a02172eba7608").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0fc20ed544115339aa1a59249f02f62856feb602ea083f93300000000000000000000d30c9e1e276efd3be62b7cd7194aa7fb0b807f2fecb9a56074247887b2299882f5756a6ad43a021702a2b823").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000000202d2c60af64f5b2a415bc46bca915042d5136b46929c501000000000000000000e0e3b49cd8b926a7468831c1b115e14838640cb735833561d551a804cf1a752cc3766a6ad43a0217d81e878d").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3f3c41153e725b51dc793b18c0bfd9cfaf14cd091e000402000000000000000000ff9b55b7ed1dcff73f458e48dd2799ae141829586ce0a3902a783f00981c798bff776a6ad43a021700849f4b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00200f237376e2f7ed32d9e0818617150255cc5c87bfc43d182c000000000000000000003c08d2ba07dda561bcb53f56fff0780c5d1046686c363743b17e1591de293a8538776a6ad43a02176fda963f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800020f3ac5bb512764387873548d1cc0d4fef3b70077b6e700100000000000000000035accf350c72501ce6aedd556177cd99090cb4952c7b23fa424ac1f7a282c0315a786a6ad43a02174cf87729").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3fcee6362f1a42288aa20c3f21e9fcde46dcc1a9402d19010000000000000000006e40bfc29eb8859f30d083cefc45482f77946dcb429bcf1e064d1bcbd378f23c0c7b6a6ad43a02173d75f866").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002ae9ef834e71842c871391dd44a09960dd63990dd557e30100000000000000000052b6b1f008e5b0a5f38be6b2082ec17889619f0627f4064106e820364e1cc8072f7a6a6ad43a021740becfc5").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a0ab238496ac097daf6fee7acdd40ef1a503089d76db2256c400000000000000000000a6e564e0de9af79eb797a4b9afb26b5ad56395f3cbb7a9b4bde5a760d140aae8ba7a6a6ad43a021735b5f7f8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00200320e3d603f57832afb5e67c782aa5fd5d8e2d655230b50e020000000000000000005451ec2c708395b414a347863a8cb7ee4a7d8b0309086af4c329c3ba164be5930d7c6a6ad43a02171ffe7b21").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000720159f0a42d5afd3196d6bc4745786b08017e9f2fe65c4010000000000000000002204634460a10b17be10462c08d3a3247e8955da5ec01f525fd0d58b14a5e2ebab826a6ad43a021737c8b684").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000022030d1a71c70fe8d8e994e1dcddcda8c8c5798ea9bb20d000000000000000000005d5579d7b5cc4c5e43d45169c93abf90dc6f212cd462407a48fc144d8315efb35b836a6ad43a0217aeaa2b47").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000000222a3e9205528e4d5618a7287d3d2370a41a6cd2d346fb0000000000000000000016d19ca5aad54bceea5245a221fddbaedb3f56fd6b8f12d59af65a167a7ab56aee836a6ad43a02173c540bc8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0ff3f9ddcd3ff85fdcd54b32df85a1eb8648b211b72a247870000000000000000000043b56016ddc1c29bd76c92a831b4de36a59cc0fb88d294b6faf24339afd8d64de6886a6ad43a0217126de83e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0060c424aa0dedbd17459541b2df58d403e78824d0b306e13888000000000000000000000d05d08f6044eb00963044e75b4dc14417fd7c3acf1f6d2f9eaf9432627d6a93c68a6a6ad43a0217971b219c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00006c2235ade56a30b832a1d86a389853d9d7ef251dcd31d5de0000000000000000000028cbac80a576b4b03407029062c64268eae619201f84de43d65b6cc29d49945d598b6a6ad43a02178216ff0c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0060c924fee183b290d1e5ba0dd2a211888cb9a45ac3261128ca01000000000000000000c64693de6d95445e23df797f3591719428ea07754aad0c5c03707a1696a49316e48c6a6ad43a02172c70405e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c09c217b4715e06ee536c88f2b04f8da84c9be46bb89b337cb0000000000000000000035ed0514da32bff54f234681d567b7bb8bebd48c1981e88a480a0069cf19fae9318f6a6ad43a0217578cf0e8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c00f2089ef5721cd6bfe541cf56a30fe029e8b6a99d426b59b010000000000000000001cc5db1b35955722b2b072be5497665f2bcc0116021fc52ad106b86f85eb7a15ea8f6a6ad43a021726299c25").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00400a20b73d674783455e1728c82701ae1c5bab268563697aa001000000000000000000c7ef430a886118fb6bdd0536e867b9b6fc15cb198c7df4f4669c2fb94244445ebc916a6ad43a021757775d4f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0012028d9ad98ee6302bffc27d2fc51e905e7ff1455e9ad9a000000000000000000005334bd5deb896d99d5f85f762ca5b5057bd8a11fb1f718f794ed67525ce5275639926a6ad43a021742a4ad22").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0982497ed444856620be9d4a2b3f2dd8385aef13794945fc4000000000000000000007a27f7d0b1a20ec5e2f5b69edb844ceb52acbe24cc44b1e486a86105591678c846926a6ad43a02176d3f1083").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800420a90444f33700b0a903d8dd28c7e4b00f1784a4f12c0801000000000000000000068f9295a349845eb2431428cfc318b72eff6d8b828b61533e5b9889be6a455f69926a6ad43a02176536c20e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0ff3f9efdf00d718c728b94e39dde5c42b0450539b1e0d20a02000000000000000000fff04185f99913ff903e7fff46f04e0ae0f132677d10b8fa1537b98be859020c3f946a6ad43a021759b8976c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002ecf497d98fdd70adce90e8ce350f3d9f14b735f7cd03c010000000000000000003e3c02c9fd4bf5cde2f4f0ef50570669aa586b3b12e1dff52507337198d3d879bc966a6ad43a0217d2822239").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000522c9273bf66ca7ea531cd3409f3b7fb9f228966e67149300000000000000000000ad5c81834cc230f4ea59cd148298080d22cd008a440a75e34d946ea646f94e7791976a6ad43a0217899d978e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("008003203404ad959ad7bc9787388e0035542ad5d540f5077130010000000000000000005082dd1fd7487596fe92f1c06261d3f1abd2da19c2116b175d62adc245ae9ccfcf976a6ad43a0217eafb7678").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0ff3f6e2ede80814d029f614eca38ab9c9a15e9400a023d0200000000000000000000429fa6b33b892eb6544a11e0f3728d109acbeb0f9ad59a48ecd3d64e28543a4987986a6ad43a02179c1c0930").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a00820b709f5dde282465f55c4d7e66cb2ba6fc916c0d51c4c01000000000000000000eb37059a5ba1cdd3f3810607c8d194363679bc9f99dcd95652283a12d7603b29ed986a6ad43a02174b733dd8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3f4b173d1bc45aa63418b7bba7c0fa5a5acfe53d22eb1e01000000000000000000f745f6a23d5cc180ff74ad724e3a12bf2bdf6d5d8f1cbacac629b28634fdd3b71c9c6a6ad43a02174c304983").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002830ec33bdc09b86dd4d9e637a2c5c4811989d9bc33505010000000000000000008caab6a74d42b54943d5ab85c80a95f17bdabaaccf27f8e1d18923fe2ad0113c2f9d6a6ad43a0217a53735c3").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a04c25fb6183fac04e92b6a4b899c9ef156babf959dce7c7b600000000000000000000adae556e5b521d5c392f39d3c8695d5cc11dd3234a6c38c7c423ac430fe5d10f2b9f6a6ad43a0217fd964416").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00606b202e489dafa11d28ef8c748ed8da4a954c5affb25454bb01000000000000000000e3f9ee43488ffbb8b711222f0540d1fd61bbec8ffbb9ced960dc2352392eeee180a06a6ad43a02170208ea28").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a00b207940814db8828f2f99a835fec3507ac1e3c6b8c7aaf100000000000000000000a4986016840beba7d215510f02f5e88cd41fc4cfc9bee4069ea56a4a7a9cae8715a26a6ad43a02177d018b27").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e04325f4a84a1a7043948c2af0faa632f4735b8dd989f49c250100000000000000000072707db2fc682148e6aa27d4dbdf7c20009db65c848132f76bc1b3a31bcddcb57ca46a6ad43a021777dae935").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0ff3fad0b6289eaa9c8932a76daadd7a5c3bef539fa19708b00000000000000000000dd2c965c6cea3dc01c21466b8b575c24f0b789a79c6e3f6ba7dcbe45740e1fbb9ea86a6ad43a02173bd6dce6").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000022064c18468116c4e6aa84afd708a1dc597a4aeb5e0d26001000000000000000000d0132ba263c4eb125280313ea30d4853b2201973b825588780c5e080eaaa8418c0a96a6ad43a0217d3de833b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c00e25575e08652190884e0ce0e963d24dc654eeeb47c77f1900000000000000000000068daf8886ba4daec55638e8f7e06890a186b6e6aefb5e7912b360a5a644bf1704aa6a6ad43a0217693c1a54").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002ccb2e57d6fa6db31eb2fe4b099c98602747060080927a010000000000000000004ae5c82107d3b01d762bd7250842757420ab88bd9eafa531e45ddd5460ac1c4524aa6a6ad43a0217ae883618").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000420385cfb56a9c0aa212ad2d8cfc3be492cb768e7be66e801000000000000000000e47b35c93528a98e5ccc07f7a64120f10fe44a7d2add76f851f2ef1ff86fb35588aa6a6ad43a0217e74aa794").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000002ae44f541085b616ee2b6b66143d45b8d9d005a13ee58601000000000000000000eb7efe30fbcae6ef197af5303e922d5f13af046afe6722a44a407de33cfb05ecd1aa6a6ad43a0217cc1c1fc5").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c00220ddf782b16cfb698ec47c721caa0e043c6e3045bd7d3f010000000000000000008ebe86fc87779b0ab7b1eeda4f938fa6bff75fdf3f03326d4c3f0f212db1a5df24ac6a6ad43a021787917f4b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000003a8e0ffb97a26cb108666a56d11cfa7e8083858aefd35c00000000000000000000f741327a673e2680ea856c80fe7c45f495f35c0809c79095d2aa49bdac42d8a91eae6a6ad43a0217ee19f61c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0420a12a06c61bdd24754ca5788b5b98127fa4cad6bfc6b4943000000000000000000000c0bc9db8ba6c3cd5e4852713723ab9462ed8cdff9a8e7a59f106a7d1e5d9596223af6a6ad43a021752931fe7").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a0cd2bcda5fed994deb88252ba588292824dafbfed2be7bb8300000000000000000000b021ceecadb5f6f0fcdec2d4dc38cc9021bec331a411769cdbfa1f0d731756c55fb16a6ad43a02174c791b12").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("04e05c24b1a2e59ea42a7424eea8fdb9b95d65865090c6745fec01000000000000000000d49392556462b7cc6a324f3297b95655e8558a842829fa4dbe42a8e8740029149ab16a6ad43a021794875146").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a037310cb4c38e4d2d46702721cf104e9adf7a2120c0ddf85e01000000000000000000309c7334a84c50b5274a7efcc0ee46380415322cb2215a0220d84b7db6f96e6ac3b16a6ad43a0217d06362de").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00001c2a5253d07fb8f9c845066cd8d88ae9cbe590f2239d323a010000000000000000002430bbca343667d2cee8a3d7a86901914c852f38a92dcc8142f694235c3d8bf334b26a6ad43a0217dd6db83c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00805c22ab4c07fb2ce6a683075bde23e7d94f50b588e8f9aa9c00000000000000000000ae3e39b85b23c403d31143839b34cc074e7eef0ecb1bf27c54e324a5be96676d92b26a6ad43a02177008583b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c004200a2cd7ef7703ae1fb2093c550eb744c3ea18108ae7a800000000000000000000d0bada625a70a2fd6cfaa2aede6d72bf4bdd10b64a52289820832323519d62ef43b56a6ad43a021767cab9b1").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("008000206d1cce7f0978d68ffdf92d43c7027bdb81b612641586010000000000000000008d818799a9cada0f4f9243f2cad85b8940125850a9ba37f7d1e6fd282c8c62e145b76a6ad43a02179e855276").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0060af24e8f12bb06b4a71cf56a91e73a3966e14c17208a54d6501000000000000000000fbc1e7250797d3e71b49e1abaef047d8cf7e16f1c0ff9de688182d503d0cae67d6b86a6ad43a02171ebed5cc").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c00220511c75e44d1d627b986749aead0f55ab37f04df146ad0100000000000000000037e94c77e2c639bbec32dc4dd570b31516ea25fd1497156602b5e149f8d391d682c06a6ad43a02176534c3d3").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3f78fb96b3c3c7b29aa1915342bddc921bffd01bfbb63d010000000000000000007a5c1741944ca4332ef03742b971469c0c966655b31d10609a8ffbea38a94cfa2dc56a6ad43a0217a35c947e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e004204870e85ab18fcb38d67c35979efc5df4105490b6713f00000000000000000000c7714efab1a223e7fa21c6f87db62b32cdcd048d5b70e59def0d434de384b3e871c66a6ad43a02178f3d7140").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("004005209f1f0b92d362f5d4f3b1cceab1a7b6e0d2e55e2c38e8010000000000000000000f4e65ca15db0700809fa074c96785754236d3429e2fcf909a0a0cd7e59a31e6c0ce6a6ad43a02172c31d37e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0080e029852c36bc969233723d0d0c7b44c3820b1233edd53c0001000000000000000000b068c891e2d2e65e1d48a74cb19d5e2aba449403d0cc021cf49cec0175226499d5ce6a6ad43a02171413e6c3").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e00020947175283c53840a5fd95ff5fb52a69cbf049640600e00000000000000000000d4ffb8d309f3aaf7023d9e3df8587e63b672b2180b85c0d307cfbf3e334ddb1a5fd76a6ad43a0217d543d85a").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00801d21203bd017379dede3b30858ec121ad3afebe66093deaa00000000000000000000b15433a1853ef7eab266109f32e22af002690eea752fa0afcfd517c8c51957ed5ad76a6ad43a0217e750210f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0040032083cc6f37b94764a79231d6aff125a14f964aa83aef2300000000000000000000605c53cb797ad5e44717e9345170a30375813712b4374440b7b2db0c916fcce34ddd6a6ad43a0217ac7dca95").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e00020d8f1948230933cf727b9d64f1e5786939d595727ca0c0000000000000000000064559da1901f51ddb2307790cd717cdb08421a0d596a119ea26c795695e91fb60ade6a6ad43a0217252accd0").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0060e524ba12f94ea5504e31ef86e8cfaacd1dfde5f2981a0a1202000000000000000000b7768b6df326e64a6091b06c253adf3ad85d58b6900c9f0262ceeff036c65ae969df6a6ad43a02177e5d71b9").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00200a2052a2caca7cbb37e81cafc0d37c29bb530ac9bbc42cc701000000000000000000d2b7cf8533ff1f8f67aa7a9278cea1885a1404465b8d42beee3c22572887406e7be36a6ad43a021723dfac06").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000000220550058d8ed56d8640ee35b6684020f73610626342c900000000000000000000adfcd1ef1d32d7394be8a552ced86e00d4a1fecd68c78e047ea79bf6fc002b7a91e46a6ad43a0217ca6118f3").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0c02658c8f4dfd9d02cccdc514498243a7fefb91f9c49069400000000000000000000a48bf36c7ff5543f078df43076242f3992e00a7cf842e80af8f3e1699acb242db3e76a6ad43a0217637c9ff1").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("002047242bfc14c46c37991c2cef140720b1b3081ef7d87db8d3010000000000000000007f4d937fe1cfa4629e958b43d638b2079ea77e57c9cef7606f07e2fe5e888b18c4e76a6ad43a02178d9c644f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a091278a26924cfba43b61448a7a64c5b1fee904fb8e811cd70000000000000000000008abe2adedf7edfe2fab80f34a7a930b1267f123679414348649a39ff53230808def6a6ad43a021753c783de").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00004830337426f9ece788d4c8241e02589ffea33f898cbe09f501000000000000000000e2e8f5511c6553157451a89067fdcc790097d030bb4bc0690bc17014eef5d2c990f16a6ad43a0217a872716e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e02920546922cd52e7f92111d725d812c1532308eb59fa22ee00000000000000000000fe634310b53673fc6c018af12c64c19060157b1fc3b73d5a5c9caa03cf3565f2baf56a6ad43a0217c76fd370").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0c121fa38469efe9223b571f74e760c96df203d80b08f3b5600000000000000000000f33c1eea09c6d87bbc4d1d2321270c21454495f21ff525534f4e20c85138a592c1f56a6ad43a021792df7b16").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0a126df55ad5154bd8f45fbe0cdb9fc9722783ce4b7a535ac01000000000000000000c533cefab59d506f73176e595df8cea46363e9b5567dff08a163a3c465dc98d14ffc6a6ad43a0217c26d2516").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a09d2b81fd6f7a8e612d81d80d5b5d64859c1b2b2b101bd1fb00000000000000000000800989d2c31d1dc5a3308452b91b1ed40206a030839737953c9d21d6923786451efd6a6ad43a021749e04799").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0d729b34736f399b13a8a2fb7479954d43c2df771c757e23901000000000000000000f7c933bad214186ec2e49c9d1b1585937280868818f320803cf6436e09a8626973016b6ad43a02171b81e870").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00601521556ce1a2354410a18c80b0a67be6dc16257be28890ec010000000000000000003059a134b7eca83de9aca917fabb76ee41d97032583222d6840ef22da16b5b92dd016b6ad43a021725071a5b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00602e2064168b2c362c354444ea0828f36d7cdb3572169b615601000000000000000000a4aeaacb4c25d3a83b2f8ea594325d756ec78f290a73c4953f3cbfaff10e3a3792026b6ad43a02170bb4640f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0040c82357a6c79e2fb6f4d4bf95724a4bcc29bde7b449debdec01000000000000000000fa99bac89c24d15717ad877fecfbf734eb0bc75f8fb578f9470a5db668dbb8fbc0046b6ad43a02174b869a7d").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000034395bcc21a6a2c2d3ccbc2773194d11a7ddfebf0e0919000000000000000000006f505e60f36ce9f73494bfbf0c0f058fee1e2e4822c1b0fa766fb9e3251fd9b38e056b6ad43a02172c4c0184").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800d20354b5388fb81effe12c3291a4cb50cc2adc5b98d079b00000000000000000000103e5e5b3d6d17e134cc0c76af0e91fe08d72591bf4c3ad7b6aa869d5d4cb02ba7066b6ad43a02172202f70c").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00200e20b3001fb8573b91195cb7b9c1de390d9991f3c07978d3010000000000000000003e6addca9c34eee9459e631d9d08e77c2de70703f102ce7903838cc77208f595c7066b6ad43a02173fa4ae35").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("006004209e4646297e4d4fd4f60548b0616a359650c35f1de7f30000000000000000000072aff40819e1e686905c68af2e73b9b951be3736437b416986cf18fd71fb8bd95a0b6b6ad43a021729d33887").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000022ee651093128e4598ff28ca297f351401d19696638b380000000000000000000038d529507d803dcf8080875cf2258aa8a551511c24f8b8ae3fa88423b0602748920b6b6ad43a02170846a89b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00608320be57374d6179c78ada919fffb2d9f908f5e18a74ff0501000000000000000000fa2f2dfb7b4d5235da7f1029804e23edbb7d3323af88199fc0ef1b79a1753459120c6b6ad43a02179a132a56").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a0d227a580e546e615e1c532f47d89de7368c30df20e2e781b01000000000000000000e12705f05a4bbf09b87493335471db4f833e12d99fd36d76c5b3279082dd096e580c6b6ad43a02177e704ef5").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00600f3912f664c2673dde9ab6886a85a2c9fc4efc177a0cbf4c01000000000000000000f1a81ddd80ef01b4a480de488f15402069ae8683ecc46770878b54fb7991c1a57d0d6b6ad43a0217bb384b12").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00803427b9cddcc2df57f4563ae0131adb3dd35bb9917139b95400000000000000000000c0b2ec9353f60fbdf5cf7da0ab0c17a78003bce2052a412c27d12665f76cb555fa136b6ad43a0217066d21e6").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0040ec33bb8f6fa5e2f2ed3cf60cf91d6e79fcf7cde05b11087e01000000000000000000e6075b19a084d7c5aaa8484aeff15d8b5db4db88ad69d5ee647a3d9f778fedda61146b6ad43a0217a27b3033").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("10a0c2227eb8240f150309fcaad8a2be205ddcbf06429a508662010000000000000000007e17cf9282b77723437c083af1aa83482a73fee35c73f110303d402d88fa8bed4c156b6ad43a02174b470b31").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e00320cb4dec5b41a12bc19c29a6d424197f86c3b2ba025e25010000000000000000009d1a60c3889690294fc993f397a4dd1df48b3f9c4bdcb41306c3dedf033e4ee8461b6b6ad43a021763b89d28").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000022052e6e25c9112220845c4c3e7fd939340336d3d33cdb600000000000000000000bfc9920255dcbba49376571e9ce3bcafc87bd36045903ecf4076480d96614b7b051d6b6ad43a02172e1a9b25").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00209b22982e9a7ed287539bce7c3e603687aa2d3125079fb86a010000000000000000004102e56d0ce9029acb48c28745a9dce1783074391f3e70c695afdd392f6ad6d0ef1d6b6ad43a021722025f6d").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("10e0ff3f1bb91d2d09c8f74d7245ec18f7e5b6810a7f461dcdc900000000000000000000ac2b5e4ba6695790ec04953ef4774ece540667da058dc6c0888363c4fe922e0dc81f6b6ad43a021760ea40d6").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0f731f4aa3d9f670eaece00ac84c4c19cc4e2a26ef07851220200000000000000000060ee14b2e84e0f736907009618bc2d8296194d66c7ca0a027c702569990b49090d206b6ad43a0217eeb19de9").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0020f92325a882bfdca063304e330248bdc067d51df8e92bcaf800000000000000000000c753e6732da8e5c98b3376480be610cbdf3cb16d836b59ab6362d2d228efedea29206b6ad43a0217c924f2a1").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a0a220f0dc827b72a458a9ce7550312c0a8750d4f068713efa01000000000000000000cc94b5a5e75437a7c856b29b12da804ddb02fe1353bb210da2606d8180ec6fb631206b6ad43a02174e9d590a").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000082063447f3bc8b428d461b82a070be3db9410ab1fc40f24000000000000000000008dc755c30eaa2c22cc7a4a5792e3656f4bf698d38c41ebc4c85165fb8c9c6ded79206b6ad43a02173e731a1f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("10c05729b6ea9fbe42fb1d9405e47116cb0424329411d017e1aa0000000000000000000076a747223eaa6d45d3f84e2f6828cdc487c0dbdbad42b08a9b8fffc27665a960a7216b6ad43a02174394d82b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a08520af579ef409a189ab162c788896ad825b38faea3641c401000000000000000000dee9be72bd60eda2ab12e0217e2ca16a8bcb34c32f4972b273b77a17440d00e70a226b6ad43a0217116884ce").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a00620482fd173f0a825fc34f487502c1566555f2862f0a02801000000000000000000bb5482aae60bfd220388693225362b18f72e82f9b5ceb9c6d2f9a61f2c28094fe0296b6ad43a0217fb407034").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c00120e0e9282069496b464ef564a9ead03d37ce8e7a9f12c701000000000000000000975e9f48c1083438f32c873d67b2fddfc3c8e130ac9fbc1a53da01c01c3d46c8992a6b6ad43a0217ed908988").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00200723899fc0d3576718c15a6719c4bc71a91bd3ccc96192ec0000000000000000000004fdb6ab38260ca750671d7a6637ef75a651448c82cfc14554d57e2748be2be9722e6b6ad43a02178a807416").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3fbf073df0898e0324336409d64b6b11987fd4465bd82900000000000000000000f4e8fc5a02f4d8f4311eb5fbf73592c88906b0bce6a08b6e321e6c7b19509dfe8f326b6ad43a02175cba391b").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000b2080a47bfbe3066208d4fc39e065d584930a323adb645b0100000000000000000044ad1fa9768359f8528ddcbee3b5f1c773f184fabb6bf87d883e91b0f3f15b89d8316b6ad43a0217329de815").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("006003204682f5d50b7c8bdea389f6c43515d3459584ab50c406010000000000000000003d441907ec00061b73368da7754ad305e6fd796cb4cf9d3410d5a65927d8e6ce74356b6ad43a02174b0fcdc3").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000d3255924665d3d5fe933b16e09b06dcdd3bf7cce977223690000000000000000000024f812c8d99a9596021d3934a646bd50db9ca98931f3228c236952ac222217d3fe356b6ad43a0217effcbbff").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("0000ff3f11bce3edc9ad7d6858a81f240977adb5517ef078ac5600000000000000000000a11d0de0146f427f426965160dbb0db66cebb6eb805bb3af11ee21924f2bdd71383c6b6ad43a0217936d08ca").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e0002007bb096f631e7168dcda31d75201201e543037a0b43300000000000000000000a2a7803b593fbcd881866db771b59f2f98f5f69600460fb6ad553867a1e00a00323e6b6ad43a02170dbeda27").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000000205a18258ccf12385ebd72c8f4f0aa2f5b5c5ec841ce10000000000000000000004a994b5f6c109f3cf616de3c0641711d1d4b848c7a04fe6e243765f67ec9cac875406b6ad43a02170d80ed5a").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0722b62395602530b6d60060a8fce332cd1908f21b30b8be4010000000000000000009508e51190aa63ec2298af3cb644eeb1498c0d83d67680efe30d5fd1f6a95d6d7e426b6ad43a02179c08e6e0").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00a02320b4b25be6c2ba4576586b90f8c4c3732648680cc5c2d901000000000000000000e5865a3d45646d51b65855b4b9a972198fca36241574a652ecfbab67b3f6256f97456b6ad43a021715c30439").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000030ee8847f7a144b68c6e1e0bef2b4183db01bf825d6d05020000000000000000009798b0f81dc791ce7b17eec0d89ef9db7a2b541e9f582fb2387fd9b642418faa6e476b6ad43a0217a06ef431").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00804521440b70d25839dbc3dd0bccd37ef751d50e576188eaa500000000000000000000314ff0850863609b917541ec201a10cd9f47f69cd2d1bf267cdb19fe3e91238bcc486b6ad43a02175e15bd74").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("000065337e07a76349f99f3eaedfbd7ec9b048ae462fc439cddf0100000000000000000000268bf66e3c00ffff66a507729af9df227492b86f436331ebad0241ad055fe97b516b6ad43a021729b64713").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000022b936d35a2f4a755778159e39d6d80b5946bbf15b7c0b01000000000000000000640504d1606de9fce2fd0be5304c553b7f7092bd5f18c291e3fb1c63a973194fc3566b6ad43a021725c6c2d8").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000020301ba83277f8012e1902f97eea3e53d0515ef73319f201000000000000000000f8a415b0a937ef5175c9715ac98e3373e373afad63cbc9c8541c51119d91115fca5b6b6ad43a02176c9b53e1").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00800420ec8823f23684b5a607fa9d6194dc174497f8501a194301000000000000000000af001c7d8797bdc2f8f45149d1f4cd394903e12d59c1f8afbcdbdce3b9925436665d6b6ad43a021776224a13").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00000020465a566082cb8faeeb44e097377a9174a11b65a051ff010000000000000000000b6ef86b33a25b37856c2e7c68543a5066dbe67c2a087c8f4199eb289357b17360616b6ad43a021776fc5e72").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("10e0a02c5d89719ab4dce3ed69c543336776072cdcca1c26514301000000000000000000e860d743e10db71b2b7c5a1fadb2cea11ab7200ba04b4b776fd55d0939dc6cbc61616b6ad43a02171151725e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00606e31bb3cc5e304822bfdf4506cc3b2e0aeb98a7b21280a0f01000000000000000000c897183f710feca8271a8709df053983a6d4489610a73d85b9f0014f58dd770262636b6ad43a0217a723ee06").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00c0812002e5d76425f391738eac0c614d8afc795fde8c0dbf7d010000000000000000008c6bae74a46ad9f2905e5258d4adaf61af2bcf7c87e86a22f084f11710d58e2b36646b6ad43a021795ac6c1e").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00601820856aed25759848cd1ad02caec25afdc0cf17e9ffa0aa01000000000000000000c17d5c0a7bf4af165552c3e91b4d98f4d2afa2938e7b1bdf0912f1e850680a1c53656b6ad43a0217193b3d87").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00e02c3ade01a429e0f5c328824f4ee063b98f24ff3b0dc0db08000000000000000000009d0b402f41ab9c16e08f95de962044b206413f878608abca2015ee2074713078606d6b6ad43a0217c0650150").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("00008f2604b952dcd44dfcb61080ad77c5b626efd760e5f8ac8401000000000000000000ee81697e6cf7f41db37b63a9680906d3023be7a905785ef1b5ebd03fd22dd8bb2d6f6b6ad43a0217287aa17f").expect("Bytes32 from UTXO should be valid"),
|
||||
hex::decode("008017262c315a069a8896ec04d88ef423ff5cb3845320a6f123010000000000000000002d42d9519e62f5a48d4880b0ff0cd8021a38eadd469a2f7aabd0886061026a5026706b6ad43a02172ea14dd2").expect("Bytes32 from UTXO should be valid"),
|
||||
]
|
||||
);
|
||||
|
||||
let thread_proof = ThreadProof::UtxoThreadProof(utxo_thread_proof);
|
||||
let pulled_thread_key = thread_proof.get_unique_key(session);
|
||||
|
||||
assert!(!PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
#[extrinsic_call]
|
||||
pull_thread(RawOrigin::None, network_id, session, thread_proof);
|
||||
|
||||
assert!(PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn pull_evm_thread() -> Result<(), BenchmarkError> {
|
||||
let session = 69;
|
||||
let (network_id, _, _) = prepare_pallet::<T>(1);
|
||||
|
||||
// https://sepolia.etherscan.io/address/0x9cffbdbdf29c67c5dbab1b5e8ae897aeafcaf70c#readContract
|
||||
// getRoot: session 0, atBlock 11383380
|
||||
|
||||
let root_string =
|
||||
hex::decode("f72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d")
|
||||
.expect("EVM Root hex should be valid");
|
||||
let expected_root = H256::from_slice(&root_string);
|
||||
LoomStates::<T>::insert(&network_id, &session, expected_root);
|
||||
|
||||
let receiver_32: [u8; 32] =
|
||||
hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e")
|
||||
.expect("Bytes32 from EVM should be valid")
|
||||
.try_into()
|
||||
.expect("Hex string must be exactly 32 bytes long");
|
||||
|
||||
let receiver_account = sp_runtime::AccountId32::from(receiver_32);
|
||||
let transfer_amount: BalanceOf<T> = 420u64.unique_saturated_into();
|
||||
|
||||
let proof = vec![
|
||||
H256::from_slice(
|
||||
&hex::decode("6dfb19f049f557abff273ce9efb9195924d41e5699b1b7755be2c515b25ef7b6")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("9e0f9c5b8ffa146b74bb614482c0bc65a1ef961a8fa9a3a031a36c97f8c70733")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("890740a8eb06ce9be422cb8da5cdafc2b58c0a5e24036c578de2a433c828ff7d")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("3b8ec09e026fdc305365dfc94e189a81b38c7597b3d941c279f042e8206e0bd8")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("ecd50eee38e386bd62be9bedb990706951b65fe053bd9d8a521af753d139e2da")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("defff6d330bb5403f63b14f33b578274160de3a50df4efecf0e0db73bcdd3da5")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("617bdd11f7c0a11f49db22f629387a12da7596f9d1704d7465177c63d88ec7d7")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
H256::from_slice(
|
||||
&hex::decode("292c23a9aa1d8bea7e2435e555a4a60e379a5a35f3f452bae60121073fb6eead")
|
||||
.expect("Proof should be valid"),
|
||||
),
|
||||
];
|
||||
let slots = vec![H256::from_slice(
|
||||
&hex::decode("2e474dabf492786b3505d7b7cf0bf07c9dbce92c91982fa7344c77e24fd4310d")
|
||||
.expect("Slot should be valid hex"),
|
||||
)];
|
||||
|
||||
let payload = ThreadPayload::new(1, transfer_amount, receiver_account);
|
||||
let evm_thread_proof = EvmThreadProof::new(0, payload, proof, slots);
|
||||
|
||||
let thread_proof = ThreadProof::EvmThreadProof(evm_thread_proof);
|
||||
let pulled_thread_key = thread_proof.get_unique_key(session);
|
||||
|
||||
assert!(!PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
#[extrinsic_call]
|
||||
pull_thread(RawOrigin::None, network_id, session, thread_proof);
|
||||
|
||||
assert!(PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(vec![], vec![]),
|
||||
crate::mock::TestRuntime
|
||||
);
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
use crate::ExternalBlockNumber;
|
||||
|
||||
pub enum WeavingError<NetworkId> {
|
||||
DeadlineReached,
|
||||
NoStoredNetworks,
|
||||
UnparsableRequestBody,
|
||||
UnknownWeavingPhase(NetworkId),
|
||||
OffchainLockPeriod(NetworkId),
|
||||
NoEndpointsAvailable(NetworkId),
|
||||
ContradictoryHashes(NetworkId),
|
||||
ContradictoryBlocks(u32, ExternalBlockNumber, NetworkId),
|
||||
}
|
||||
|
||||
impl<NetworkId: core::fmt::Debug> core::fmt::Debug for WeavingError<NetworkId> {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::NoStoredNetworks => write!(fmt, "No stored networks to be watch on."),
|
||||
Self::UnparsableRequestBody => {
|
||||
write!(fmt, "HTTP request body contains invalid UTF-8 sequences.")
|
||||
}
|
||||
Self::DeadlineReached => {
|
||||
write!(fmt, "The request deadline or network timeout was hit.")
|
||||
}
|
||||
Self::UnknownWeavingPhase(network_id) => {
|
||||
write!(fmt, "Unknown weaving phase for network: {:?}.", network_id)
|
||||
}
|
||||
Self::OffchainLockPeriod(network_id) => write!(
|
||||
fmt,
|
||||
"Storage lock is still held for network: {:?}.",
|
||||
network_id
|
||||
),
|
||||
Self::NoEndpointsAvailable(network_id) => write!(
|
||||
fmt,
|
||||
"Zero RPC URLs configured for network: {:?}.",
|
||||
network_id
|
||||
),
|
||||
Self::ContradictoryHashes(network_id) => write!(
|
||||
fmt,
|
||||
"Divergence among reported hashes for network: {:?}.",
|
||||
network_id
|
||||
),
|
||||
Self::ContradictoryBlocks(len, deviation, network_id) => {
|
||||
write!(fmt, "Divergence among {len} reported blocks exceeds the maximum threshold of {deviation} provided for network: {:?}.", network_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RpcResolverError {
|
||||
JsonDeserializationFailed,
|
||||
UnparsableResponseBody,
|
||||
RpcEmptyResultField,
|
||||
UnknownNetworkType,
|
||||
InvalidResponseId,
|
||||
RpcProtocolError,
|
||||
DeadlineReached,
|
||||
Non200HttpCode,
|
||||
IoError,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for RpcResolverError {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::JsonDeserializationFailed => {
|
||||
write!(fmt, "Failed to deserialize JSON payload into target type.")
|
||||
}
|
||||
Self::UnparsableResponseBody => {
|
||||
write!(fmt, "HTTP response body contains invalid UTF-8 sequences.")
|
||||
}
|
||||
Self::RpcEmptyResultField => write!(
|
||||
fmt,
|
||||
"The 'result' field in the JSON-RPC response is missing or null."
|
||||
),
|
||||
Self::UnknownNetworkType => {
|
||||
write!(fmt, "he provided network type identifier is not supported.")
|
||||
}
|
||||
Self::RpcProtocolError => write!(
|
||||
fmt,
|
||||
"The server returned a protocol-level JSON-RPC error block."
|
||||
),
|
||||
Self::IoError => write!(
|
||||
fmt,
|
||||
"An internal input/output or stream processing error occurred."
|
||||
),
|
||||
Self::Non200HttpCode => {
|
||||
write!(fmt, "Server responded with a non-200 OK HTTP status code.")
|
||||
}
|
||||
Self::DeadlineReached => {
|
||||
write!(fmt, "The request deadline or network timeout was hit.")
|
||||
}
|
||||
Self::InvalidResponseId => write!(fmt, "Response ID does not match request ID."),
|
||||
Self::Unknown => write!(fmt, "An unclassified or generic error occurred."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,412 +0,0 @@
|
||||
use ghost_traits::networks::NetworkRpcResolver;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use sp_std::vec;
|
||||
use serde_json::json;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_core::{H256, U256};
|
||||
use sp_runtime::offchain::http::{Error as HttpError, HttpResult, PendingRequest};
|
||||
|
||||
use crate::{
|
||||
ExternalBlockNumber, NetworkType, RequestId, RpcResolverError, WeavingError, WeavingSession,
|
||||
LOG_TARGET,
|
||||
};
|
||||
|
||||
fn parse_response(
|
||||
http_result: HttpResult,
|
||||
request_id: RequestId,
|
||||
network_type: &NetworkType,
|
||||
) -> Result<NetworkResponseType, RpcResolverError> {
|
||||
let response = http_result.map_err(|err| match err {
|
||||
HttpError::DeadlineReached => RpcResolverError::DeadlineReached,
|
||||
HttpError::IoError => RpcResolverError::IoError,
|
||||
HttpError::Unknown => RpcResolverError::Unknown,
|
||||
})?;
|
||||
|
||||
if response.code != 200 {
|
||||
return Err(RpcResolverError::Non200HttpCode);
|
||||
}
|
||||
|
||||
let response_body = response.body().collect::<Vec<u8>>();
|
||||
let response_str = sp_std::str::from_utf8(&response_body)
|
||||
.map_err(|_| RpcResolverError::UnparsableResponseBody)?;
|
||||
|
||||
let response_data: NetworkResponse = match network_type {
|
||||
NetworkType::Evm => {
|
||||
let evm_response: EvmResponse = serde_json::from_str(response_str)
|
||||
.map_err(|_| RpcResolverError::JsonDeserializationFailed)?;
|
||||
|
||||
evm_response.into()
|
||||
}
|
||||
NetworkType::Utxo => {
|
||||
let utxo_response: UtxoResponse = serde_json::from_str(response_str)
|
||||
.map_err(|_| RpcResolverError::JsonDeserializationFailed)?;
|
||||
|
||||
utxo_response.into()
|
||||
}
|
||||
_ => return Err(RpcResolverError::UnknownNetworkType),
|
||||
};
|
||||
|
||||
if request_id != response_data.id {
|
||||
return Err(RpcResolverError::InvalidResponseId);
|
||||
}
|
||||
|
||||
if response_data.error.is_some() {
|
||||
return Err(RpcResolverError::RpcProtocolError);
|
||||
}
|
||||
|
||||
response_data
|
||||
.result
|
||||
.ok_or(RpcResolverError::RpcEmptyResultField)
|
||||
}
|
||||
|
||||
fn parse_pending_requests<'a, R: Copy + Into<RequestId> + 'static>(
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: R,
|
||||
network_type: &'a NetworkType,
|
||||
) -> impl Iterator<Item = NetworkResponseType> + 'a {
|
||||
PendingRequest::try_wait_all(pending_requests, Some(deadline))
|
||||
.into_iter()
|
||||
.filter_map(move |wait_result| {
|
||||
let http_result = wait_result
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"🕸️ Error during request preparation: {:?}",
|
||||
WeavingError::<u32>::DeadlineReached,
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
parse_response(http_result, request_id.into(), network_type)
|
||||
.inspect_err(|err| {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"🕸️ Error during response parsing: {:?}",
|
||||
err,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
|
||||
impl NetworkRpcResolver<WeavingError<u32>, ExternalBlockNumber, H256, RequestId, WeavingSession>
|
||||
for NetworkType
|
||||
{
|
||||
fn get_block_request_body(&self, id: u64) -> Vec<u8> {
|
||||
let method = match self {
|
||||
NetworkType::Evm => "eth_blockNumber",
|
||||
NetworkType::Utxo => "getblockcount",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
serde_json::to_vec(&json!({"id": id, "jsonrpc": "2.0", "method": method}))
|
||||
.unwrap_or_else(|_| b"{}".to_vec())
|
||||
}
|
||||
|
||||
fn get_hash_request_body(
|
||||
&self,
|
||||
id: u64,
|
||||
target_block: ExternalBlockNumber,
|
||||
weaving_session: WeavingSession,
|
||||
contract_ref: &[u8],
|
||||
selector_ref: &[u8],
|
||||
) -> Vec<u8> {
|
||||
extern crate alloc;
|
||||
|
||||
let (method, params) = match self {
|
||||
NetworkType::Evm => {
|
||||
let contract_hex = hex::encode(contract_ref);
|
||||
let selector_hex = hex::encode(selector_ref);
|
||||
|
||||
let encoded_session = alloc::format!("{:064x}", weaving_session);
|
||||
let encoded_block = alloc::format!("{:064x}", target_block);
|
||||
|
||||
let to_field = alloc::format!("0x{}", contract_hex);
|
||||
let data_field =
|
||||
alloc::format!("0x{}{}{}", selector_hex, encoded_session, encoded_block,);
|
||||
|
||||
(
|
||||
"eth_call",
|
||||
json!([{"to": to_field, "data": data_field}, "latest"]),
|
||||
)
|
||||
}
|
||||
NetworkType::Utxo => ("getblockhash", json!([target_block])),
|
||||
_ => ("unknown", json!([])),
|
||||
};
|
||||
|
||||
serde_json::to_vec(&json!({"id": id, "jsonrpc": "2.0", "method": method, "params": params}))
|
||||
.unwrap_or_else(|_| b"{}".to_vec())
|
||||
}
|
||||
|
||||
fn parse_block_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: RequestId,
|
||||
) -> Vec<ExternalBlockNumber> {
|
||||
parse_pending_requests(pending_requests, deadline, request_id, self)
|
||||
.filter_map(|response| match response {
|
||||
NetworkResponseType::BlockNumber(block) => Some(block),
|
||||
NetworkResponseType::TreeRoot(_, _) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_hash_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: RequestId,
|
||||
) -> Vec<(H256, WeavingSession)> {
|
||||
parse_pending_requests(pending_requests, deadline, request_id, self)
|
||||
.filter_map(|response| match response {
|
||||
NetworkResponseType::TreeRoot(hash, next) => Some((hash, next)),
|
||||
NetworkResponseType::BlockNumber(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Deserialize)]
|
||||
struct NetworkResponse {
|
||||
pub id: RequestId,
|
||||
pub error: Option<serde_json::Value>,
|
||||
pub result: Option<NetworkResponseType>,
|
||||
}
|
||||
|
||||
impl From<EvmResponse> for NetworkResponse {
|
||||
fn from(evm_response: EvmResponse) -> Self {
|
||||
Self {
|
||||
id: evm_response.id,
|
||||
error: evm_response.error,
|
||||
result: evm_response.result.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UtxoResponse> for NetworkResponse {
|
||||
fn from(utxo_response: UtxoResponse) -> Self {
|
||||
Self {
|
||||
id: utxo_response.id,
|
||||
error: utxo_response.error,
|
||||
result: utxo_response.result.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Deserialize)]
|
||||
enum NetworkResponseType {
|
||||
BlockNumber(ExternalBlockNumber),
|
||||
TreeRoot(H256, WeavingSession),
|
||||
}
|
||||
|
||||
impl From<EvmResponseType> for NetworkResponseType {
|
||||
fn from(evm_type: EvmResponseType) -> Self {
|
||||
match evm_type {
|
||||
EvmResponseType::BlockNumber(block) => Self::BlockNumber(block),
|
||||
EvmResponseType::TreeRoot(root, next) => Self::TreeRoot(root, next),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UtxoResponseType> for NetworkResponseType {
|
||||
fn from(utxo_type: UtxoResponseType) -> Self {
|
||||
match utxo_type {
|
||||
UtxoResponseType::BlockNumber(block) => Self::BlockNumber(block),
|
||||
UtxoResponseType::TreeRoot(root) => Self::TreeRoot(root, WeavingSession::MAX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Eq, PartialEq, Clone)]
|
||||
struct EvmResponse {
|
||||
pub id: RequestId,
|
||||
pub error: Option<serde_json::Value>,
|
||||
pub result: Option<EvmResponseType>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Eq, PartialEq, Clone)]
|
||||
struct UtxoResponse {
|
||||
pub id: RequestId,
|
||||
pub error: Option<serde_json::Value>,
|
||||
pub result: Option<UtxoResponseType>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
enum EvmResponseType {
|
||||
#[serde(deserialize_with = "de_hex_string_to_block_number")]
|
||||
BlockNumber(ExternalBlockNumber),
|
||||
#[serde(deserialize_with = "de_contract_tuple")]
|
||||
TreeRoot(H256, WeavingSession),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
enum UtxoResponseType {
|
||||
BlockNumber(ExternalBlockNumber),
|
||||
#[serde(deserialize_with = "de_string_to_h256")]
|
||||
TreeRoot(H256),
|
||||
}
|
||||
|
||||
fn de_hex_string_to_block_number<'de, D>(de: D) -> Result<ExternalBlockNumber, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &'de str = Deserialize::deserialize(de)?;
|
||||
|
||||
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
||||
let trimmed_hex = clean_hex.trim_start_matches('0');
|
||||
|
||||
if trimmed_hex.is_empty() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
ExternalBlockNumber::from_str_radix(trimmed_hex, 16)
|
||||
.map_err(|_| serde::de::Error::custom("Failed to parse block number hex"))
|
||||
}
|
||||
|
||||
fn de_string_to_h256<'de, D>(de: D) -> Result<H256, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &'de str = Deserialize::deserialize(de)?;
|
||||
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
||||
|
||||
let mut bytes = [0u8; 32];
|
||||
hex::decode_to_slice(clean_hex, &mut bytes)
|
||||
.map_err(|_| serde::de::Error::custom("Failed to parse hex string into H256"))?;
|
||||
|
||||
Ok(H256::from(bytes))
|
||||
}
|
||||
|
||||
fn de_contract_tuple<'de, D>(de: D) -> Result<(H256, WeavingSession), D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &str = Deserialize::deserialize(de)?;
|
||||
let clean_hex = s.strip_prefix("0x").unwrap_or(s);
|
||||
|
||||
if clean_hex.len() != 128 {
|
||||
return Err(serde::de::Error::custom(
|
||||
"Invalid hex string length for (bytes32, uint256)",
|
||||
));
|
||||
}
|
||||
|
||||
let (bytes32_hex, uint256_hex) = clean_hex.split_at(64);
|
||||
|
||||
let mut bytes = [0u8; 32];
|
||||
hex::decode_to_slice(bytes32_hex, &mut bytes)
|
||||
.map_err(|_| serde::de::Error::custom("Failed to parse hex string into H256"))?;
|
||||
|
||||
let root_hash = H256::from(bytes);
|
||||
|
||||
let uint256_val = U256::from_str_radix(uint256_hex, 16)
|
||||
.map_err(|_| serde::de::Error::custom("Failed to parse uint256"))?;
|
||||
|
||||
let next_weaving_session: WeavingSession = uint256_val
|
||||
.try_into()
|
||||
.map_err(|_| serde::de::Error::custom("EVM uint256 value overflow bounds"))?;
|
||||
|
||||
Ok((root_hash, next_weaving_session))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod parsing_tests {
|
||||
use super::*;
|
||||
use serde::Deserialize;
|
||||
use sp_core::H256;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TestBlock {
|
||||
#[serde(deserialize_with = "de_hex_string_to_block_number")]
|
||||
val: ExternalBlockNumber,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TestHash {
|
||||
#[serde(deserialize_with = "de_string_to_h256")]
|
||||
val: H256,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TestTuple {
|
||||
#[serde(deserialize_with = "de_contract_tuple")]
|
||||
val: (H256, WeavingSession),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_hex_string_to_block_number_success() {
|
||||
let json = r#"{"val": "0x64"}"#;
|
||||
let parsed: TestBlock = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(parsed.val, 100);
|
||||
|
||||
let json_zeros = r#"{"val": "0x00000a"}"#;
|
||||
let parsed_zeros: TestBlock = serde_json::from_str(json_zeros).unwrap();
|
||||
assert_eq!(parsed_zeros.val, 10);
|
||||
|
||||
let json_zero = r#"{"val": "0x0"}"#;
|
||||
let parsed_zero: TestBlock = serde_json::from_str(json_zero).unwrap();
|
||||
assert_eq!(parsed_zero.val, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_hex_string_to_block_number_invalid() {
|
||||
let json = r#"{"val": "0x6G"}"#;
|
||||
let result: Result<TestBlock, _> = serde_json::from_str(json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_string_to_h256_success() {
|
||||
let raw_hash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let json = format!(r#"{{"val": "0x{}"}}"#, raw_hash);
|
||||
|
||||
let parsed: TestHash = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.val, H256::repeat_byte(0xAA));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_string_to_h256_invalid_length() {
|
||||
let json = r#"{"val": "0x00aa"}"#;
|
||||
let result: Result<TestHash, _> = serde_json::from_str(json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_contract_tuple_success() {
|
||||
let hash_part = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||
let session_part = "000000000000000000000000000000000000000000000000000000000000002a";
|
||||
let json = format!(r#"{{"val": "0x{}{}"}}"#, hash_part, session_part);
|
||||
|
||||
let parsed: TestTuple = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(parsed.val.0, H256::repeat_byte(0xBB));
|
||||
assert_eq!(parsed.val.1, 42u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_contract_tuple_invalid_length() {
|
||||
let json = r#"{"val": "0xbbbb002a"}"#;
|
||||
let result: Result<TestTuple, _> = serde_json::from_str(json);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.err().unwrap().to_string();
|
||||
assert!(err_msg.contains("Invalid hex string length"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_de_contract_tuple_overflow() {
|
||||
let hash_part = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||
let session_overflow = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
||||
let json = format!(r#"{{"val": "0x{}{}"}}"#, hash_part, session_overflow);
|
||||
|
||||
let result: Result<TestTuple, _> = serde_json::from_str(&json);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err_msg = result.err().unwrap().to_string();
|
||||
assert!(err_msg.contains("EVM uint256 value overflow bounds"));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,171 +0,0 @@
|
||||
#![cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
|
||||
use crate::{self as ghost_weaver, *};
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64, Everything},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use sp_core::{Pair, H256};
|
||||
use sp_runtime::{
|
||||
testing::TestXt,
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
AccountId32, BuildStorage,
|
||||
};
|
||||
|
||||
use ghost_helpers::networks::NetworkInitiationBuilder;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||
type Balance = u64;
|
||||
type NetworkId = u32;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum TestRuntime {
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
GhostNetworks: ghost_networks,
|
||||
GhostWeaver: ghost_weaver,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId32;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for TestRuntime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxNetworks: u32 = 5;
|
||||
}
|
||||
|
||||
impl ghost_networks::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type NetworkId = NetworkId;
|
||||
type RegisterOrigin = EnsureRoot<Self::AccountId>;
|
||||
type UpdateOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RemoveOrigin = EnsureRoot<Self::AccountId>;
|
||||
type MaxNetworks = MaxNetworks;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type Extrinsic = TestXt<RuntimeCall, ()>;
|
||||
|
||||
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for TestRuntime
|
||||
where
|
||||
RuntimeCall: From<LocalCall>,
|
||||
{
|
||||
type OverarchingCall = RuntimeCall;
|
||||
type Extrinsic = TestXt<RuntimeCall, ()>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const UnsignedPriority: u64 = 100;
|
||||
pub const WeavingDelay: u64 = 10;
|
||||
pub const AttestationDelay: u64 = 3;
|
||||
}
|
||||
|
||||
impl ghost_weaver::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AuthorityId = ghost_weaver::sr25519::AuthorityId;
|
||||
type Currency = Balances;
|
||||
type NetworkDataHandler = GhostNetworks;
|
||||
type BlockNumberProvider = System;
|
||||
type MaxAuthorities = ConstU32<20>;
|
||||
type MaxAuthoritiesChunks = ConstU32<1>;
|
||||
type WeavingDelay = WeavingDelay;
|
||||
type AttestationDelay = AttestationDelay;
|
||||
type UnsignedPriority = UnsignedPriority;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn new_test_ext(
|
||||
authorities: Vec<sr25519::AuthorityPair>,
|
||||
networks_raw: Vec<(NetworkId, NetworkData, Balance)>,
|
||||
) -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<TestRuntime>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
let genesis_authorities = authorities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, pair)| {
|
||||
let mut id_bytes = [0u8; 32];
|
||||
let id_u64 = (i + 1) as u64;
|
||||
id_bytes[0..8].copy_from_slice(&id_u64.to_le_bytes());
|
||||
|
||||
let account_id = AccountId32::from(id_bytes);
|
||||
(account_id, pair.public().clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let networks = networks_raw
|
||||
.into_iter()
|
||||
.map(|(network_id, network_data, amount)| {
|
||||
NetworkInitiationBuilder::default()
|
||||
.with_network_id(network_id)
|
||||
.with_network_data(network_data)
|
||||
.with_gatekeeper_amount(amount)
|
||||
.build()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ghost_networks::GenesisConfig::<TestRuntime> { networks }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
ghost_weaver::GenesisConfig::<TestRuntime> {
|
||||
authorities: genesis_authorities,
|
||||
loom_states: vec![],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let keystore = sp_keystore::testing::MemoryKeystore::new();
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
|
||||
ext.register_extension(sp_keystore::KeystoreExt::new(keystore));
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
|
||||
ext
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,598 +0,0 @@
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{pallet_prelude::ConstU32, BoundedVec};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_core::{H256, U256};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{AtLeast32BitUnsigned, Saturating, UniqueSaturatedInto},
|
||||
AccountId32, RuntimeDebug,
|
||||
};
|
||||
|
||||
use ghost_helpers::{
|
||||
hash_chain::{cumulative_hash, sequential_hash, verify_hash_ancestry},
|
||||
merkle_tree::verify_tree_proof,
|
||||
SubstrateBlake2Hasher, SubstrateKeccakHasher, UtxoSha2Hasher,
|
||||
};
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
|
||||
use crate::{ExternalBlockNumber, ThreadId, WeavingSession};
|
||||
|
||||
const EVM_MERKLE_DEPTH: u32 = 8;
|
||||
const EVM_VALUES_DEPTH: u32 = 8;
|
||||
|
||||
const UTXO_MAX_PROOF_BYTES: u32 = 1024;
|
||||
const UTXO_MAX_HEADER_BYTES: u32 = 80;
|
||||
const UTXO_MAX_HISTORY_DEPTH: u32 = 144;
|
||||
const UTXO_MAX_TX_BYTES: u32 = ghost_helpers::utxo::MAX_TX_LEN as u32;
|
||||
|
||||
pub enum WeavingOk<NetworkId> {
|
||||
Block(ExternalBlockNumber, NetworkId),
|
||||
Hash(ExternalBlockNumber, H256, WeavingSession, NetworkId),
|
||||
}
|
||||
|
||||
impl<NetworkId> core::fmt::Display for WeavingOk<NetworkId>
|
||||
where
|
||||
NetworkId: core::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Block(block, network_id) => write!(fmt, "Verified block height #{:?} found for network: {:?}.", block, network_id),
|
||||
Self::Hash(block, hash, next_session, network_id) => write!(fmt, "Extracted Merkle Root {:?} where next session is {:?}; from block #{:?} for network: {:?}.", hash, next_session, block, network_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
RuntimeDebug,
|
||||
Clone,
|
||||
Copy,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct WeavingState<AuthIndex> {
|
||||
pub hash: H256,
|
||||
pub count: AuthIndex,
|
||||
pub next_session: WeavingSession,
|
||||
}
|
||||
|
||||
impl<AuthIndex> WeavingState<AuthIndex> {
|
||||
pub fn new(hash: H256, next_session: WeavingSession, count: AuthIndex) -> Self {
|
||||
Self {
|
||||
hash,
|
||||
next_session,
|
||||
count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
RuntimeDebug,
|
||||
Clone,
|
||||
Copy,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct AttestationContext<Block, NetworkId, AuthIndex> {
|
||||
block: Block,
|
||||
network_id: NetworkId,
|
||||
authority_index: AuthIndex,
|
||||
weaving_session: WeavingSession,
|
||||
}
|
||||
|
||||
pub trait AttestationMetadata<Block, NetworkId, AuthIndex> {
|
||||
fn context(&self) -> &AttestationContext<Block, NetworkId, AuthIndex>;
|
||||
|
||||
fn block(&self) -> Block
|
||||
where
|
||||
Block: Copy,
|
||||
{
|
||||
self.context().block
|
||||
}
|
||||
|
||||
fn network_id(&self) -> NetworkId
|
||||
where
|
||||
NetworkId: Copy,
|
||||
{
|
||||
self.context().network_id
|
||||
}
|
||||
|
||||
fn authority_index(&self) -> AuthIndex
|
||||
where
|
||||
AuthIndex: Copy,
|
||||
{
|
||||
self.context().authority_index
|
||||
}
|
||||
|
||||
fn weaving_session(&self) -> WeavingSession {
|
||||
self.context().weaving_session
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, NetworkId, AuthIndex> AttestationContext<Block, NetworkId, AuthIndex> {
|
||||
pub fn with_current_block(mut self, block: Block) -> Self {
|
||||
self.block = block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
|
||||
self.network_id = network_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weaving_session(mut self, weaving_session: WeavingSession) -> Self {
|
||||
self.weaving_session = weaving_session;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_block_attestation(
|
||||
self,
|
||||
external_block: ExternalBlockNumber,
|
||||
) -> BlockAttestation<Block, NetworkId, AuthIndex> {
|
||||
BlockAttestation::<Block, NetworkId, AuthIndex> {
|
||||
context: self,
|
||||
external_block,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_hash_attestation(
|
||||
self,
|
||||
root_hash: H256,
|
||||
next_session: WeavingSession,
|
||||
) -> HashAttestation<Block, NetworkId, AuthIndex> {
|
||||
HashAttestation::<Block, NetworkId, AuthIndex> {
|
||||
root_hash,
|
||||
next_session,
|
||||
context: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
RuntimeDebug,
|
||||
Clone,
|
||||
Copy,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct BlockAttestation<Block, NetworkId, AuthIndex> {
|
||||
context: AttestationContext<Block, NetworkId, AuthIndex>,
|
||||
pub external_block: ExternalBlockNumber,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
RuntimeDebug,
|
||||
Clone,
|
||||
Copy,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct HashAttestation<Block, NetworkId, AuthIndex> {
|
||||
context: AttestationContext<Block, NetworkId, AuthIndex>,
|
||||
pub next_session: WeavingSession,
|
||||
pub root_hash: H256,
|
||||
}
|
||||
|
||||
impl<Block, NetworkId, AuthIndex> AttestationMetadata<Block, NetworkId, AuthIndex>
|
||||
for BlockAttestation<Block, NetworkId, AuthIndex>
|
||||
{
|
||||
fn context(&self) -> &AttestationContext<Block, NetworkId, AuthIndex> {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, NetworkId, AuthIndex> AttestationMetadata<Block, NetworkId, AuthIndex>
|
||||
for HashAttestation<Block, NetworkId, AuthIndex>
|
||||
{
|
||||
fn context(&self) -> &AttestationContext<Block, NetworkId, AuthIndex> {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct NetworkAttestation<Block, BoundedMap>
|
||||
where
|
||||
Block: AtLeast32BitUnsigned + Encode + Decode + TypeInfo + MaxEncodedLen + Default,
|
||||
BoundedMap: Encode + Decode + TypeInfo + MaxEncodedLen + Default + Clone,
|
||||
{
|
||||
pub until: Block,
|
||||
pub map: BoundedMap,
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct TapestryDraft<Block: AtLeast32BitUnsigned> {
|
||||
pub until: Block,
|
||||
pub median: ExternalBlockNumber,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct TapestryDraftBuilder<Block, Delay>
|
||||
where
|
||||
Block: Saturating + AtLeast32BitUnsigned + Copy,
|
||||
Delay: UniqueSaturatedInto<Block> + AtLeast32BitUnsigned + Copy,
|
||||
{
|
||||
delay: Delay,
|
||||
block: Block,
|
||||
median: ExternalBlockNumber,
|
||||
}
|
||||
|
||||
impl<Block, Delay> TapestryDraftBuilder<Block, Delay>
|
||||
where
|
||||
Block: Saturating + AtLeast32BitUnsigned + Copy,
|
||||
Delay: UniqueSaturatedInto<Block> + AtLeast32BitUnsigned + Copy,
|
||||
{
|
||||
pub fn with_block_delay(mut self, delay: Delay) -> Self {
|
||||
self.delay = delay;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_block(mut self, block: Block) -> Self {
|
||||
self.block = block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_median_external(mut self, median: ExternalBlockNumber) -> Self {
|
||||
self.median = median;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> TapestryDraft<Block> {
|
||||
let delay_converted: Block = self.delay.unique_saturated_into();
|
||||
let until = self.block.saturating_add(delay_converted);
|
||||
TapestryDraft {
|
||||
until,
|
||||
median: self.median,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub struct LoomState {
|
||||
pub hash: H256,
|
||||
pub pulled_threads: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct ThreadPayload<Balance: UniqueSaturatedInto<u128>> {
|
||||
proof_index: ThreadId,
|
||||
amount: Balance,
|
||||
receiver: AccountId32,
|
||||
}
|
||||
|
||||
impl<Balance> ThreadPayload<Balance>
|
||||
where
|
||||
Balance: UniqueSaturatedInto<u128>,
|
||||
{
|
||||
pub fn new(proof_index: ThreadId, amount: Balance, receiver: AccountId32) -> Self {
|
||||
Self {
|
||||
proof_index,
|
||||
amount,
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub enum ThreadProof<Balance: UniqueSaturatedInto<u128>> {
|
||||
EvmThreadProof(EvmThreadProof<Balance>),
|
||||
UtxoThreadProof(UtxoThreadProof<Balance>),
|
||||
}
|
||||
|
||||
impl<Balance> ThreadProof<Balance>
|
||||
where
|
||||
Balance: Copy + Clone + Eq + PartialEq + UniqueSaturatedInto<u128>,
|
||||
{
|
||||
pub fn is_evm_proof_correct(&self) -> bool {
|
||||
match self {
|
||||
Self::EvmThreadProof(_) => true,
|
||||
Self::UtxoThreadProof(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_utxo_proof_correct(&self) -> bool {
|
||||
match self {
|
||||
Self::UtxoThreadProof(_) => true,
|
||||
Self::EvmThreadProof(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(&self) -> Balance {
|
||||
match self {
|
||||
Self::UtxoThreadProof(utxo_thread) => utxo_thread.amount(),
|
||||
Self::EvmThreadProof(evm_thread) => evm_thread.amount(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proof_index(&self) -> ThreadId {
|
||||
match self {
|
||||
Self::UtxoThreadProof(utxo_thread) => utxo_thread.proof_index(),
|
||||
Self::EvmThreadProof(evm_thread) => evm_thread.proof_index(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_proof(&self, root_hash: H256, gatekeeper: &[u8]) -> Option<AccountId32> {
|
||||
match self {
|
||||
Self::UtxoThreadProof(utxo_thread) => utxo_thread.verify_proof(root_hash, gatekeeper),
|
||||
Self::EvmThreadProof(evm_thread) => evm_thread.verify_proof(root_hash),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unique_key(&self, session: WeavingSession) -> H256 {
|
||||
match self {
|
||||
Self::EvmThreadProof(thread_info) => thread_info.get_unique_key(session),
|
||||
Self::UtxoThreadProof(utxo_thread) => utxo_thread.get_unique_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct EvmThreadProof<Balance: UniqueSaturatedInto<u128>> {
|
||||
slot_index: ThreadId,
|
||||
payload: ThreadPayload<Balance>,
|
||||
proof: BoundedVec<H256, ConstU32<EVM_MERKLE_DEPTH>>,
|
||||
slots: BoundedVec<H256, ConstU32<EVM_VALUES_DEPTH>>,
|
||||
}
|
||||
|
||||
impl<Balance> EvmThreadProof<Balance>
|
||||
where
|
||||
Balance: Copy + Clone + Eq + PartialEq + UniqueSaturatedInto<u128>,
|
||||
{
|
||||
pub fn amount(&self) -> Balance {
|
||||
self.payload.amount.clone()
|
||||
}
|
||||
pub fn proof_index(&self) -> ThreadId {
|
||||
self.payload.proof_index
|
||||
}
|
||||
|
||||
pub fn verify_proof(&self, root_hash: H256) -> Option<AccountId32> {
|
||||
if EVM_MERKLE_DEPTH as usize != self.proof.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let proof_valid = verify_tree_proof::<SubstrateKeccakHasher, _>(
|
||||
&self.get_preimage_slice().to_fixed_bytes(),
|
||||
self.proof.as_ref(),
|
||||
root_hash,
|
||||
self.payload.proof_index,
|
||||
);
|
||||
|
||||
if !proof_valid {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(self.payload.receiver.clone())
|
||||
}
|
||||
|
||||
fn get_preimage_slice(&self) -> H256 {
|
||||
let slot_index_usize: usize = self.slot_index.unique_saturated_into();
|
||||
|
||||
(slot_index_usize < self.slots.len())
|
||||
.then(|| {
|
||||
self.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, &preimage)| {
|
||||
if index == slot_index_usize {
|
||||
self.compute_arguments_hash()
|
||||
} else {
|
||||
preimage
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.map(|slots| cumulative_hash::<SubstrateKeccakHasher, _>(slots))
|
||||
.unwrap_or(SubstrateKeccakHasher::empty())
|
||||
}
|
||||
|
||||
pub fn get_unique_key(&self, session: WeavingSession) -> H256 {
|
||||
let session_bytes = (session as u64).to_be_bytes();
|
||||
let slot_bytes = (self.slot_index as u64).to_be_bytes();
|
||||
let proof_bytes = (self.payload.proof_index as u64).to_be_bytes();
|
||||
|
||||
let mut h1 = [0u8; 32]; h1[24..32].copy_from_slice(&session_bytes);
|
||||
let mut h2 = [0u8; 32]; h2[24..32].copy_from_slice(&slot_bytes);
|
||||
let mut h3 = [0u8; 32]; h3[24..32].copy_from_slice(&proof_bytes);
|
||||
|
||||
let metadata: [H256; 3] = [H256::from(h1), H256::from(h2), H256::from(h3)];
|
||||
|
||||
sequential_hash::<SubstrateBlake2Hasher, _>(metadata)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
slot_index: ThreadId,
|
||||
payload: ThreadPayload<Balance>,
|
||||
proof_vec: Vec<H256>,
|
||||
slots_vec: Vec<H256>,
|
||||
) -> Self {
|
||||
let proof = BoundedVec::<H256, ConstU32<EVM_MERKLE_DEPTH>>::try_from(proof_vec)
|
||||
.expect("Should be valid EVM proof");
|
||||
|
||||
let slots = BoundedVec::<H256, ConstU32<EVM_MERKLE_DEPTH>>::try_from(slots_vec)
|
||||
.expect("Should be valud EVM proof");
|
||||
|
||||
Self {
|
||||
slot_index,
|
||||
payload,
|
||||
proof,
|
||||
slots,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_arguments_hash(&self) -> H256 {
|
||||
let amount_u128: u128 = self.amount().unique_saturated_into();
|
||||
let amount_u256 = U256::from(amount_u128);
|
||||
|
||||
let slot_index_u64: u64 = self.slot_index.unique_saturated_into();
|
||||
let slot_index_u256 = U256::from(slot_index_u64);
|
||||
|
||||
let mut padded_amount = [0u8; 32];
|
||||
amount_u256.to_big_endian(&mut padded_amount);
|
||||
|
||||
let mut padded_slot_index = [0u8; 32];
|
||||
slot_index_u256.to_big_endian(&mut padded_slot_index);
|
||||
|
||||
let inputs: [&[u8]; 3] = [
|
||||
padded_amount.as_ref(),
|
||||
self.payload.receiver.as_ref(),
|
||||
padded_slot_index.as_ref(),
|
||||
];
|
||||
sequential_hash::<SubstrateKeccakHasher, _>(inputs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
|
||||
pub struct UtxoThreadProof<Balance: UniqueSaturatedInto<u128>> {
|
||||
o_index: ThreadId,
|
||||
w_index: ThreadId,
|
||||
expected_amount: Balance,
|
||||
payload: ThreadPayload<Balance>,
|
||||
|
||||
gettxproof_bytes: BoundedVec<u8, ConstU32<UTXO_MAX_PROOF_BYTES>>,
|
||||
getrawtransaction_bytes: BoundedVec<u8, ConstU32<UTXO_MAX_TX_BYTES>>,
|
||||
ancestry_headers: BoundedVec<
|
||||
BoundedVec<u8, ConstU32<UTXO_MAX_HEADER_BYTES>>,
|
||||
ConstU32<UTXO_MAX_HISTORY_DEPTH>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<Balance> UtxoThreadProof<Balance>
|
||||
where
|
||||
Balance: Copy + Clone + Eq + PartialEq + UniqueSaturatedInto<u128>,
|
||||
{
|
||||
pub fn amount(&self) -> Balance {
|
||||
self.expected_amount
|
||||
}
|
||||
pub fn receiver(&self) -> AccountId32 {
|
||||
self.payload.receiver.clone()
|
||||
}
|
||||
pub fn proof_index(&self) -> ThreadId {
|
||||
self.payload.proof_index
|
||||
}
|
||||
|
||||
pub fn verify_proof(&self, root_hash: H256, gatekeeper_ref: &[u8]) -> Option<AccountId32> {
|
||||
use ghost_helpers::utxo::{Header, Transaction};
|
||||
|
||||
if self.gettxproof_bytes.len() < 85 {
|
||||
return None;
|
||||
}
|
||||
let header_bytes = &self.gettxproof_bytes[..80];
|
||||
|
||||
let is_chain_valid = verify_hash_ancestry::<UtxoSha2Hasher, _>(
|
||||
header_bytes,
|
||||
self.ancestry_headers.iter(),
|
||||
root_hash,
|
||||
);
|
||||
if !is_chain_valid {
|
||||
return None;
|
||||
}
|
||||
|
||||
let transaction =
|
||||
Transaction::<UtxoSha2Hasher>::try_from(self.getrawtransaction_bytes.as_ref()).ok()?;
|
||||
let header = Header::<UtxoSha2Hasher>::try_from(self.gettxproof_bytes.as_ref()).ok()?;
|
||||
|
||||
let txid_preimage = transaction.compute_txid()?;
|
||||
|
||||
let root_hash = header.extract_proof(txid_preimage).ok()?;
|
||||
|
||||
if root_hash != header.merkle_root {
|
||||
return None;
|
||||
}
|
||||
|
||||
let account_id_bytes = transaction.validate(
|
||||
self.o_index,
|
||||
self.w_index,
|
||||
gatekeeper_ref,
|
||||
self.expected_amount,
|
||||
)?;
|
||||
|
||||
Some(AccountId32::new(account_id_bytes))
|
||||
}
|
||||
|
||||
pub fn get_unique_key(&self) -> H256 {
|
||||
let o_index_bytes = (self.o_index as u64).to_be_bytes();
|
||||
let mut o_index_raw = [0u8; 32];
|
||||
o_index_raw[24..32].copy_from_slice(&o_index_bytes);
|
||||
|
||||
let o_index_hash = H256::from(o_index_raw);
|
||||
let metadata: &[&[u8]] = &[
|
||||
self.gettxproof_bytes.as_ref(),
|
||||
self.getrawtransaction_bytes.as_ref(),
|
||||
o_index_hash.as_ref(),
|
||||
];
|
||||
|
||||
sequential_hash::<SubstrateBlake2Hasher, _>(metadata)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
o_index: ThreadId,
|
||||
w_index: ThreadId,
|
||||
expected_amount: Balance,
|
||||
payload: ThreadPayload<Balance>,
|
||||
gettxproof_vec: Vec<u8>,
|
||||
getrawtransaction_vec: Vec<u8>,
|
||||
ancestry_headers_vec: Vec<Vec<u8>>,
|
||||
) -> Self {
|
||||
let gettxproof_bytes =
|
||||
BoundedVec::<u8, ConstU32<UTXO_MAX_PROOF_BYTES>>::try_from(gettxproof_vec)
|
||||
.expect("Should be valid UTXO gettxproof");
|
||||
|
||||
let getrawtransaction_bytes =
|
||||
BoundedVec::<u8, ConstU32<UTXO_MAX_TX_BYTES>>::try_from(getrawtransaction_vec)
|
||||
.expect("Should be valid UTXO proof");
|
||||
|
||||
let inner_bounded_headers = ancestry_headers_vec
|
||||
.into_iter()
|
||||
.map(|raw_header| {
|
||||
BoundedVec::<u8, ConstU32<UTXO_MAX_HEADER_BYTES>>::try_from(raw_header)
|
||||
.expect("Should be valid header")
|
||||
})
|
||||
.collect::<Vec<BoundedVec<u8, ConstU32<UTXO_MAX_HEADER_BYTES>>>>();
|
||||
|
||||
let ancestry_headers = BoundedVec::<
|
||||
BoundedVec<u8, ConstU32<UTXO_MAX_HEADER_BYTES>>,
|
||||
ConstU32<UTXO_MAX_HISTORY_DEPTH>,
|
||||
>::try_from(inner_bounded_headers)
|
||||
.expect("Should be valud UTXO headers");
|
||||
|
||||
Self {
|
||||
o_index,
|
||||
w_index,
|
||||
expected_amount,
|
||||
gettxproof_bytes,
|
||||
getrawtransaction_bytes,
|
||||
ancestry_headers,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn extend_warp() -> Weight;
|
||||
fn weave_weft() -> Weight;
|
||||
fn pull_evm_thread() -> Weight;
|
||||
fn pull_utxo_thread() -> Weight;
|
||||
}
|
||||
|
||||
impl WeightInfo for () {
|
||||
fn extend_warp() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn weave_weft() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn pull_evm_thread() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
fn pull_utxo_thread() -> Weight {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user