Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

183 changed files with 11905 additions and 46655 deletions

1223
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ homepage.workspace = true
[workspace.package] [workspace.package]
license = "GPL-3.0-only" license = "GPL-3.0-only"
authors = ["571nky", "57r37ch", "f4750"] authors = ["571nky", "57r37ch", "f4750"]
version = "0.9.3" version = "0.8.2"
edition = "2021" edition = "2021"
homepage = "https://ghostchain.io" homepage = "https://ghostchain.io"
repository = "https://git.ghostchain.io/ghostchain/ghost-node" repository = "https://git.ghostchain.io/ghostchain/ghost-node"
@ -67,21 +67,18 @@ serial_test = { version = "2.0.0" }
color-eyre = { version = "0.6.1" } color-eyre = { version = "0.6.1" }
bs58 = { version = "0.5.0" } bs58 = { version = "0.5.0" }
prometheus-parse = { version = "0.2.2" } prometheus-parse = { version = "0.2.2" }
k256 = { version = "0.13.0", default-features = false }
rustc-hex = { version = "2.1.0", default-features = false } rustc-hex = { version = "2.1.0", default-features = false }
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
num-traits = { version = "0.2.17", default-features = false } num-traits = { version = "0.2.17", default-features = false }
libsecp256k1 = { version = "0.7", default-features = false } libsecp256k1 = { version = "0.7", default-features = false }
bip39 = { package = "parity-bip39", version = "2.0.1" } bip39 = { package = "parity-bip39", version = "2.0.1" }
sha3 = { version = "0.10", default-features = false } sha3 = { version = "0.10", default-features = false }
sha2 = { version = "0.10.7", default-features = false }
serde = { version = "1.0.197", default-features = false } serde = { version = "1.0.197", default-features = false }
serde_derive = { version = "1.0.117", default-features = false } serde_derive = { version = "1.0.117", default-features = false }
serde_json = { version = "1.0.114", default-features = false } serde_json = { version = "1.0.114", default-features = false }
blake2-rfc = { version = "0.2.18", default-features = false } blake2-rfc = { version = "0.2.18", default-features = false }
impl-serde = { version = "0.4.0", default-features = false } impl-serde = { version = "0.4.0", default-features = false }
hex = { version = "0.4.3", 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 } metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false }
scale-value = { version = "0.16.0" } 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 } scale-info = { version = "2.5.0", default-features = false }
subxt = { version = "0.37.0", default-features = false } subxt = { version = "0.37.0", default-features = false }
frost-secp256k1-tr = { 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 # Frames
frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } 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 } 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-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-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 = { 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-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-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 } 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-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-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-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-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-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 } 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-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-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-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 # Substrate Client dependencies
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" } 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 } ghost-rpc = { path = "rpc", default-features = false }
service = { package = "ghost-service", path = "service", default-features = false } service = { package = "ghost-service", path = "service", default-features = false }
ghost-traits = { path = "pallets/traits", 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-networks = { path = "pallets/networks", default-features = false }
ghost-governance = { path = "pallets/governance", default-features = false } ghost-claims = { path = "pallets/claims", default-features = false }
ghost-weaver = { path = "pallets/weaver", default-features = false } ghost-slow-clap = { path = "pallets/slow-clap", default-features = false }
ghost-exodus = { path = "pallets/exodus", default-features = false }
ghost-sudo = { path = "pallets/sudo", 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 } ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false }
casper-runtime = { path = "runtime/casper", 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 } 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 } 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] [dependencies]
color-eyre = { workspace = true } color-eyre = { workspace = true }
@ -285,13 +271,9 @@ members = [
"runtime/casper", "runtime/casper",
"runtime/casper/constants", "runtime/casper/constants",
"pallets/networks", "pallets/networks",
"pallets/governance", "pallets/claims",
"pallets/pools", "pallets/slow-clap",
"pallets/sudo", "pallets/sudo",
"pallets/exodus",
"pallets/exodus/runtime-api",
"pallets/helpers",
"pallets/weaver",
"utils/bags-list", "utils/bags-list",
"utils/chain-spec-builder", "utils/chain-spec-builder",
"utils/generate-bags", "utils/generate-bags",
@ -344,12 +326,9 @@ crypto-mac = { opt-level = 3 }
curve25519-dalek = { opt-level = 3 } curve25519-dalek = { opt-level = 3 }
ed25519-dalek = { opt-level = 3 } ed25519-dalek = { opt-level = 3 }
flate2 = { opt-level = 3 } flate2 = { opt-level = 3 }
frost-core = { opt-level = 3 }
frost-secp256k1-tr = { opt-level = 3 }
futures-channel = { opt-level = 3 } futures-channel = { opt-level = 3 }
hash-db = { opt-level = 3 } hash-db = { opt-level = 3 }
hashbrown = { opt-level = 3 } hashbrown = { opt-level = 3 }
hkdf = { opt-level = 3 }
hmac = { opt-level = 3 } hmac = { opt-level = 3 }
httparse = { opt-level = 3 } httparse = { opt-level = 3 }
integer-sqrt = { opt-level = 3 } integer-sqrt = { opt-level = 3 }
@ -365,7 +344,6 @@ parking_lot = { opt-level = 3 }
parking_lot_core = { opt-level = 3 } parking_lot_core = { opt-level = 3 }
percent-encoding = { opt-level = 3 } percent-encoding = { opt-level = 3 }
primitive-types = { opt-level = 3 } primitive-types = { opt-level = 3 }
rand_chacha = { opt-level = 3 }
ring = { opt-level = 3 } ring = { opt-level = 3 }
rustls = { opt-level = 3 } rustls = { opt-level = 3 }
sha2 = { opt-level = 3 } sha2 = { opt-level = 3 }

View File

@ -346,7 +346,7 @@ sha256sum /etc/ghost/casper.json
You should see: You should see:
``` ```
4645c7d39a64424d823f659d408070f8c90426117e7af147b0360697b2db7f4f /etc/ghost/casper.json 9da6045ed6d4fd844e3939573b31c013d0e564e542029330faa6d978cb4a915a
``` ```
Create running `ghost-node` service that starts on system boot using `--unit-file` flag. Create running `ghost-node` service that starts on system boot using `--unit-file` flag.

View File

@ -55,7 +55,6 @@ pub enum Subcommand {
/// Revert the chain to a previous state. /// Revert the chain to a previous state.
Revert(sc_cli::RevertCmd), Revert(sc_cli::RevertCmd),
#[cfg(feature = "runtime-benchmarks")]
/// The custom benchmark subcommmand benchmarking runtime pallets. /// The custom benchmark subcommmand benchmarking runtime pallets.
#[clap(subcommand)] #[clap(subcommand)]
Benchmark(frame_benchmarking_cli::BenchmarkCmd), Benchmark(frame_benchmarking_cli::BenchmarkCmd),

View File

@ -1,10 +1,13 @@
use crate::cli::{Cli, Subcommand}; use crate::cli::{Cli, Subcommand};
use futures::future::TryFutureExt;
use sc_cli::SubstrateCli;
use service::{self, IdentifyVariant};
#[cfg(feature = "runtime-benchmarks")]
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory}; use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
use futures::future::TryFutureExt;
use keyring::Sr25519Keyring;
use sc_cli::SubstrateCli;
use service::{
self,
benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder},
IdentifyVariant,
};
pub use crate::{error::Error, service::BlockId}; pub use crate::{error::Error, service::BlockId};
#[cfg(feature = "pyroscope")] #[cfg(feature = "pyroscope")]
@ -64,11 +67,11 @@ impl SubstrateCli for Cli {
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper" => Box::new(service::chain_spec::casper_config()?), "casper" => Box::new(service::chain_spec::casper_config()?),
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper-dev" | "casper-development" => { "casper-dev" | "dev" | "development" => {
Box::new(service::chain_spec::casper_development_config()?) Box::new(service::chain_spec::casper_development_config()?)
} }
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper-local" => { "casper-local" | "local" => {
Box::new(service::chain_spec::casper_local_testnet_config()?) Box::new(service::chain_spec::casper_local_testnet_config()?)
} }
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
@ -123,10 +126,7 @@ where
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
runner.run_node_until_exit(move |mut config| async move { runner.run_node_until_exit(move |config| async move {
config.offchain_worker.enabled = true;
config.offchain_worker.indexing_enabled = true;
let hwbench = (!cli.run.no_hardware_benchmarks) let hwbench = (!cli.run.no_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| { .then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(&database_path); let _ = std::fs::create_dir_all(&database_path);
@ -276,13 +276,22 @@ pub fn run() -> Result<()> {
task_manager, task_manager,
)) ))
})?) })?)
}, }
#[cfg(feature = "runtime-benchmarks")]
Some(Subcommand::Benchmark(cmd)) => { Some(Subcommand::Benchmark(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
match cmd { match cmd {
#[cfg(not(feature = "runtime-benchmarks"))]
BenchmarkCmd::Storage(_) => {
return Err(sc_cli::Error::Input(
"Compile with `--feature=runtime-benchmarks \
to enable storage benchmarks."
.into(),
)
.into())
}
#[cfg(feature = "runtime-benchmarks")]
BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| {
let (client, backend, _, _) = service::new_chain_ops(&mut config)?; let (client, backend, _, _) = service::new_chain_ops(&mut config)?;
let db = backend.expose_db(); let db = backend.expose_db();
@ -297,8 +306,6 @@ pub fn run() -> Result<()> {
}), }),
// These commands are very similar and can be handled in nearly the same way // These commands are very similar and can be handled in nearly the same way
BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => { BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
use service::benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder};
runner.sync_run(|mut config| { runner.sync_run(|mut config| {
let (client, _, _, _) = service::new_chain_ops(&mut config)?; let (client, _, _, _) = service::new_chain_ops(&mut config)?;
@ -312,7 +319,7 @@ pub fn run() -> Result<()> {
BenchmarkCmd::Extrinsic(cmd) => { BenchmarkCmd::Extrinsic(cmd) => {
let tka_builder = TransferKeepAliveBuilder::new( let tka_builder = TransferKeepAliveBuilder::new(
client.clone(), client.clone(),
keyring::Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.to_account_id(),
config.chain_spec.identify_chain(), config.chain_spec.identify_chain(),
); );
@ -340,11 +347,21 @@ pub fn run() -> Result<()> {
BenchmarkCmd::Pallet(cmd) => { BenchmarkCmd::Pallet(cmd) => {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
if cfg!(feature = "runtime-benchmarks") {
runner.sync_run(|config| { runner.sync_run(|config| {
cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>( cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>(
Some(config.chain_spec), Some(config.chain_spec),
).map_err(|e| Error::SubstrateCli(e)) )
.map_err(|e| Error::SubstrateCli(e))
}) })
} else {
Err(sc_cli::Error::Input(
"Benchmarking wasn't enabled when building the node. \
You can enable it with `--features=runtime-benchmarks`."
.into(),
)
.into())
}
} }
BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| { BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| {
cmd.run( cmd.run(

View File

@ -1,7 +1,7 @@
[package] [package]
name = "ghost-governance" name = "ghost-claims"
version = "0.3.7" version = "0.2.4"
description = "Full-chain and cross-chain governance pallet with early adopter share claims" description = "Ghost balance and rank claims based on EVM actions"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -20,6 +20,8 @@ frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true } frame-support = { workspace = true }
frame-system = { workspace = true } frame-system = { workspace = true }
pallet-ranked-collective = { workspace = true }
pallet-vesting = { workspace = true }
pallet-balances = { workspace = true } pallet-balances = { workspace = true }
sp-core = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true }
@ -27,13 +29,10 @@ sp-runtime = { features = ["serde"], workspace = true }
sp-io = { workspace = true } sp-io = { workspace = true }
sp-std = { workspace = true } sp-std = { workspace = true }
ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
ghost-networks = { workspace = true }
[dev-dependencies] [dev-dependencies]
libsecp256k1 = { workspace = true, default-features = true }
hex-literal = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true }
libsecp256k1 = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
[features] [features]
default = ["std"] default = ["std"]
@ -49,11 +48,10 @@ std = [
"sp-runtime/std", "sp-runtime/std",
"sp-io/std", "sp-io/std",
"sp-std/std", "sp-std/std",
"pallet-ranked-collective/std",
"pallet-vesting/std",
"pallet-balances/std", "pallet-balances/std",
"rustc-hex/std", "rustc-hex/std",
"ghost-traits/std",
"ghost-helpers/std",
"ghost-networks/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"libsecp256k1/hmac", "libsecp256k1/hmac",
@ -61,13 +59,16 @@ runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
"pallet-ranked-collective/runtime-benchmarks",
"pallet-vesting/runtime-benchmarks",
"pallet-balances/runtime-benchmarks", "pallet-balances/runtime-benchmarks",
"sp-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks",
"ghost-networks/runtime-benchmarks",
] ]
try-runtime = [ try-runtime = [
"frame-support/try-runtime", "frame-support/try-runtime",
"frame-system/try-runtime", "frame-system/try-runtime",
"pallet-ranked-collective/try-runtime",
"pallet-vesting/try-runtime",
"pallet-balances/try-runtime", "pallet-balances/try-runtime",
"sp-runtime/try-runtime", "sp-runtime/try-runtime",
] ]

View 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(&ethereum_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>(&ethereum_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
View 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(&ethereum_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
View 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
}

View 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
View 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,
}));
})
}

View File

@ -0,0 +1,104 @@
// This file is part of Ghost Network.
// Ghost Network is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Ghost Network is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Ghost Network. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `ghost_claims`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-08-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
// Executed Command:
// ./target/release/ghost
// benchmark
// pallet
// --chain=casper-dev
// --steps=50
// --repeat=20
// --pallet=ghost_claims
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --header=./file_header.txt
// --output=./runtime/casper/src/weights/ghost_claims.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{Weight, constants::RocksDbWeight},
};
use core::marker::PhantomData;
/// Weight functions needed for `ghost_claims`.
pub trait WeightInfo {
fn claim() -> Weight;
}
/// Weight for ghost_claims using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `GhostClaims::Total` (r:1 w:1)
/// Proof: `GhostClaims::Total` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `CultCollective::Members` (r:2 w:2)
/// Proof: `CultCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::MemberCount` (r:6 w:6)
/// Proof: `CultCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::IdToIndex` (r:6 w:12)
/// Proof: `CultCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::IndexToId` (r:0 w:6)
/// Proof: `CultCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
fn claim() -> Weight {
// Proof Size summary in bytes:
// Measured: `896`
// Estimated: `16164`
// Minimum execution time: 754_086_000 picoseconds.
Weight::from_parts(756_147_000, 0)
.saturating_add(Weight::from_parts(0, 16164))
.saturating_add(T::DbWeight::get().reads(17))
.saturating_add(T::DbWeight::get().writes(29))
}
}
impl WeightInfo for () {
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `GhostClaims::Total` (r:1 w:1)
/// Proof: `GhostClaims::Total` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `CultCollective::Members` (r:2 w:2)
/// Proof: `CultCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::MemberCount` (r:6 w:6)
/// Proof: `CultCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::IdToIndex` (r:6 w:12)
/// Proof: `CultCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
/// Storage: `CultCollective::IndexToId` (r:0 w:6)
/// Proof: `CultCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
fn claim() -> Weight {
// Proof Size summary in bytes:
// Measured: `896`
// Estimated: `16164`
// Minimum execution time: 754_086_000 picoseconds.
Weight::from_parts(756_147_000, 0)
.saturating_add(Weight::from_parts(0, 16164))
.saturating_add(RocksDbWeight::get().reads(17))
.saturating_add(RocksDbWeight::get().writes(29))
}
}

View File

@ -1,108 +0,0 @@
[package]
name = "ghost-exodus"
version = "0.0.11"
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-tr = { 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-tr/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",
]

View File

@ -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"]

View File

@ -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>;
}
}

View File

@ -1,853 +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_quorum_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 network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
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_address = EvmAddress::repeat_byte(69);
let dummy_nonce = vec![0u8; network_curve.element_bytes_len()];
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
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 network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
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_address = EvmAddress::repeat_byte(69);
let dummy_merkle_root = ExodusHash::repeat_byte(69);
let dummy_group_commitment = vec![0u8; network_curve.element_bytes_len()];
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
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_quorum_threshold(max_signers);
let network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
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_address = EvmAddress::repeat_byte(69);
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
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!(ExodusSignedGovernance::<T>::contains_key((&exodus_session, network_id), dkg_index));
Ok(())
}
#[benchmark]
fn register_evm_bridge_out_exodus() -> Result<(), BenchmarkError> {
let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
let (_, network_curve, _, _) = prepare_pallet::<T>(1usize);
let network_id = NetworkIdOf::<T>::default();
let amount: BalanceOf<T> = 10_000u32.into();
let bounty = Perbill::from_percent(5);
let receiver = EvmAddress::from([69u8; 20]);
QualificationDkgState::<T>::mutate(&network_curve, |state| {
loop {
if state.is_dkg_finalized() { break; }
state.next_phase();
}
});
let total_existential = T::Currency::minimum_balance();
let initial_balance = total_existential
.saturating_mul(5u32.into())
.saturating_add(amount);
let _ = T::Currency::make_free_balance_be(&caller, initial_balance);
let _ = T::NetworkDataHandler::register_incoming(&network_id, amount).unwrap();
let current_exodus_session = CurrentExodus::<T>::get();
#[extrinsic_call]
_(
RawOrigin::Signed(caller.clone()),
network_id,
amount,
bounty,
receiver,
);
assert!(ExodusRequests::<T>::contains_key(&network_curve, current_exodus_session));
assert_eq!(CurrentExodus::<T>::get(), current_exodus_session + 1);
assert_eq!(T::Currency::free_balance(&caller), initial_balance - amount);
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext_benchmarks(),
crate::mock::Runtime
);
}

View File

@ -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."),
}
}
}

View File

@ -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)
}
}

View File

@ -1,343 +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 = key_package.normalize_package();
let pubkey_package = pubkey_package.normalize_package();
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)
})
}
}

View File

@ -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)
})
}
}

View File

@ -1,446 +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 signature_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 64,
NetworkCurve::Ed25519 => 64,
}
}
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 mut 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 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 mut r_i_element = hiding_element + (binding_element * binding_factor_scalar);
let group_commitment_wrapper =
frost_core::GroupCommitment::<Ciphersuite>::from_element(group_commitment_element);
if !group_commitment_wrapper.is_normalized() {
r_i_element = -r_i_element;
group_commitment_element = -group_commitment_element;
}
let challenge = <Ciphersuite as frost_core::Ciphersuite>::challenge(
&group_commitment_element,
&verifying_key,
message,
).map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
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::<Ciphersuite>(&signing_package, &verifying_key, &[])
.map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
let group_commitment =
frost_core::compute_group_commitment::<Ciphersuite>(&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())
})
}
}

View File

@ -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)
}
}

View File

@ -1,74 +0,0 @@
#[macro_export]
macro_rules! with_ciphersuite {
($curve:expr, |$alias:ident| $body:block) => {
match $curve {
NetworkCurve::Secp256k1 => {
#[allow(unused_imports)]
use frost_secp256k1_tr as $alias;
#[allow(unused_imports)]
use frost_secp256k1_tr::Secp256K1Sha256TR as Ciphersuite;
use $alias::keys::EvenY;
#[allow(dead_code)]
trait UniversalNormalizer {
fn normalize_package(self) -> Self;
fn is_normalized(&self) -> bool;
}
impl<T: EvenY> UniversalNormalizer for T {
#[inline]
fn normalize_package(self) -> Self {
self.into_even_y(None)
}
#[inline]
fn is_normalized(&self) -> bool {
self.has_even_y()
}
}
$body
},
NetworkCurve::Ed25519 => {
#[allow(unused_imports)]
use frost_ed25519 as $alias;
#[allow(unused_imports)]
use frost_ed25519::Ed25519Sha512 as Ciphersuite;
#[allow(dead_code)]
trait UniversalNormalizer {
fn normalize_package(self) -> Self;
fn is_normalized(&self) -> bool;
}
impl<T> UniversalNormalizer for T {
#[inline]
fn normalize_package(self) -> Self { self }
#[inline]
fn is_normalized(&self) -> bool { true }
}
$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

View File

@ -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(420)
.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

View File

@ -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 }
}
}

View File

@ -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,
};
}
}

View File

@ -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 })
}
}

View File

@ -1,732 +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(())
}
}
)*
};
(const_array: $( [ $inner_ty:ty ; $len:expr ] ),* $(,)? ) => {
$(
impl EvmAbiEncode for [ $inner_ty ; $len ] {
#[inline(always)]
fn size(&self) -> usize {
$len * 32
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
for item in self.iter() {
EvmAbiEncode::abi_encode(item, buffer, cursor)?;
}
Some(())
}
}
)*
};
(bytes_slice: ) => {
impl EvmAbiEncode for [u8] {
#[inline(always)]
fn size(&self) -> usize {
let content_len = self.len();
let remainder = content_len % 32;
let alignment = if remainder == 0 { 0 } else { 32 - remainder };
32 + content_len + alignment
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
if self.size() >= MAX_MESSAGE_SIZE as usize { return None; }
let pointer = cursor.checked_sub(4).map(|x| x.saturating_add(32))?;
let head_pointer = pointer as u32;
EvmAbiEncode::abi_encode(&head_pointer, buffer, cursor);
let content_len = self.len() as u32;
EvmAbiEncode::abi_encode(&content_len, buffer, cursor)?;
let value_slice = buffer.get_mut(*cursor..*cursor + self.len())?;
value_slice.copy_from_slice(self);
*cursor += self.len();
let remainder = self.len() % 32;
if remainder != 0 {
let alignment = 32 - remainder;
let alignment_slice = buffer.get_mut(*cursor..*cursor + alignment)?;
alignment_slice.fill(0);
*cursor += alignment;
}
Some(())
}
}
};
}
impl_evm_encodable!(numeric: u8, u16, u32, u64, u128);
impl_evm_encodable!(bytes_fixed: EvmAddress, EvmBytes32);
impl_evm_encodable!(const_array: [u128; 3], [u128; 2], [u32; 2], [u8; 2]);
impl_evm_encodable!(bytes_slice:);
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, Copy, PartialEq, TypeInfo, RuntimeDebug)]
#[repr(u8)]
pub enum GovernanceStatus {
ReserveDepositor = 0,
ReserveToken = 1,
LiquidityDepositor = 2,
LiquidityToken = 3,
RewardManager = 4,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub enum GovernanceAction {
SetDistributor { distributor: EvmAddress },
SetWarmupPeriod { warmup_period: u128 },
UpdateGatekeeperAddress { new_gatekeeper: EvmAddress },
SetBounty { bounty: u128 },
AddPool { address: EvmAddress },
RemovePool { index: u128 },
SetAdjustment { rate: u128, target: u128, add: bool },
Enable { status: GovernanceStatus, address: EvmAddress, calculator: EvmAddress },
Disable { status: GovernanceStatus, address: EvmAddress },
ForfeitReserves { router: EvmAddress, liquidity: u128, destroyer_mode: bool },
RedeemReserves { router: EvmAddress, amount: u128 },
Withdraw { token: EvmAddress, amount: u128 },
AuditReserves,
Close { id: u128 },
Create {
market: [u128; 3],
terms: [u128; 2],
token: EvmAddress,
intervals: [u32; 2],
booleans: [bool; 2],
},
}
impl GovernanceAction {
pub fn get_message<'a>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
match &self {
Self::SetDistributor { distributor } => {
abi_encode_evm_message!(
message_buffer,
"setDistributor(address)",
[distributor]
)
},
Self::SetWarmupPeriod { warmup_period } => {
abi_encode_evm_message!(
message_buffer,
"setWarmupPeriod(uint256)",
[warmup_period]
)
},
Self::UpdateGatekeeperAddress { new_gatekeeper } => {
abi_encode_evm_message!(
message_buffer,
"updateGatekeeperAddress(address)",
[new_gatekeeper]
)
},
Self::SetBounty { bounty } => {
abi_encode_evm_message!(
message_buffer,
"setBounty(uint256)",
[bounty]
)
},
Self::AddPool { address } => {
abi_encode_evm_message!(
message_buffer,
"addPool(address)",
[address]
)
},
Self::RemovePool { index } => {
abi_encode_evm_message!(
message_buffer,
"removePool(uint256)",
[index]
)
},
Self::SetAdjustment { rate, target, add } => {
abi_encode_evm_message!(
message_buffer,
"setAdjustment(uint256,uint256,bool)",
[rate, target, &(*add as u8)]
)
},
Self::Enable { status, address, calculator } => {
abi_encode_evm_message!(
message_buffer,
"enable(uint8,address,address)",
[&(*status as u8), address, calculator]
)
},
Self::Disable { status, address } => {
abi_encode_evm_message!(
message_buffer,
"disable(uint8,address)",
[&(*status as u8), address]
)
},
Self::ForfeitReserves { router, liquidity, destroyer_mode } => {
abi_encode_evm_message!(
message_buffer,
"forfeitReserves(address,uint256,bool)",
[router, liquidity, &(*destroyer_mode as u8)]
)
},
Self::RedeemReserves { router, amount } => {
abi_encode_evm_message!(
message_buffer,
"redeemReserve(address,uint256)",
[router, amount]
)
},
Self::Withdraw { token, amount } => {
abi_encode_evm_message!(
message_buffer,
"withdraw(address,uint256)",
[token, amount]
)
},
Self::AuditReserves => {
abi_encode_evm_message!(message_buffer, "auditReserves()", [])
},
Self::Close { id } => {
abi_encode_evm_message!(message_buffer, "close(uint256)", [id])
},
Self::Create { market, terms, token, intervals, booleans } => {
let booleans_u8 = [booleans[0] as u8, booleans[1] as u8];
abi_encode_evm_message!(
message_buffer,
"create(uint256[3],uint256[2],address,uint32[2],bool[2])",
[market, terms, token, intervals, &booleans_u8]
)
},
}
}
}
#[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,
bounty: Perbill,
receiver: EvmAddress,
},
EvmGovernance {
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
},
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::EvmGovernance { exodus_session, network_id, target, action } => {
let mut action_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let action_payload = action.get_message(&mut action_buffer)?;
let mut metadata_buffer = [0u8; 32];
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
metadata_buffer[0..8].copy_from_slice(&chain_id_u64.to_be_bytes());
metadata_buffer[8..28].copy_from_slice(target.as_ref());
let packed_metadata = EvmBytes32::from(&metadata_buffer);
abi_encode_evm_message!(
message_buffer,
"govern(uint256,uint256,bytes)",
[exodus_session, &packed_metadata, action_payload]
)
},
ExodusRequestType::EvmBridgeOut { exodus_session, network_id, amount, bounty, 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_bounty = multiply_by_rational_with_rounding(
bounty.deconstruct() as u128,
1u128 << 32,
1_000_000_000,
sp_runtime::Rounding::Down
)?;
let bounty_uint32 = scaled_bounty as u32;
buffer[28..32].copy_from_slice(&bounty_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,
"recall(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,
bounty: Perbill,
receiver: EvmAddress,
) -> Self {
let evm_bridge_out = ExodusRequestType::EvmBridgeOut {
exodus_session,
network_id,
amount,
bounty,
receiver,
};
Self::default_request_for(evm_bridge_out)
}
pub fn evm_governance(
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
) -> Self {
let evm_governance = ExodusRequestType::EvmGovernance {
exodus_session,
network_id,
target,
action,
};
Self::default_request_for(evm_governance)
}
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() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_io::hashing::keccak_256;
#[test]
fn test_rotation_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let public_key = EvmBytes32::repeat_byte(0x11);
let session = 69u64;
let rotation_request = ExodusRequest::<u64, u128>::evm_rotation(
session,
public_key.clone(),
1,
);
let calldata = rotation_request.get_message(&mut buffer).unwrap();
assert_eq!(calldata.len(), 100);
let expected_hash = keccak_256("rotate(uint256,bytes32,uint8)".as_bytes());
assert_eq!(calldata[0..4], expected_hash[0..4]);
assert_eq!(calldata[35], 0x45);
assert_eq!(&calldata[4..35], &[0u8; 31]);
assert_eq!(&calldata[36..68], public_key.as_ref());
assert_eq!(calldata[99], 1);
assert_eq!(&calldata[68..99], &[0u8; 31]);
}
#[test]
fn test_bridge_out_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let receiver = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let bridge_request = ExodusRequest::<u64, u128>::evm_bridge_out(
session,
network_id,
1_000_000u128,
sp_runtime::Perbill::from_percent(1),
receiver.clone()
);
let calldata = bridge_request.get_message(&mut buffer).unwrap();
assert_eq!(calldata.len(), 100);
let expected_hash = keccak_256("recall(uint256,uint256,uint256)".as_bytes());
assert_eq!(calldata[0..4], expected_hash[0..4]);
assert_eq!(calldata[35], 69);
assert_eq!(calldata[67], 0x40);
assert_eq!(calldata[66], 0x42);
assert_eq!(calldata[65], 0x0F);
let metadata_slot = &calldata[68..100];
assert_eq!(&metadata_slot[0..20], receiver.as_ref());
assert_eq!(metadata_slot[27], 0xA4);
assert_eq!(metadata_slot[26], 0x01);
assert_eq!(metadata_slot[31], 0x28);
assert_eq!(metadata_slot[30], 0x5C);
assert_eq!(metadata_slot[29], 0x8F);
assert_eq!(metadata_slot[28], 0x02);
}
#[test]
fn test_governance_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let target = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let actions = vec![
GovernanceAction::SetDistributor { distributor: EvmAddress::repeat_byte(0x11) },
GovernanceAction::SetWarmupPeriod { warmup_period: 3600 },
GovernanceAction::UpdateGatekeeperAddress { new_gatekeeper: EvmAddress::repeat_byte(0x22) },
GovernanceAction::SetBounty { bounty: 500 },
GovernanceAction::AddPool { address: EvmAddress::repeat_byte(0x22) },
GovernanceAction::RemovePool { index: 2 },
GovernanceAction::SetAdjustment { rate: 10, target: 100, add: true },
GovernanceAction::Enable {
status: GovernanceStatus::ReserveDepositor,
address: EvmAddress::repeat_byte(0x33),
calculator: EvmAddress::repeat_byte(0x44)
},
GovernanceAction::Disable {
status: GovernanceStatus::ReserveToken,
address: EvmAddress::repeat_byte(0x55)
},
GovernanceAction::ForfeitReserves {
router: EvmAddress::repeat_byte(0x66),
liquidity: 5000,
destroyer_mode: false
},
GovernanceAction::RedeemReserves { router: EvmAddress::repeat_byte(0x77), amount: 10000 },
GovernanceAction::Withdraw { token: EvmAddress::repeat_byte(0x88), amount: 2500 },
GovernanceAction::AuditReserves,
GovernanceAction::Close { id: 99 },
GovernanceAction::Create {
market: [69, 420, 1337],
terms: [69, 420],
token: EvmAddress::repeat_byte(0x99),
intervals: [69, 1337],
booleans: [true, false],
},
];
for (index, action) in actions.into_iter().enumerate() {
let action_clone = action.clone();
let governance_request = ExodusRequest::<u64, u128>::evm_governance(
session,
network_id,
target,
action,
);
let message = governance_request
.get_message(&mut buffer)
.get_or_insert(&[])
.to_vec();
assert!(!message.is_empty(), "Failed to encode action at index {}", index);
let expected_master_hash = keccak_256("govern(uint256,uint256,bytes)".as_bytes());
assert_eq!(message[0..4], expected_master_hash[0..4]);
assert_eq!(message[35], 69);
assert_eq!(&message[4..35], &[0u8; 31]);
assert_eq!(message[43], 0xA4);
assert_eq!(message[42], 0x01);
assert_eq!(&message[44..64], target.as_ref());
assert_eq!(&message[64..68], &[0u8; 4]);
assert_eq!(message[99], 96);
let inner_payload_len = u32::from_be_bytes(message[128..132].try_into().unwrap()) as usize;
let inner_payload = &message[132..132 + inner_payload_len];
assert_eq!((message.len() - 4) % 32, 0);
match action_clone {
GovernanceAction::SetDistributor { distributor } => {
let hash = keccak_256("setDistributor(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], distributor.as_ref());
},
GovernanceAction::SetWarmupPeriod { warmup_period } => {
let hash = keccak_256("setWarmupPeriod(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], warmup_period as u8);
},
GovernanceAction::UpdateGatekeeperAddress { new_gatekeeper } => {
let hash = keccak_256("updateGatekeeperAddress(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], new_gatekeeper.as_ref());
},
GovernanceAction::SetBounty { bounty } => {
let hash = keccak_256("setBounty(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], bounty as u8);
},
GovernanceAction::AddPool { address } => {
let hash = keccak_256("addPool(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], address.as_ref());
},
GovernanceAction::RemovePool { index } => {
let hash = keccak_256("removePool(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], index as u8);
},
GovernanceAction::SetAdjustment { rate, target, add } => {
let hash = keccak_256("setAdjustment(uint256,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], rate as u8);
assert_eq!(inner_payload[67], target as u8);
assert_eq!(inner_payload[99], add as u8);
},
GovernanceAction::Enable { status, address, calculator } => {
let hash = keccak_256("enable(uint8,address,address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], status as u8);
assert_eq!(&inner_payload[48..68], address.as_ref());
assert_eq!(&inner_payload[80..100], calculator.as_ref());
},
GovernanceAction::Disable { status, address } => {
let hash = keccak_256("disable(uint8,address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], status as u8);
assert_eq!(&inner_payload[48..68], address.as_ref());
},
GovernanceAction::ForfeitReserves { router, liquidity, destroyer_mode } => {
let hash = keccak_256("forfeitReserves(address,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], router.as_ref());
assert_eq!(inner_payload[67], liquidity as u8);
assert_eq!(inner_payload[99], destroyer_mode as u8);
},
GovernanceAction::RedeemReserves { router, amount } => {
let hash = keccak_256("redeemReserve(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], router.as_ref());
assert_eq!(inner_payload[67], amount as u8);
},
GovernanceAction::Withdraw { token, amount } => {
let hash = keccak_256("withdraw(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], token.as_ref());
assert_eq!(inner_payload[67], amount as u8);
},
GovernanceAction::AuditReserves => {
let hash = keccak_256("auditReserves()".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload.len(), 4);
},
GovernanceAction::Close { id } => {
let hash = keccak_256("close(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], id as u8);
},
GovernanceAction::Create { market, terms, token, intervals, booleans } => {
let hash = keccak_256("create(uint256[3],uint256[2],address,uint32[2],bool[2])".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload.len(), 324);
assert_eq!(inner_payload[35], 69);
assert_eq!(u128::from_be_bytes(inner_payload[20..36].try_into().unwrap()), market[0]);
assert_eq!(u128::from_be_bytes(inner_payload[52..68].try_into().unwrap()), market[1]);
assert_eq!(u128::from_be_bytes(inner_payload[84..100].try_into().unwrap()), market[2]);
assert_eq!(u128::from_be_bytes(inner_payload[116..132].try_into().unwrap()), terms[0]);
assert_eq!(u128::from_be_bytes(inner_payload[148..164].try_into().unwrap()), terms[1]);
assert_eq!(&inner_payload[176..196], token.as_ref());
assert_eq!(u32::from_be_bytes(inner_payload[224..228].try_into().unwrap()), intervals[0]);
assert_eq!(u32::from_be_bytes(inner_payload[256..260].try_into().unwrap()), intervals[1]);
assert_eq!(inner_payload[291], booleans[0] as u8);
assert_eq!(inner_payload[323], booleans[1] as u8);
}
}
}
}
}

View File

@ -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(&current_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(&current_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(())
}
}

View File

@ -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::*;

View File

@ -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 }
}
}

View File

@ -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,
}
}
}

View File

@ -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
}
}

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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}."),
}
}
}

View File

@ -1,354 +0,0 @@
// This file is part of Ghost Network.
// Ghost Network is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Ghost Network is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Ghost Network. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `ghost_exodus`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
// Executed Command:
// ./target/release/ghost
// benchmark
// pallet
// --chain=casper-dev
// --steps=50
// --repeat=20
// --pallet=ghost_exodus
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --header=./file_header.txt
// --output=./runtime/casper/src/weights/ghost_exodus.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{Weight, constants::RocksDbWeight},
};
use core::marker::PhantomData;
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;
}
/// Weight functions for `ghost_exodus`.
impl WeightInfo for () {
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:1)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:0 w:1)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round0_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `351`
// Estimated: `3816`
// Minimum execution time: 239_012_000 picoseconds.
Weight::from_parts(241_388_000, 0)
.saturating_add(Weight::from_parts(0, 3816))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:0)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round1Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round1Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[4, 300]`.
fn register_round1_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `391 + c * (32 ±0)`
// Estimated: `3856 + c * (32 ±0)`
// Minimum execution time: 1_339_828_000 picoseconds.
Weight::from_parts(1_212_625_790, 0)
.saturating_add(Weight::from_parts(0, 3856))
// Standard Error: 11_831
.saturating_add(Weight::from_parts(33_120_868, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round2Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round2Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_encrypted_round2_packages(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 251_478_000 picoseconds.
Weight::from_parts(254_144_621, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 24_613
.saturating_add(Weight::from_parts(6_595_819, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Complaints` (r:1 w:1)
/// Proof: `GhostExodus::Complaints` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_round3_complaints(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 236_155_000 picoseconds.
Weight::from_parts(242_055_948, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 15_623
.saturating_add(Weight::from_parts(360_411, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Justifications` (r:1 w:1)
/// Proof: `GhostExodus::Justifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_round4_justifications(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 249_507_000 picoseconds.
Weight::from_parts(248_505_595, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 20_302
.saturating_add(Weight::from_parts(4_191_526, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Verifications` (r:1 w:1)
/// Proof: `GhostExodus::Verifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round5_verifying_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 264_214_000 picoseconds.
Weight::from_parts(266_905_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingSharesParticipants` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingSharesParticipants` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 9]`.
fn register_round6_public_share_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + c * (770 ±0)`
// Estimated: `1551 + c * (1019 ±27)`
// Minimum execution time: 281_073_000 picoseconds.
Weight::from_parts(280_778_987, 0)
.saturating_add(Weight::from_parts(0, 1551))
// Standard Error: 92_951
.saturating_add(Weight::from_parts(3_489_552, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 1019).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:0 w:1)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_nonce_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `588`
// Estimated: `4053`
// Minimum execution time: 291_972_000 picoseconds.
Weight::from_parts(295_122_000, 0)
.saturating_add(Weight::from_parts(0, 4053))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(5))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:0)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:0)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_group_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `968`
// Estimated: `4433`
// Minimum execution time: 331_528_000 picoseconds.
Weight::from_parts(333_974_000, 0)
.saturating_add(Weight::from_parts(0, 4433))
.saturating_add(RocksDbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusSignedGovernance` (r:1 w:1)
/// Proof: `GhostExodus::ExodusSignedGovernance` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:1 w:0)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveVerifyingKey` (r:1 w:0)
/// Proof: `GhostExodus::ActiveVerifyingKey` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::SignatureScalars` (r:1 w:1)
/// Proof: `GhostExodus::SignatureScalars` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:0 w:1)
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_signature_share() -> Weight {
// Proof Size summary in bytes:
// Measured: `11259`
// Estimated: `14724`
// Minimum execution time: 3_842_259_000 picoseconds.
Weight::from_parts(3_985_167_000, 0)
.saturating_add(Weight::from_parts(0, 14724))
.saturating_add(RocksDbWeight::get().reads(14))
.saturating_add(RocksDbWeight::get().writes(8))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::GatekeeperAmounts` (r:1 w:1)
/// Proof: `GhostNetworks::GatekeeperAmounts` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkImbalance` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodus` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:0 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_evm_bridge_out_exodus() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 137_053_000 picoseconds.
Weight::from_parts(138_693_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
}

View File

@ -1,147 +0,0 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_support::dispatch::RawOrigin;
#[benchmarks(where T: Config)]
mod benchmarks {
use super::*;
#[benchmark]
fn claim(
p: Linear<1, { T::MaxProofDepth::get() }>
) -> Result<(), BenchmarkError> {
let network_id = Default::default();
let current_account: T::AccountId = frame_benchmarking::whitelisted_caller();
let max_proof_depth = p;
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_shares_u128 = 420u128;
let dummy_shares: BalanceOf<T> = dummy_shares_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 target_index = max_proof_depth - 1;
let claim_package_init = ClaimPackage {
shares: dummy_shares,
merkle_proof: Default::default(),
token_id: 0,
index: target_index,
};
let preimage_init = claim_package_init.get_preimage(&dummy_evm_address, network_id);
let mut leaf_combined = sp_std::vec![0u8; 1 + preimage_init.len()];
leaf_combined[1..].copy_from_slice(&preimage_init);
let some_hash = SubstrateKeccakHasher::hash(&leaf_combined);
let mut current_hash = some_hash;
let mut proof_hashes = Vec::with_capacity(max_proof_depth as usize);
let mut current_index = target_index as usize;
let hash_len = SubstrateKeccakHasher::hash_len();
let mut combined = sp_std::vec![1u8; 1 + hash_len * 2];
for _ in 0..max_proof_depth {
proof_hashes.push(some_hash);
if current_index % 2 == 0 {
combined[1..1 + hash_len].copy_from_slice(&current_hash.to_fixed_bytes());
combined[1 + hash_len..1 + hash_len * 2].copy_from_slice(&some_hash.to_fixed_bytes());
} else {
combined[1..1 + hash_len].copy_from_slice(&some_hash.to_fixed_bytes());
combined[1 + hash_len..1 + hash_len * 2].copy_from_slice(&current_hash.to_fixed_bytes());
}
current_hash = SubstrateKeccakHasher::hash(&combined);
current_index >>= 1;
}
let merkle_root = current_hash;
let member_share = MemberShare {
initiated_network: None,
activated_shares: Default::default(),
locked_balance: minimum_donation,
};
MemberShares::<T>::insert(&current_account, member_share);
let network_state = NetworkShare::new(
dummy_shares,
merkle_root,
max_proof_depth,
);
NetworkShares::<T>::insert(&network_id, network_state);
let global_state = ShareState::new(dummy_shares);
GlobalShares::<T>::put(global_state);
let claim_package = ClaimPackage {
index: target_index,
merkle_proof: BoundedVec::try_from(proof_hashes).unwrap(),
shares: dummy_shares,
token_id: 0
};
let preimage = claim_package.get_preimage(&dummy_evm_address, network_id);
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
let message_string_bytes = Pallet::<T>::ethereum_signable_message(
&current_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_shares, GlobalShares::<T>::get().claimed_shares);
assert_eq!(
dummy_shares,
MemberShares::<T>::get(&current_account).activated_shares
);
assert_eq!(
dummy_shares,
NetworkShares::<T>::get(&network_id).claimed_shares(),
);
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(),
crate::mock::Test,
);
}

View File

@ -1,475 +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 index: u32,
pub token_id: TokenId,
pub shares: 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<NetworkId>(
&self,
evm_address: &EvmAddress,
network_id: NetworkId
) -> [u8; 128]
where
NetworkId: Default + Copy + Clone + UniqueSaturatedInto<u128>,
{
let mut token_id_bytes = [0u8; 32];
U256::from(self.token_id as u128).to_big_endian(&mut token_id_bytes);
let mut shares_bytes = [0u8; 32];
let shares_u128: u128 = self.shares.unique_saturated_into();
U256::from(shares_u128).to_big_endian(&mut shares_bytes);
let mut address_bytes = [0u8; 32];
address_bytes[12..32].copy_from_slice(evm_address.as_bytes());
let mut network_id_bytes = [0u8; 32];
let network_id_u128: u128 = network_id.unique_saturated_into();
U256::from(network_id_u128).to_big_endian(&mut network_id_bytes);
let mut preimage = [0u8; 128];
preimage[0..32].copy_from_slice(&token_id_bytes);
preimage[32..64].copy_from_slice(&shares_bytes);
preimage[64..96].copy_from_slice(&address_bytes);
preimage[96..128].copy_from_slice(&network_id_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,
shares: 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(
claim_package.merkle_proof.len() as u32,
))]
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, network_id);
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.shares);
ensure!(increased.is_some(), Error::<T>::SharesOverflowed);
Ok(())
})?;
GlobalShares::<T>::try_mutate(|state| -> DispatchResult {
let increased = state.try_increase_claimed(claim_package.shares);
ensure!(increased.is_some(), Error::<T>::SharesOverflowed);
Ok(())
})?;
member_share.increase_activated_shares(claim_package.shares);
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 {
shares: claim_package.shares,
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, _>(
preimage,
&claim_package.merkle_proof,
network_state.merkle_root,
claim_package.index,
);
ensure!(is_merkle_proof_valid, Error::<T>::InvalidMerkleProof);
Ok(())
}
}

View File

@ -1,89 +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<u128>;
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 ExistentialDeposit = frame_support::traits::ConstU128<1>;
type AccountStore = System;
type Balance = u128;
}
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
}

View File

@ -1,309 +0,0 @@
#![cfg(test)]
use mock::*;
use super::*;
use hex_literal::hex;
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_shares: u128,
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_shares: 420u128,
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_shares: 1337u128,
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_shares: 69u128,
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 {
shares: u.allowed_shares,
merkle_proof: Default::default(),
token_id: u.token_id,
index: index as u32
};
Ok((index as usize, tmp_package.get_preimage(&u.evm_address, network_id).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(&current_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 {
shares: current_user.allowed_shares,
merkle_proof: BoundedVec::try_from(proof).unwrap(),
token_id: current_user.token_id,
index: slot_index as u32,
};
let preimage = claim_package.get_preimage(&current_user.evm_address, network_id);
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
let message_string_bytes = Governance::ethereum_signable_message(
&current_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, &current_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(&current_user.substrate_account);
assert_eq!(updated_share.activated_shares, current_user.allowed_shares);
global_activated += current_user.allowed_shares;
network_activated += current_user.allowed_shares;
}
assert_eq!(global_activated, GlobalShares::<Test>::get().claimed_shares);
assert_eq!(
network_activated,
NetworkShares::<Test>::get(&network_id).claimed_shares(),
);
});
}
#[test]
fn verify_proof_from_python_preclaims_for_sepolia() {
new_test_ext().execute_with(|| {
// ===========================================================================================================
// === FINAL MERKLE ROOT FOR [SEPOLIA]: c41c281c8404f5ce0adfca067ef8ed260f28c2cb1d7944c84ce2431936b1919e ===
// ===========================================================================================================
// [+] Total padded leaves: 128
// [+] Merkle tree size: 255
// [+] Network ID: 11155111
// [+] Proof size: 7
// [+] Total shares: 1398900000000000000000
let network_id = 11155111u64;
let merkle_root = EvmHash::from(hex!("c41c281c8404f5ce0adfca067ef8ed260f28c2cb1d7944c84ce2431936b1919e"));
let proof_size = 7;
let total_shares = 1398900000000000000000;
let network_state = NetworkShare::new(total_shares, merkle_root, proof_size);
let global_state = ShareState::new(total_shares);
NetworkShares::<Test>::insert(&network_id, network_state);
GlobalShares::<Test>::put(global_state);
let evm_holder_1 = EvmAddress::from(hex!("D500EFDef75E89Bf6caF5C98F7633575d0049a72"));
let claim_package_1 = ClaimPackage {
index: 0,
token_id: 1,
shares: 684900000000000000000,
merkle_proof: BoundedVec::try_from(
vec![
EvmHash::from(hex!("86d599b297ed8fb56476884b43502e15e7b207835f9a62bd07b4f3338ac63b44")),
EvmHash::from(hex!("85ec1f887b1a32e76cd1b70de6a5b1900045efd7dfe3b218d3b6cef672735da6")),
EvmHash::from(hex!("1e8a291eb2ba08696ca482c7869cab5ad360da4e4df55589adf40b08b020cc5b")),
EvmHash::from(hex!("a090ff633d4831ca76640ffce9d9399a0f8409bbbc98814d0d885c538c9ebc08")),
EvmHash::from(hex!("85ad79082ed24d2fc6748f131ed39280d402cb7a0844b42b9d768d4639b516a9")),
EvmHash::from(hex!("2c55408bc86c5a292611bc01ba46ac6c6b37b8409097b752bad884be24249ec4")),
EvmHash::from(hex!("9e046ea7683ac6dfa95caf833912d806b41296fcaa86718b4822f42ae68a6ff5"))
]
).unwrap(),
};
let preimage_1 = claim_package_1.get_preimage(&evm_holder_1, network_id);
assert_ok!(
Governance::verify_merkle_proof(
&network_id,
&preimage_1,
&claim_package_1,
)
);
let evm_holder_2 = EvmAddress::from(hex!("Ee71538ae7677829676BD8dD18259Ed93E6B44e9"));
let claim_package_2 = ClaimPackage {
index: 2,
token_id: 3,
shares: 14900000000000000000,
merkle_proof: BoundedVec::try_from(
vec![
EvmHash::from(hex!("547eb6ce3345e183448c61ee30e4eca0523537114e8d3971894196598651040e")),
EvmHash::from(hex!("9e9936d6cd248898747a8d29d2ef1b02ca06c40583873fd7c3a7ddd1554f5472")),
EvmHash::from(hex!("1e8a291eb2ba08696ca482c7869cab5ad360da4e4df55589adf40b08b020cc5b")),
EvmHash::from(hex!("a090ff633d4831ca76640ffce9d9399a0f8409bbbc98814d0d885c538c9ebc08")),
EvmHash::from(hex!("85ad79082ed24d2fc6748f131ed39280d402cb7a0844b42b9d768d4639b516a9")),
EvmHash::from(hex!("2c55408bc86c5a292611bc01ba46ac6c6b37b8409097b752bad884be24249ec4")),
EvmHash::from(hex!("9e046ea7683ac6dfa95caf833912d806b41296fcaa86718b4822f42ae68a6ff5"))
]
).unwrap(),
};
let preimage_2 = claim_package_2.get_preimage(&evm_holder_2, network_id);
assert_ok!(
Governance::verify_merkle_proof(
&network_id,
&preimage_2,
&claim_package_2,
)
);
let evm_holder_3 = EvmAddress::from(hex!("29c6D9e3c3981e4FcF2AC6B486e4E4210daeA1Ee"));
let claim_package_3 = ClaimPackage {
index: 6,
token_id: 7,
shares: 10900000000000000000,
merkle_proof: BoundedVec::try_from(
vec![
EvmHash::from(hex!("441f13d353fe0224fe4ab418fe56c66287e0ba1e5e562f56a4b7195ca3d9f741")),
EvmHash::from(hex!("17f9ae41fbcbbfc35accf1e4e132a905b0a95e68e085757ae8304b6a0554e908")),
EvmHash::from(hex!("a6f6783441ed0d4c5f1da8d1f026c037e15706c4ecb4c400216d485105ec9596")),
EvmHash::from(hex!("a090ff633d4831ca76640ffce9d9399a0f8409bbbc98814d0d885c538c9ebc08")),
EvmHash::from(hex!("85ad79082ed24d2fc6748f131ed39280d402cb7a0844b42b9d768d4639b516a9")),
EvmHash::from(hex!("2c55408bc86c5a292611bc01ba46ac6c6b37b8409097b752bad884be24249ec4")),
EvmHash::from(hex!("9e046ea7683ac6dfa95caf833912d806b41296fcaa86718b4822f42ae68a6ff5"))
]
).unwrap(),
};
let preimage_3 = claim_package_3.get_preimage(&evm_holder_3, network_id);
assert_ok!(
Governance::verify_merkle_proof(
&network_id,
&preimage_3,
&claim_package_3,
)
);
let evm_holder_4 = EvmAddress::from(hex!("Fe962E8BB2a6877d14b53E0a07932C040eC885ea"));
let claim_package_4 = ClaimPackage {
index: 35,
token_id: 36,
shares: 10900000000000000000,
merkle_proof: BoundedVec::try_from(
vec![
EvmHash::from(hex!("5ddb291d62d02afab17cee218f2f367d612736b0f1ae59d2d28aac811e2057f3")),
EvmHash::from(hex!("d990cb37076db084b1621bc8327e0f740e242da4c463ea3a0457a6483c07fdbf")),
EvmHash::from(hex!("7bf41c14904c75e189893d05cf99c55516a194882463980cf08468c3bfd7dd8a")),
EvmHash::from(hex!("1c4c8136fe9e6dc08dc1c69f2da707a271ae510cdb5af6051b5222f2812edee0")),
EvmHash::from(hex!("3ec8d2443fd6f9decef06bf930e37cd37d78bf1c6004dc84b9ca1199712a82f8")),
EvmHash::from(hex!("1951c00e48f734ba95daeb3e8f6a2d971cc6fb0afac471ae39a21013f5756794")),
EvmHash::from(hex!("9e046ea7683ac6dfa95caf833912d806b41296fcaa86718b4822f42ae68a6ff5"))
]
).unwrap(),
};
let preimage_4 = claim_package_4.get_preimage(&evm_holder_4, network_id);
assert_ok!(
Governance::verify_merkle_proof(
&network_id,
&preimage_4,
&claim_package_4,
)
);
})
}

View File

@ -1,76 +0,0 @@
// This file is part of Ghost Network.
// Ghost Network is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Ghost Network is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Ghost Network. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `ghost_governance`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
// Executed Command:
// ./target/release/ghost
// benchmark
// pallet
// --chain=casper-dev
// --steps=50
// --repeat=20
// --pallet=ghost_governance
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --header=./file_header.txt
// --output=./runtime/casper/src/weights/ghost_governance.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{Weight, constants::RocksDbWeight},
};
use core::marker::PhantomData;
/// Weight functions needed for `ghost_claims`.
pub trait WeightInfo {
fn claim(p: u32) -> Weight;
}
impl WeightInfo for () {
/// Storage: `GhostGovernance::MemberShares` (r:1 w:1)
/// Proof: `GhostGovernance::MemberShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostGovernance::ClaimedHashes` (r:1 w:1)
/// Proof: `GhostGovernance::ClaimedHashes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostGovernance::NetworkShares` (r:1 w:1)
/// Proof: `GhostGovernance::NetworkShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostGovernance::GlobalShares` (r:1 w:1)
/// Proof: `GhostGovernance::GlobalShares` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `p` is `[1, 20]`.
fn claim(p: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `283`
// Estimated: `3748`
// Minimum execution time: 214_208_000 picoseconds.
Weight::from_parts(216_987_870, 0)
.saturating_add(Weight::from_parts(0, 3748))
// Standard Error: 30_856
.saturating_add(Weight::from_parts(1_702_104, 0).saturating_mul(p.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(4))
}
}

View File

@ -1,43 +0,0 @@
[package]
name = "ghost-helpers"
version = "0.0.11"
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

View File

@ -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);
}
}

View File

@ -1,137 +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_quorum_threshold<I>(value: I) -> I
where
I: num_traits::PrimInt,
{
let zero = I::zero();
let one = I::one();
let three = one + one + one;
if value == zero { return zero; }
let f = (value - one) / three;
value - f
}
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()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bft_threshold_standard_paper_cases() {
// Lower boundaries (f increases here)
assert_eq!(get_byzantium_quorum_threshold(1u32), 1);
assert_eq!(get_byzantium_quorum_threshold(4u32), 3);
assert_eq!(get_byzantium_quorum_threshold(7u32), 5);
assert_eq!(get_byzantium_quorum_threshold(10u32), 7);
// Upper boundaries (max nodes for a given 'f')
assert_eq!(get_byzantium_quorum_threshold(3u32), 3);
assert_eq!(get_byzantium_quorum_threshold(6u32), 5);
assert_eq!(get_byzantium_quorum_threshold(9u32), 7);
}
#[test]
fn test_bft_threshold_zero_nodes() {
assert_eq!(get_byzantium_quorum_threshold(0u32), 0);
}
#[test]
fn test_bft_threshold_integer_boundaries() {
let max_val = usize::MAX;
let expected_f = (max_val - 1) / 3;
let expected_honest = max_val - expected_f;
assert_eq!(get_byzantium_quorum_threshold(max_val), expected_honest);
}
#[test]
fn test_bft_threshold_different_types() {
assert_eq!(get_byzantium_quorum_threshold(4u8), 3u8);
assert_eq!(get_byzantium_quorum_threshold(4u64), 3u64);
assert_eq!(get_byzantium_quorum_threshold(4usize), 3usize);
}
}

View File

@ -1,603 +0,0 @@
use ghost_traits::hashing::GhostHasher;
use sp_arithmetic::traits::AtLeast8BitUnsigned;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_std::vec::Vec;
const LEAF_PREFIX: u8 = 0;
const NODE_PREFIX: u8 = 1;
fn leaf_prefixed<H>(preimage: &[u8]) -> H::Hash
where
H: GhostHasher,
{
let mut prefixed_data = sp_std::vec![LEAF_PREFIX; 1 + preimage.len()];
prefixed_data[1..].copy_from_slice(preimage);
H::hash(&prefixed_data)
}
fn node_prefixed<H>(preimage1: &[u8], preimage2: &[u8]) -> H::Hash
where
H: GhostHasher,
{
let total_len = preimage1.len() + preimage2.len();
let mut prefixed_data = sp_std::vec![NODE_PREFIX; 1 + total_len];
prefixed_data[1..1 + preimage1.len()].copy_from_slice(preimage1);
prefixed_data[1 + preimage1.len()..].copy_from_slice(preimage2);
H::hash(&prefixed_data)
}
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 empty_hash = H::empty();
let empty_leaf_hash = leaf_prefixed::<H>(empty_hash.as_ref());
let mut merkle_tree = sp_std::vec![empty_leaf_hash; total_capacity];
for item in raw_values.into_iter() {
let (index, preimage) = generate_preimage(item)?;
if index >= padded_leaves { return Ok(sp_std::vec![]); }
merkle_tree[index] = leaf_prefixed::<H>(&preimage)
}
let mut layer_start = 0;
let mut current_layer_len = padded_leaves;
let mut write_ptr = padded_leaves;
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();
merkle_tree[write_ptr] = node_prefixed::<H>(left_bytes, right_bytes);
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>,
{
if index > max_index { return sp_std::vec![]; }
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_index: usize = index.unique_saturated_into();
if current_index >= (1 << merkle_proof.len()) { return false; }
let mut current_hash = leaf_prefixed::<H>(preimage);
for sibling in merkle_proof.iter() {
let sibling_bytes = sibling.as_ref();
let hash_bytes = current_hash.as_ref();
if current_index % 2 == 0 {
current_hash = node_prefixed::<H>(hash_bytes, sibling_bytes);
} else {
current_hash = node_prefixed::<H>(sibling_bytes, hash_bytes);
}
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(&[ &[0u8], [1].as_slice() ].concat());
let h1 = TestBlake2Hasher::hash(&[ &[0u8], [2].as_slice() ].concat());
let h2 = TestBlake2Hasher::hash(&[ &[0u8], [3].as_slice() ].concat());
let h3 = TestBlake2Hasher::hash(&[ &[0u8], [4].as_slice() ].concat());
let mut c1 = [0u8; 65];
c1[0] = NODE_PREFIX;
c1[1..33].copy_from_slice(h0.as_ref());
c1[33..].copy_from_slice(h1.as_ref());
let parent_left = TestBlake2Hasher::hash(&c1);
let mut c2 = [0u8; 65];
c2[0] = NODE_PREFIX;
c2[1..33].copy_from_slice(h2.as_ref());
c2[33..].copy_from_slice(h3.as_ref());
let parent_right = TestBlake2Hasher::hash(&c2);
let mut c_root = [0u8; 65];
c_root[0] = NODE_PREFIX;
c_root[1..33].copy_from_slice(parent_left.as_ref());
c_root[33..].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();
let mut empty_data = sp_std::vec![LEAF_PREFIX];
empty_data.extend_from_slice(&H256::zero().to_fixed_bytes());
let empty_hash = TestBlake2Hasher::hash(&empty_data);
assert_eq!(tree.len(), 7);
assert_eq!(tree[3], empty_hash);
let mut leaf2_data = sp_std::vec![LEAF_PREFIX];
leaf2_data.extend_from_slice(&[3]);
let h2 = TestBlake2Hasher::hash(&leaf2_data);
let mut combined_right = [0u8; 65];
combined_right[0] = NODE_PREFIX;
combined_right[1..33].copy_from_slice(h2.as_ref());
combined_right[33..].copy_from_slice(empty_hash.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![LEAF_PREFIX, 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);
let mut empty_data = sp_std::vec![LEAF_PREFIX];
empty_data.extend_from_slice(&H256::zero().to_fixed_bytes());
let empty_hash = TestBlake2Hasher::hash(&empty_data);
assert_eq!(proof[0], empty_hash);
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);
}
}

View File

@ -1,359 +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
}
#[derive(Default)]
pub struct NetworkClaimBuilder<NetworkId, Balance> {
network_id: Option<NetworkId>,
merkle_root: Option<H256>,
amount: Option<Balance>,
proof_size: Option<u32>
}
impl<NetworkId, Balance> NetworkClaimBuilder<NetworkId, Balance> {
pub fn with_amount(mut self, amount: Balance) -> Self {
self.amount = Some(amount);
self
}
pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
self.network_id = Some(network_id);
self
}
pub fn with_merkle_root(mut self, merkle_root: H256) -> Self {
self.merkle_root = Some(merkle_root);
self
}
pub fn with_proof_size(mut self, proof_size: u32) -> Self {
self.proof_size = Some(proof_size);
self
}
pub fn build(self) -> NetworkClaim<NetworkId, Balance> {
NetworkClaim {
network_id: self.network_id.expect("NetworkClaim Error: missing network_id"),
amount: self.amount.expect("NetworkClaim Error: missing amount"),
merkle_root: self.merkle_root.expect("NetworkClaim Error: merkle_root"),
proof_size: self.proof_size.expect("NetworkClaim Error: missing proof_size"),
}
}
}
#[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 decimals: u8,
pub finality_delay: u64,
pub rate_limit_delay: u64,
pub block_deviation: u64,
pub incoming_share: u32,
pub outgoing_share: 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 decimals: u8,
pub finality_delay: u64,
pub rate_limit_delay: u64,
pub block_deviation: u64,
pub incoming_share: u32,
pub outgoing_share: 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_decimals(mut self, decimals: u8) -> Self {
self.decimals = decimals;
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_share(mut self, incoming_share: u32) -> Self {
self.incoming_share = incoming_share;
self
}
pub fn with_outgoing_share(mut self, outgoing_share: u32) -> Self {
self.outgoing_share = outgoing_share;
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,
decimals: self.decimals,
finality_delay: self.finality_delay,
rate_limit_delay: self.rate_limit_delay,
block_deviation: self.block_deviation,
incoming_share: self.incoming_share,
outgoing_share: self.outgoing_share,
}
}
}

View File

@ -1,584 +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 = 512;
pub const MAX_TX_LEN: usize = 4096;
type Prefix = [u8; 7];
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 Transaction<H: GhostHasher> {
version: i32,
inputs: Vec<TxIn<H>>,
outputs: Vec<TxOut>,
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_bytes = self.version.to_le_bytes();
let lock_time_bytes = self.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,
&self.inputs,
&self.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 parse_receiver_and_amount(
&self,
destination: &[u8],
prefix: &Prefix,
) -> Option<([u8; 32], u64)> {
let mut maybe_receiver = None;
let mut op_return_count = 0;
let mut total_amount: u64 = 0;
for output in self.outputs.iter() {
let script = &output.script_pubkey;
if let Some(receiver) = self.parse_receiver(script, prefix) {
op_return_count += 1;
maybe_receiver = Some(receiver);
}
if script == &destination {
total_amount = total_amount.checked_add(output.value)?;
}
}
if total_amount > 0 && op_return_count == 1 {
maybe_receiver.map(|receiver| (receiver, total_amount))
} else {
None
}
}
pub fn convert_into_p2pkh(hash: &[u8]) -> Option<[u8; 25]> {
if hash.len() != 20 { return None; }
let mut script = [0u8; 25];
script[0] = 0x76; // OP_DUP
script[1] = 0xa9; // OP_HASH160
script[2] = 0x14; // OP_PUSHBYTES_20
script[3..23].copy_from_slice(hash);
script[23] = 0x88; // OP_EQUALVERIFY
script[24] = 0xac; // OP_CHECKSIG
Some(script)
}
pub fn convert_into_p2tr(pubkey: &[u8]) -> Option<[u8; 34]> {
if pubkey.len() != 32 { return None; }
let mut script = [0u8; 34];
script[0] = 0x51; // OP_1 (Taproot indicator)
script[1] = 0x20; // OP_PUSHBYTES_32
script[2..34].copy_from_slice(pubkey);
Some(script)
}
fn parse_receiver(&self, script: &[u8], prefix: &Prefix) -> Option<[u8; 32]> {
// Standard OP_RETURN with 32 bytes of data:
// script[0] = 0x6a (OP_RETURN)
// script[1] = 0x27 (OP_PUSHBYTES_39)
// script[2..9] = 7 bytes for prefix
// script[9..41] = 32 bytes of account
if script.len() == 41 && script[0] == 0x6a && script[1] == 0x27 {
if &script[2..9] == prefix.as_ref() {
let mut receiver = [0u8; 32];
receiver.copy_from_slice(&script[9..41]);
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 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 bytes.len() < 4 {
return Err(ParseError::InvalidLength);
}
let lock_time = u32::from_le_bytes(
bytes[..4]
.try_into()
.map_err(|_| ParseError::TypeConversionFailed)?,
);
Ok(Transaction {
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,
})
}
}

View File

@ -1,7 +1,6 @@
[package] [package]
name = "ghost-networks" name = "ghost-networks"
version = "0.2.10" version = "0.1.16"
description = "Registry and lifecycle management for external network metadata and cryptographic profiles."
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -11,9 +10,7 @@ repository.workspace = true
[dependencies] [dependencies]
scale-info = { workspace = true, features = ["derive"] } scale-info = { workspace = true, features = ["derive"] }
codec = { workspace = true, features = ["max-encoded-len"] } codec = { workspace = true, features = ["max-encoded-len"] }
hex = { workspace = true, features = ["alloc"] }
num-traits = { workspace = true } num-traits = { workspace = true }
log = { workspace = true }
frame-benchmarking = { workspace = true, optional = true } frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true } frame-support = { workspace = true }
@ -22,7 +19,6 @@ pallet-staking = { workspace = true }
sp-runtime = { workspace = true } sp-runtime = { workspace = true }
sp-std = { workspace = true } sp-std = { workspace = true }
ghost-traits = { workspace = true } ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
[dev-dependencies] [dev-dependencies]
primitives = { workspace = true } primitives = { workspace = true }
@ -35,7 +31,6 @@ default = ["std"]
std = [ std = [
"scale-info/std", "scale-info/std",
"codec/std", "codec/std",
"log/std",
"num-traits/std", "num-traits/std",
"frame-support/std", "frame-support/std",
"frame-system/std", "frame-system/std",
@ -44,7 +39,6 @@ std = [
"sp-std/std", "sp-std/std",
"sp-io/std", "sp-io/std",
"ghost-traits/std", "ghost-traits/std",
"ghost-helpers/std",
"pallet-staking/std", "pallet-staking/std",
"pallet-balances/std", "pallet-balances/std",
] ]

View File

@ -6,10 +6,9 @@ use crate::Pallet as GhostNetworks;
use frame_benchmarking::v1::{benchmarks, BenchmarkError}; use frame_benchmarking::v1::{benchmarks, BenchmarkError};
use sp_runtime::Saturating; use sp_runtime::Saturating;
use ghost_helpers::networks::{ const MAX_NAME_LEN: u32 = 20;
MAX_ENDPOINT_LEN, MAX_ENDPOINTS_LEN, MAX_GATEKEEPER_LEN, const MAX_ENDPOINT_LEN: u32 = 150;
MAX_SELECTOR_LEN, const MAX_ENDPOINT_NUMBER: u32 = 20;
};
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into()); frame_system::Pallet::<T>::assert_last_event(generic_event.into());
@ -19,13 +18,18 @@ fn prepare_network<T: Config>(
n: u32, n: u32,
m: u32, m: u32,
k: 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: <T as module::Config>::NetworkId = Default::default();
let chain_id = chain_id.saturating_add((n + m).into()); let chain_id = chain_id.saturating_add((n + m).into());
let mut gatekeeper = sp_std::vec![]; let mut gatekeeper = b"0x".to_vec();
for i in 0..n { for i in 0..40 {
gatekeeper.push((i % 256) as u8); 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![]; let mut default_endpoints = sp_std::vec![];
@ -33,24 +37,24 @@ fn prepare_network<T: Config>(
default_endpoints.push(sp_std::vec![0x69; m as usize]); default_endpoints.push(sp_std::vec![0x69; m as usize]);
} }
let network = ghost_helpers::networks::NetworkDataBuilder::default() let network = NetworkData {
.with_gatekeeper(gatekeeper) chain_name: sp_std::vec![0x69; n as usize],
.with_default_endpoints(default_endpoints) default_endpoints,
.with_selector(vec![1u8; 4]) gatekeeper,
.with_network_type(NetworkType::Evm) topic_name,
.with_network_curve(NetworkCurve::Secp256k1) network_type: NetworkType::Evm,
.with_rate_limit_delay(6) finality_delay: 69,
.with_finality_delay(69) rate_limit_delay: 69,
.with_block_deviation(420) block_distance: 69,
.with_incoming_share(0) incoming_fee: 0,
.with_outgoing_share(0) outgoing_fee: 0,
.build(); };
(chain_id, network) (chain_id, network)
} }
fn create_network<T: Config>( fn create_network<T: Config>(
chain_id: T::NetworkId, chain_id: <T as module::Config>::NetworkId,
network: NetworkData, network: NetworkData,
) -> Result<Option<NetworkData>, BenchmarkError> { ) -> Result<Option<NetworkData>, BenchmarkError> {
let prev_network = match GhostNetworks::<T>::get(&chain_id) { let prev_network = match GhostNetworks::<T>::get(&chain_id) {
@ -59,8 +63,8 @@ fn create_network<T: Config>(
let authority = T::RegisterOrigin::try_successful_origin() let authority = T::RegisterOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
GhostNetworks::<T>::register_network( GhostNetworks::<T>::register_network(
authority, authority.clone(),
chain_id, chain_id.clone(),
network.clone(), network.clone(),
) )
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
@ -73,52 +77,53 @@ fn create_network<T: Config>(
benchmarks! { benchmarks! {
register_network { register_network {
let i in 1 .. MAX_GATEKEEPER_LEN; let i in 1 .. MAX_NAME_LEN;
let j in 1 .. MAX_ENDPOINT_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 (chain_id, network) = prepare_network::<T>(i, j, k);
let authority = T::RegisterOrigin::try_successful_origin() let authority = T::RegisterOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
assert_eq!(GhostNetworks::<T>::networks(chain_id), None); let prev_network = GhostNetworks::<T>::networks(chain_id.clone());
assert!(GhostNetworks::<T>::network_indexes().is_empty()); }: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone())
}: _<T::RuntimeOrigin>(authority, chain_id, network.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkRegistered {chain_id: chain_id}.into()); assert_last_event::<T>(Event::NetworkRegistered {
assert_eq!(GhostNetworks::<T>::networks(chain_id), Some(network)); chain_id: chain_id.clone(), network,
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
update_network_selector { update_network_name {
let raw_selector = sp_std::vec![0x42; MAX_SELECTOR_LEN as usize]; let n in 1 .. MAX_NAME_LEN;
let selector = BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(raw_selector) let name = sp_std::vec![0x42; n as usize];
.map_err(|_| BenchmarkError::Weightless)?;
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, selector) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), name.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkSelectorUpdated { chain_id }.into()); assert_last_event::<T>(Event::NetworkNameUpdated {
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); chain_id: chain_id.clone(), chain_name: name,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
update_network_endpoint { update_network_endpoint {
let n in 1 .. MAX_ENDPOINT_LEN; let n in 1 .. MAX_ENDPOINT_LEN;
let index = 0u32; let index_to_update = 0u32;
let raw_endpoint = sp_std::vec![0x42; n as usize]; let 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 (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, Some(index), Some(endpoint)) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), Some(index_to_update), Some(endpoint.clone()))
verify { verify {
assert_last_event::<T>(Event::NetworkEndpointUpdated {chain_id, index }.into()); assert_last_event::<T>(Event::NetworkEndpointUpdated {
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); 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 { update_network_finality_delay {
@ -126,13 +131,13 @@ benchmarks! {
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, delay) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
verify { verify {
assert_last_event::<T>(Event::NetworkFinalityDelayUpdated { assert_last_event::<T>(Event::NetworkFinalityDelayUpdated {
chain_id, finality_delay: delay, chain_id: chain_id.clone(), finality_delay: delay,
}.into()); }.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 { update_network_rate_limit_delay {
@ -140,25 +145,27 @@ benchmarks! {
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, rate_limit) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), rate_limit)
verify { verify {
assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated { assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated {
chain_id, rate_limit_delay: rate_limit, chain_id: chain_id.clone(), rate_limit_delay: rate_limit,
}.into()); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
update_network_block_deviation { update_network_block_distance {
let block_deviation = 1337; let block_distance = 1337;
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, block_deviation) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), block_distance)
verify { verify {
assert_last_event::<T>(Event::NetworkBlockDeviationUpdated { chain_id, block_deviation }.into()); assert_last_event::<T>(Event::NetworkBlockDistanceUpdated {
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); chain_id: chain_id.clone(), block_distance,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
update_network_type { update_network_type {
@ -166,92 +173,83 @@ benchmarks! {
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, network_type.clone()) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), network_type.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkTypeUpdated { chain_id, network_type }.into()); assert_last_event::<T>(Event::NetworkTypeUpdated {
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); chain_id: chain_id.clone(), network_type: network_type.clone(),
}.into());
}
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);
}
update_network_decimals {
let decimals = 18;
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, decimals)
verify {
assert_last_event::<T>(Event::NetworkDecimalsUpdated { chain_id, decimals }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), 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 (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)
verify {
assert_last_event::<T>(Event::NetworkGatekeeperUpdated { chain_id }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
}
update_incoming_network_share {
let incoming_share = 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, incoming_share)
verify {
assert_last_event::<T>(Event::NetworkIncomingShareUpdated { chain_id, incoming_share }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network); assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
update_outgoing_network_share { update_network_gatekeeper {
let outgoing_share = 1337; 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 (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin() let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id, outgoing_share) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), gatekeeper.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkOutgoingShareUpdated { chain_id, outgoing_share }.into()); assert_last_event::<T>(Event::NetworkGatekeeperUpdated {
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network); 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 {
let incoming_fee = 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.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), incoming_fee)
verify {
assert_last_event::<T>(Event::NetworkIncomingFeeUpdated {
chain_id: chain_id.clone(), incoming_fee,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_outgoing_network_fee {
let outgoing_fee = 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.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), outgoing_fee)
verify {
assert_last_event::<T>(Event::NetworkOutgoingFeeUpdated {
chain_id: chain_id.clone(), outgoing_fee,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
remove_network { remove_network {
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::RemoveOrigin::try_successful_origin() let authority = T::RemoveOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let maybe_network = create_network::<T>(chain_id, network)?; let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
assert_eq!(GhostNetworks::<T>::networks(chain_id), maybe_network); }: _<T::RuntimeOrigin>(authority, chain_id.clone())
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
}: _<T::RuntimeOrigin>(authority, chain_id)
verify { verify {
assert_last_event::<T>(Event::NetworkRemoved { chain_id }.into()); assert_last_event::<T>(Event::NetworkRemoved {
assert_eq!(GhostNetworks::<T>::networks(chain_id), None); chain_id: chain_id.clone(),
assert!(GhostNetworks::<T>::network_indexes().is_empty()); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
} }
impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test); impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
use crate::AtLeast32BitUnsigned;
pub struct MulDiv<Balance>(core::marker::PhantomData<Balance>);
impl<Balance> MulDiv<Balance>
where
Balance: Copy
+ AtLeast32BitUnsigned
+ num_traits::ops::wrapping::WrappingAdd
+ num_traits::ops::overflowing::OverflowingAdd
+ sp_std::ops::AddAssign
+ sp_std::ops::Not<Output = Balance>
+ sp_std::ops::Shl<Output = Balance>
+ sp_std::ops::Shr<Output = Balance>
+ sp_std::ops::BitAnd<Balance, Output = Balance>,
{
fn zero(&self) -> Balance {
0u32.into()
}
fn one(&self) -> Balance {
1u32.into()
}
fn bit_shift(&self) -> Balance {
let u32_shift: u32 = core::mem::size_of::<Balance>()
.saturating_mul(4)
.try_into()
.unwrap_or_default();
u32_shift.into()
}
fn least_significant_bits(&self, a: Balance) -> Balance {
a & ((self.one() << self.bit_shift()) - self.one())
}
fn most_significant_bits(&self, a: Balance) -> Balance {
a >> self.bit_shift()
}
fn two_complement(&self, a: Balance) -> Balance {
(!a).wrapping_add(&self.one())
}
fn adjusted_ratio(&self, a: Balance) -> Balance {
(self.two_complement(a) / a).wrapping_add(&self.one())
}
fn modulo(&self, a: Balance) -> Balance {
self.two_complement(a) % a
}
fn overflow_resistant_addition(
&self,
a0: Balance,
a1: Balance,
b0: Balance,
b1: Balance,
) -> (Balance, Balance) {
let (r0, overflow) = a0.overflowing_add(&b0);
let overflow: Balance = overflow.then(|| 1u32).unwrap_or_default().into();
let r1 = a1.wrapping_add(&b1).wrapping_add(&overflow);
(r0, r1)
}
fn overflow_resistant_multiplication(&self, a: Balance, b: Balance) -> (Balance, Balance) {
let (a0, a1) = (
self.least_significant_bits(a),
self.most_significant_bits(a),
);
let (b0, b1) = (
self.least_significant_bits(b),
self.most_significant_bits(b),
);
let (x, y) = (a1 * b0, b1 * a0);
let (r0, r1) = (a0 * b0, a1 * b1);
let (r0, r1) = self.overflow_resistant_addition(
r0,
r1,
self.least_significant_bits(x) << self.bit_shift(),
self.most_significant_bits(x),
);
let (r0, r1) = self.overflow_resistant_addition(
r0,
r1,
self.least_significant_bits(y) << self.bit_shift(),
self.most_significant_bits(y),
);
(r0, r1)
}
fn overflow_resistant_division(
&self,
mut a0: Balance,
mut a1: Balance,
b: Balance,
) -> (Balance, Balance) {
if b == self.one() {
return (a0, a1);
}
let zero: Balance = 0u32.into();
let (q, r) = (self.adjusted_ratio(b), self.modulo(b));
let (mut x0, mut x1) = (zero, zero);
while a1 != zero {
let (t0, t1) = self.overflow_resistant_multiplication(a1, q);
let (new_x0, new_x1) = self.overflow_resistant_addition(x0, x1, t0, t1);
x0 = new_x0;
x1 = new_x1;
let (t0, t1) = self.overflow_resistant_multiplication(a1, r);
let (new_a0, new_a1) = self.overflow_resistant_addition(t0, t1, a0, zero);
a0 = new_a0;
a1 = new_a1;
}
self.overflow_resistant_addition(x0, x1, a0 / b, zero)
}
fn mul_div(&self, a: Balance, b: Balance, c: Balance) -> Balance {
let (t0, t1) = self.overflow_resistant_multiplication(a, b);
self.overflow_resistant_division(t0, t1, c).0
}
pub fn calculate(a: Balance, b: Balance, c: Balance) -> Balance {
let inner = MulDiv(core::marker::PhantomData);
if c == inner.zero() {
return c;
}
inner.mul_div(a, b, c)
}
}

View File

@ -82,7 +82,6 @@ pallet_staking_reward_curve::build! {
parameter_types! { parameter_types! {
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const MaxNetworks: u32 = 3;
} }
ord_parameter_types! { ord_parameter_types! {
@ -99,8 +98,7 @@ impl ghost_networks::Config for Test {
type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>; type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>;
type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>; type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>;
type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>; type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>;
type MaxNetworks = MaxNetworks; type WeightInfo = crate::weights::SubstrateWeight<Test>;
type WeightInfo = ();
} }
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_networks` //! Autogenerated weights for `ghost_networks`
//! //!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! DATE: 2025-06-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000` //! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz` //! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -50,72 +50,224 @@ use sp_std::marker::PhantomData;
/// Weight functions needed for ghost_networks /// Weight functions needed for ghost_networks
pub trait WeightInfo { pub trait WeightInfo {
fn register_network(i: u32, j: u32, k: u32, ) -> Weight; 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_endpoint(n: u32, ) -> Weight;
fn update_network_finality_delay() -> Weight; fn update_network_finality_delay() -> Weight;
fn update_network_rate_limit_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_type() -> Weight;
fn update_network_curve() -> Weight;
fn update_network_decimals() -> Weight;
fn update_network_gatekeeper() -> Weight; fn update_network_gatekeeper() -> Weight;
fn update_incoming_network_share() -> Weight; fn update_network_topic_name() -> Weight;
fn update_outgoing_network_share() -> Weight; fn update_incoming_network_fee() -> Weight;
fn update_outgoing_network_fee() -> Weight;
fn remove_network() -> Weight; fn remove_network() -> Weight;
} }
/// Weight for ghost_networks using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `i` is `[1, 20]`.
/// The range of component `j` is `[1, 150]`.
/// The range of component `k` is `[1, 20]`.
fn register_network(_i: u32, _j: u32, k: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `109`
// Estimated: `3574`
// Minimum execution time: 46_023_000 picoseconds.
Weight::from_parts(97_871_287, 0)
.saturating_add(Weight::from_parts(0, 3574))
// Standard Error: 94_524
.saturating_add(Weight::from_parts(940_486, 0).saturating_mul(k.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// 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_name(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_906_000 picoseconds.
Weight::from_parts(55_537_587, 0)
.saturating_add(Weight::from_parts(0, 3767))
// Standard Error: 87_704
.saturating_add(Weight::from_parts(92_366, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// 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, 150]`.
fn update_network_endpoint(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 50_556_000 picoseconds.
Weight::from_parts(57_726_674, 0)
.saturating_add(Weight::from_parts(0, 3767))
// Standard Error: 12_261
.saturating_add(Weight::from_parts(274, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_finality_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_406_000 picoseconds.
Weight::from_parts(51_256_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_rate_limit_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_572_000 picoseconds.
Weight::from_parts(52_584_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_block_distance() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 48_880_000 picoseconds.
Weight::from_parts(50_596_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_type() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 48_282_000 picoseconds.
Weight::from_parts(49_137_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_gatekeeper() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 50_853_000 picoseconds.
Weight::from_parts(51_982_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_topic_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 50_343_000 picoseconds.
Weight::from_parts(52_380_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_incoming_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_393_000 picoseconds.
Weight::from_parts(80_966_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_outgoing_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_579_000 picoseconds.
Weight::from_parts(51_126_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn remove_network() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 44_634_000 picoseconds.
Weight::from_parts(45_815_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
}
impl WeightInfo for () { impl WeightInfo for () {
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkIndexes` (r:1 w:1) /// The range of component `i` is `[1, 20]`.
/// Proof: `GhostNetworks::NetworkIndexes` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `j` is `[1, 150]`.
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:1) /// The range of component `k` is `[1, 20]`.
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`) fn register_network(_i: u32, _j: u32, k: u32, ) -> Weight {
/// Storage: `GhostNetworks::NetworkCurvesVec` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkCurvesVec` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `i` is `[1, 65]`.
/// The range of component `j` is `[1, 128]`.
/// The range of component `k` is `[1, 10]`.
fn register_network(i: u32, j: u32, k: u32, ) -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `6` // Measured: `109`
// Estimated: `3471` // Estimated: `3574`
// Minimum execution time: 57_039_000 picoseconds. // Minimum execution time: 46_023_000 picoseconds.
Weight::from_parts(53_368_140, 0) Weight::from_parts(97_871_287, 0)
.saturating_add(Weight::from_parts(0, 3471)) .saturating_add(Weight::from_parts(0, 3574))
// Standard Error: 874 // Standard Error: 94_524
.saturating_add(Weight::from_parts(8_884, 0).saturating_mul(i.into())) .saturating_add(Weight::from_parts(940_486, 0).saturating_mul(k.into()))
// Standard Error: 442
.saturating_add(Weight::from_parts(32_778, 0).saturating_mul(j.into()))
// Standard Error: 5_890
.saturating_add(Weight::from_parts(833_630, 0).saturating_mul(k.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_selector() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 42_680_000 picoseconds.
Weight::from_parts(43_454_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `n` is `[1, 128]`. /// The range of component `n` is `[1, 20]`.
fn update_network_name(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_906_000 picoseconds.
Weight::from_parts(55_537_587, 0)
.saturating_add(Weight::from_parts(0, 3767))
// Standard Error: 87_704
.saturating_add(Weight::from_parts(92_366, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// 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, 150]`.
fn update_network_endpoint(n: u32, ) -> Weight { fn update_network_endpoint(n: u32, ) -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `166` // Measured: `302`
// Estimated: `3631` // Estimated: `3767`
// Minimum execution time: 42_265_000 picoseconds. // Minimum execution time: 50_556_000 picoseconds.
Weight::from_parts(43_357_887, 0) Weight::from_parts(57_726_674, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
// Standard Error: 457 // Standard Error: 12_261
.saturating_add(Weight::from_parts(13_745, 0).saturating_mul(n.into())) .saturating_add(Weight::from_parts(274, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
@ -123,11 +275,11 @@ impl WeightInfo for () {
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_finality_delay() -> Weight { fn update_network_finality_delay() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `166` // Measured: `302`
// Estimated: `3631` // Estimated: `3767`
// Minimum execution time: 43_008_000 picoseconds. // Minimum execution time: 49_406_000 picoseconds.
Weight::from_parts(43_917_000, 0) Weight::from_parts(51_256_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
@ -135,23 +287,23 @@ impl WeightInfo for () {
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_rate_limit_delay() -> Weight { fn update_network_rate_limit_delay() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 43_104_000 picoseconds. // Minimum execution time: 49_572_000 picoseconds.
Weight::from_parts(43_981_000, 0) Weight::from_parts(52_584_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// 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: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 41_734_000 picoseconds. // Minimum execution time: 48_880_000 picoseconds.
Weight::from_parts(42_645_000, 0) Weight::from_parts(50_596_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
@ -159,39 +311,11 @@ impl WeightInfo for () {
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_type() -> Weight { fn update_network_type() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 42_080_000 picoseconds. // Minimum execution time: 48_282_000 picoseconds.
Weight::from_parts(42_404_000, 0) Weight::from_parts(49_137_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkCurves` (r:2 w:2)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkCurvesVec` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkCurvesVec` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn update_network_curve() -> Weight {
// Proof Size summary in bytes:
// Measured: `180`
// Estimated: `6120`
// Minimum execution time: 82_975_000 picoseconds.
Weight::from_parts(84_583_000, 0)
.saturating_add(Weight::from_parts(0, 6120))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 41_602_000 picoseconds
Weight::from_parts(42_603_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
@ -199,54 +323,60 @@ impl WeightInfo for () {
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_network_gatekeeper() -> Weight { fn update_network_gatekeeper() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 42_643_000 picoseconds. // Minimum execution time: 50_853_000 picoseconds.
Weight::from_parts(43_190_000, 0) Weight::from_parts(51_982_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_incoming_network_share() -> Weight { fn update_network_topic_name() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 42_978_000 picoseconds. // Minimum execution time: 50_343_000 picoseconds.
Weight::from_parts(43_002_000, 0) Weight::from_parts(52_380_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_outgoing_network_share() -> Weight { fn update_incoming_network_fee() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `167` // Measured: `302`
// Estimated: `3632` // Estimated: `3767`
// Minimum execution time: 43_436_000 picoseconds. // Minimum execution time: 49_393_000 picoseconds.
Weight::from_parts(44_193_000, 0) Weight::from_parts(80_966_000, 0)
.saturating_add(Weight::from_parts(0, 3632)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn update_outgoing_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `302`
// Estimated: `3767`
// Minimum execution time: 49_579_000 picoseconds.
Weight::from_parts(51_126_000, 0)
.saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:1) /// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkIndexes` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkIndexes` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkCurvesVec` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkCurvesVec` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn remove_network() -> Weight { fn remove_network() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `180` // Measured: `302`
// Estimated: `3645` // Estimated: `3767`
// Minimum execution time: 75_969_000 picoseconds. // Minimum execution time: 44_634_000 picoseconds.
Weight::from_parts(77_610_000, 0) Weight::from_parts(45_815_000, 0)
.saturating_add(Weight::from_parts(0, 3645)) .saturating_add(Weight::from_parts(0, 3767))
.saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(4)) .saturating_add(RocksDbWeight::get().writes(1))
} }
} }

View File

@ -1,63 +0,0 @@
[package]
name = "ghost-nomination-pools"
version = "25.0.1"
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",
]

View File

@ -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",
]

View File

@ -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
);
}

View File

@ -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;

View File

@ -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)
}

View File

@ -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"

View File

@ -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<_>>(),
);
}
})
})
}
}

View File

@ -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"]

View File

@ -1,3 +0,0 @@
Runtime API definition for nomination-pools pallet.
License: Apache-2.0

View File

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

View File

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

View File

@ -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 }

View File

@ -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 }
]
);
});
}

View File

@ -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()
}

View File

@ -1,7 +1,7 @@
[package] [package]
name = "ghost-slow-clap" name = "ghost-slow-clap"
version = "0.4.29" version = "0.3.53"
description = "DEPRECATED: Applause protocol for the EVM bridge." description = "Applause protocol for the EVM bridge"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -28,7 +28,6 @@ sp-io = { workspace = true }
sp-std = { workspace = true } sp-std = { workspace = true }
ghost-networks = { workspace = true } ghost-networks = { workspace = true }
ghost-traits = { workspace = true }
[dev-dependencies] [dev-dependencies]
pallet-balances = { workspace = true } pallet-balances = { workspace = true }

View File

@ -3,6 +3,7 @@
use super::*; use super::*;
use frame_benchmarking::v1::*; use frame_benchmarking::v1::*;
use frame_support::traits::fungible::Inspect;
use frame_system::RawOrigin; use frame_system::RawOrigin;
pub fn create_account<T: Config>() -> T::AccountId { pub fn create_account<T: Config>() -> T::AccountId {
@ -11,46 +12,73 @@ pub fn create_account<T: Config>() -> T::AccountId {
.expect("32 bytes always construct an AccountId32") .expect("32 bytes always construct an AccountId32")
} }
pub fn prepare_evm_network<T: Config>(network_id: NetworkIdOf<T>) {
let network_data = NetworkData {
chain_name: "Ethereum".into(),
default_endpoints: vec![b"https://other.endpoint.network.com".to_vec()],
finality_delay: 69,
avg_block_speed: 69,
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,
outgoing_fee: 0,
};
let _ = T::NetworkDataHandler::register(network_id, network_data.clone());
}
benchmarks! { benchmarks! {
slow_clap { slow_clap {
let network_id = NetworkIdOf::<T>::default();
prepare_evm_network::<T>(network_id);
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance(); let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let receiver = create_account::<T>(); let receiver = create_account::<T>();
let amount = minimum_balance + minimum_balance; let amount = minimum_balance + minimum_balance;
let network_id = NetworkIdOf::<T>::default();
let session_index = T::ValidatorSet::session_index(); let session_index = T::ValidatorSet::session_index();
let transaction_hash = H256::repeat_byte(1u8);
let authorities = vec![T::AuthorityId::generate_pair(None)]; let authorities = vec![T::AuthorityId::generate_pair(None)];
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone()) let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
.map_err(|()| "more than the maximum number of keys provided")?; .map_err(|()| "more than the maximum number of keys provided")?;
Authorities::<T>::set(&session_index, bounded_authorities); Authorities::<T>::set(&session_index, bounded_authorities);
let authority_index = 0u32;
let clap = Clap {
session_index: 0,
authority_index: 0,
transaction_hash: H256::repeat_byte(1u8),
block_number: 69,
removed: false,
network_id,
receiver: receiver.clone(),
amount,
};
let authority_id = authorities
.get(0usize)
.expect("first authority should exist");
let encoded_clap = clap.encode();
let signature = authority_id.sign(&encoded_clap)
.ok_or("couldn't make signature")?;
}: _(RawOrigin::None, clap, signature)
verify {
assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), amount);
}
self_applause {
let session_index = T::ValidatorSet::session_index();
let next_session_index = session_index.saturating_add(1);
let authorities = vec![
T::AuthorityId::generate_pair(None),
T::AuthorityId::generate_pair(None),
];
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
.map_err(|()| "more than the maximum number of keys provided")?;
Authorities::<T>::set(&session_index, bounded_authorities.clone());
Authorities::<T>::set(&next_session_index, bounded_authorities);
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let receiver = create_account::<T>();
let receiver_clone = receiver.clone();
let amount = minimum_balance + minimum_balance;
let network_id = NetworkIdOf::<T>::default();
let transaction_hash = H256::repeat_byte(1u8);
let unique_transaction_hash = <Pallet<T>>::generate_unique_hash(
&receiver,
&amount,
&network_id,
);
let storage_key = (session_index, &transaction_hash, &unique_transaction_hash);
let next_storage_key = (next_session_index, &transaction_hash, &unique_transaction_hash);
<Pallet::<T>>::trigger_nullification_for_benchmark();
let clap = Clap { let clap = Clap {
session_index, session_index,
authority_index, authority_index: 0,
transaction_hash, transaction_hash,
block_number: 69, block_number: 69,
removed: false, removed: false,
@ -58,88 +86,28 @@ benchmarks! {
receiver: receiver.clone(), receiver: receiver.clone(),
amount, amount,
}; };
let args_hash = Pallet::<T>::generate_unique_hash(&clap);
let authority_id = authorities let authority_id = authorities
.get(authority_index as usize) .get(0usize)
.expect("first authority should exist"); .expect("first authority should exist");
let signature = authority_id.sign(&clap.encode()) let encoded_clap = clap.encode();
.ok_or("couldn't make signature")?; let signature = authority_id.sign(&encoded_clap).unwrap();
Pallet::<T>::slow_clap(RawOrigin::None.into(), clap, signature)?;
Pallet::<T>::trigger_nullification_for_benchmark();
let empty_bitmap = BitMap::new(1); assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), Default::default());
DisabledAuthorityIndexes::<T>::insert(session_index, empty_bitmap); assert_eq!(ApplausesForTransaction::<T>::get(&storage_key), false);
assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_none(), true); frame_system::Pallet::<T>::on_initialize(1u32.into());
}: _(RawOrigin::None, clap, signature)
let mut fake_received_clap =
BoundedBTreeSet::<AuthIndex, T::MaxAuthorities>::new();
assert_eq!(fake_received_clap.try_insert(1).unwrap(), true);
pallet::ReceivedClaps::<T>::insert(&next_storage_key, fake_received_clap);
}: _(RawOrigin::Signed(receiver_clone), network_id, session_index, transaction_hash, receiver_clone.clone(), amount)
verify { verify {
assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_some(), true);
assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), amount); assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), amount);
} assert_eq!(ApplausesForTransaction::<T>::get(&storage_key), true);
commit_block {
let session_index = T::ValidatorSet::session_index();
let network_id = NetworkIdOf::<T>::default();
prepare_evm_network::<T>(network_id);
let authorities = vec![T::AuthorityId::generate_pair(None)];
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
.map_err(|()| "more than the maximum number of keys provided")?;
Authorities::<T>::set(&session_index, bounded_authorities);
let authority_index = 0u32;
let block_commitment = BlockCommitment {
session_index,
authority_index,
network_id,
last_stored_block: 69,
};
let authority_id = authorities
.get(authority_index as usize)
.expect("first authority should exist");
let signature = authority_id.sign(&block_commitment.encode())
.ok_or("couldn't make signature")?;
}: _(RawOrigin::None, block_commitment, signature)
verify {
let current_block_number = <Pallet<T> as BlockNumberProvider>::current_block_number();
let stored_commitment = BlockCommitments::<T>::get(&network_id)
.get(&authority_index)
.cloned()
.unwrap_or_default();
assert_eq!(stored_commitment.last_stored_block, 69);
assert_eq!(stored_commitment.commits, 1);
assert_eq!(stored_commitment.last_updated, current_block_number);
}
try_offend_validators {
let n in 1 .. T::MaxAuthorities::get();
let d in 0 .. T::MaxAuthorities::get();
let session_index = T::ValidatorSet::session_index();
let mut validators_vec = Vec::new();
for i in 0..T::MaxAuthorities::get() {
let v = account("validator", i, 0);
validators_vec.push(v);
}
let validators = WeakBoundedVec::<ValidatorId<T>, T::MaxAuthorities>::try_from(validators_vec)
.expect("weak bounded vec should be constructed; qed");
let offence_type = OffenceType::CommitmentOffence;
let disabled_bitmap = BitMap::new(T::MaxAuthorities::get());
let mut offence_bitmap = BitMap::new(T::MaxAuthorities::get());
for i in 0..d {
offence_bitmap.set(i);
}
}: {
let _ = Pallet::<T>::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type
);
} }
impl_benchmark_test_suite!( impl_benchmark_test_suite!(

View File

@ -1,117 +0,0 @@
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::RuntimeDebug;
use sp_std::collections::btree_map::BTreeMap;
use crate::AuthIndex;
pub type BucketId = u32;
pub type Bucket = u64;
#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct BitMap {
buckets: BTreeMap<BucketId, Bucket>,
max_length: u32,
max_value: u32,
}
impl BitMap {
pub fn new(max_value: u32) -> Self {
let mut buckets: BTreeMap<BucketId, Bucket> = Default::default();
let max_length = (max_value >> Self::size_of_bucket()) + 1;
for bucket_id in 0..(max_length) {
buckets.insert(bucket_id, 0);
}
BitMap {
buckets,
max_length,
max_value,
}
}
pub fn size_of_bucket() -> u32 {
(core::mem::size_of::<Bucket>() * 8).trailing_zeros()
}
pub fn iter_buckets(&self) -> impl Iterator<Item = (&BucketId, &Bucket)> {
self.buckets.iter()
}
pub fn exists(&self, index: &AuthIndex) -> bool {
let (bucket_id, bit_position) = self.bitmap_positions(index);
self.buckets
.get(&bucket_id)
.map(|bucket| (bucket >> bit_position) & 1 == 1)
.unwrap_or_default()
}
pub fn set(&mut self, index: AuthIndex) {
let (bucket_id, bit_position) = self.bitmap_positions(&index);
if bucket_id < self.max_length {
let bucket = self.buckets.entry(bucket_id).or_insert(0);
*bucket |= 1 << bit_position;
}
}
pub fn unset(&mut self, index: AuthIndex) {
let (bucket_id, bit_position) = self.bitmap_positions(&index);
if let Some(bucket) = self.buckets.get_mut(&bucket_id) {
*bucket &= !(1 << bit_position);
if *bucket == 0 {
self.buckets.remove(&bucket_id);
}
}
}
pub fn get_bucket(&self, bucket_id: &BucketId) -> Bucket {
self.buckets.get(bucket_id).copied().unwrap_or_default()
}
pub fn count_ones(&self) -> u32 {
let zeros: u32 = self
.iter_buckets()
.map(|(_, bucket)| bucket.count_zeros())
.sum();
let total_bits = self.total_bits();
total_bits.saturating_sub(zeros)
}
pub fn bitor(self, rhs: Self) -> Self {
let (mut base, to_merge) = if self.buckets.len() < rhs.buckets.len() {
(rhs, self)
} else {
(self, rhs)
};
for (key, rhs_value) in to_merge.buckets {
base.buckets
.entry(key)
.and_modify(|lhs_value| *lhs_value |= rhs_value)
.or_insert(rhs_value);
}
base
}
pub fn max_value(&self) -> u32 {
self.max_value
}
fn total_bits(&self) -> u32 {
let size_of_bucket = Self::size_of_bucket();
let bucket_length: u32 = 1 << size_of_bucket;
self.max_length.saturating_mul(bucket_length)
}
fn bitmap_positions(&self, index: &AuthIndex) -> (u32, u32) {
let size_of_bucket = Self::size_of_bucket();
let bucket_length = 1 << size_of_bucket;
let bucket_id = index >> size_of_bucket;
let bit_position = index % bucket_length;
(bucket_id, bit_position)
}
}

View File

@ -8,7 +8,7 @@ where
Ok(Some(s.as_bytes().to_vec())) Ok(Some(s.as_bytes().to_vec()))
} }
pub fn de_string_to_block_number<'de, D>(de: D) -> Result<Option<u64>, D::Error> pub fn de_string_to_u64<'de, D>(de: D) -> Result<Option<u64>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
@ -17,7 +17,7 @@ where
Ok(u64::from_str_radix(s, 16).ok()) Ok(u64::from_str_radix(s, 16).ok())
} }
pub fn de_string_to_block_number_pure<'de, D>(de: D) -> Result<u64, D::Error> pub fn de_string_to_u64_pure<'de, D>(de: D) -> Result<u64, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {

View File

@ -1,14 +1,9 @@
use sp_runtime::{traits::UniqueSaturatedInto, SaturatedConversion, Saturating};
use sp_staking::SessionIndex;
use crate::{ use crate::{
deserialisations::{ deserialisations::{
de_string_to_block_number, de_string_to_block_number_pure, de_string_to_bytes, de_string_to_bytes, de_string_to_h256, de_string_to_u64, de_string_to_u64_pure,
de_string_to_h256, de_string_to_vec_of_bytes, de_string_to_vec_of_bytes,
}, },
AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, BlockNumberFor, Call, Clap, Config, Decode, Deserialize, Encode, RuntimeDebug, Vec, H256,
Decode, Deserialize, Encode, ExternalBlockNumber, NetworkIdOf, RuntimeAppPublic, RuntimeDebug,
SubmitTransaction, Vec, BLOCK_COMMITMENT_DELAY, H256, LOG_TARGET,
}; };
const NUMBER_OF_TOPICS: usize = 3; const NUMBER_OF_TOPICS: usize = 3;
@ -28,8 +23,8 @@ pub struct EvmResponse {
#[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)] #[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)]
#[serde(untagged)] #[serde(untagged)]
pub enum EvmResponseType { pub enum EvmResponseType {
#[serde(deserialize_with = "de_string_to_block_number_pure")] #[serde(deserialize_with = "de_string_to_u64_pure")]
BlockNumber(ExternalBlockNumber), BlockNumber(u64),
TransactionLogs(Vec<Log>), TransactionLogs(Vec<Log>),
} }
@ -38,203 +33,13 @@ pub enum EvmResponseType {
pub struct Log { pub struct Log {
#[serde(default, deserialize_with = "de_string_to_h256")] #[serde(default, deserialize_with = "de_string_to_h256")]
pub transaction_hash: Option<H256>, pub transaction_hash: Option<H256>,
#[serde(default, deserialize_with = "de_string_to_block_number")] #[serde(default, deserialize_with = "de_string_to_u64")]
pub block_number: Option<ExternalBlockNumber>, pub block_number: Option<u64>,
#[serde(default, deserialize_with = "de_string_to_vec_of_bytes")] #[serde(default, deserialize_with = "de_string_to_vec_of_bytes")]
pub topics: Vec<Vec<u8>>, pub topics: Vec<Vec<u8>>,
pub removed: bool, pub removed: bool,
} }
impl EvmResponseType {
fn prepare_clap<T: Config>(
&self,
authority_index: AuthIndex,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
log: &Log,
) -> Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>> {
Clap {
authority_index,
session_index,
network_id,
removed: log.removed,
receiver: T::AccountId::decode(&mut &log.topics[1][0..32])
.expect("32 bytes always construct an AccountId32"),
amount: u128::from_be_bytes(
log.topics[2][16..32]
.try_into()
.expect("amount is valid hex; qed"),
)
.saturated_into::<BalanceOf<T>>(),
transaction_hash: log.transaction_hash.clone().expect("tx hash exists; qed"),
block_number: log.block_number.expect("block number exists; qed"),
}
}
fn iter_claps_from_logs<T: Config>(
&self,
authority_index: AuthIndex,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
) -> Vec<Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>> {
match self {
EvmResponseType::TransactionLogs(evm_logs) => evm_logs
.iter()
.filter_map(move |log| {
log.is_sufficient().then(|| {
self.prepare_clap::<T>(authority_index, session_index, network_id, log)
})
})
.collect(),
EvmResponseType::BlockNumber(_) => Vec::new(),
}
}
fn sign_and_submit_claps<T: Config>(
&self,
authority_index: AuthIndex,
authority_key: T::AuthorityId,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
) {
let claps = self.iter_claps_from_logs::<T>(authority_index, session_index, network_id);
let claps_len = claps.len();
log::info!(
target: LOG_TARGET,
"👻 Found {:?} claps for network {:?}",
claps_len,
network_id,
);
for (clap_index, clap) in claps.iter().enumerate() {
let signature = match authority_key.sign(&clap.encode()) {
Some(signature) => signature,
None => {
log::info!(
target: LOG_TARGET,
"👻 Clap #{} signing failed from authority #{:?} for network {:?}",
clap_index,
authority_index,
network_id,
);
return;
}
};
let call = Call::slow_clap {
clap: clap.clone(),
signature,
};
if let Err(e) =
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
{
log::info!(
target: LOG_TARGET,
"👻 Failed to submit clap #{} from authority #{:?} for network {:?}: {:?}",
clap_index,
authority_index,
network_id,
e,
);
}
}
}
fn sign_and_submit_block_commitment<T: Config>(
&self,
block_now: BlockNumberFor<T>,
from_block: ExternalBlockNumber,
authority_index: AuthIndex,
authority_key: T::AuthorityId,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
) {
let block_commitment = BlockCommitment {
session_index,
authority_index,
network_id,
last_stored_block: from_block,
};
let stored_last_updated = BlockCommitments::<T>::get(&network_id)
.get(&authority_index)
.map(|details| {
details
.last_updated
.saturating_add(BLOCK_COMMITMENT_DELAY.unique_saturated_into())
})
.unwrap_or_default();
if block_now < stored_last_updated {
return;
}
log::info!(
target: LOG_TARGET,
"👻 New block commitment from authority #{:?} for network {:?}",
authority_index,
network_id,
);
let signature = match authority_key.sign(&block_commitment.encode()) {
Some(signature) => signature,
None => {
log::info!(
target: LOG_TARGET,
"👻 Block commitment signing failed from authority #{:?} for network {:?}",
authority_index,
network_id,
);
return;
}
};
let call = Call::commit_block {
block_commitment,
signature,
};
if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
log::info!(
target: LOG_TARGET,
"👻 Failed to submit block commitment from authority #{:?} for network {:?}: {:?}",
authority_index,
network_id,
e,
);
}
}
pub fn sign_and_submit<T: Config>(
&self,
block_now: BlockNumberFor<T>,
from_block: ExternalBlockNumber,
authority_index: AuthIndex,
authority_key: T::AuthorityId,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
) {
match self {
EvmResponseType::TransactionLogs(_) => self.sign_and_submit_claps::<T>(
authority_index,
authority_key,
session_index,
network_id,
),
EvmResponseType::BlockNumber(_) => self.sign_and_submit_block_commitment::<T>(
block_now,
from_block,
authority_index,
authority_key,
session_index,
network_id,
),
}
}
}
impl Log { impl Log {
pub fn is_sufficient(&self) -> bool { pub fn is_sufficient(&self) -> bool {
self.transaction_hash.is_some() self.transaction_hash.is_some()

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
pub mod v3;
pub type MigrateV2ToV3<T> = frame_support::migrations::VersionedMigration<
2,
3,
v3::CommitmentDetailsTypesChanged<T>,
crate::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;

View File

@ -1,71 +0,0 @@
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
storage,
traits::{Get, UncheckedOnRuntimeUpgrade},
weights::Weight,
};
use ghost_networks::NetworkDataInspectHandler;
use scale_info::TypeInfo;
use sp_runtime::traits::{BlockNumberProvider, UniqueSaturatedInto};
use sp_runtime::RuntimeDebug;
use sp_std::marker::PhantomData;
use crate::{AuthIndex, BTreeMap, BlockCommitments, CommitmentDetails, Config, Pallet, LOG_TARGET};
#[derive(
RuntimeDebug,
Default,
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
)]
pub struct OldCommitmentDetails {
pub last_stored_block: u64,
pub last_updated: u64,
pub commits: u64,
}
pub struct CommitmentDetailsTypesChanged<T>(PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for CommitmentDetailsTypesChanged<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(3);
let current_block_number = <Pallet<T> as BlockNumberProvider>::current_block_number();
for network_id in T::NetworkDataHandler::iter_indexes() {
let mut new_btree_map = BTreeMap::new();
let key_hash = BlockCommitments::<T>::hashed_key_for(&network_id);
let old_commitments = storage::unhashed::take_or_default::<
BTreeMap<AuthIndex, OldCommitmentDetails>,
>(&key_hash);
for (authority_index, details) in old_commitments.iter() {
new_btree_map.insert(
authority_index,
CommitmentDetails {
last_stored_block: details.last_stored_block,
last_updated: current_block_number,
commits: details.commits.unique_saturated_into(),
},
);
}
BlockCommitments::<T>::insert(&network_id, new_btree_map);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
log::info!(
target: LOG_TARGET,
"👻 Block commitments migrated from unix timestamp to internal block for network {:?}",
network_id,
);
}
weight
}
}

View File

@ -5,12 +5,11 @@ use frame_support::{
traits::{ConstU32, ConstU64}, traits::{ConstU32, ConstU64},
}; };
use frame_system::EnsureRoot; use frame_system::EnsureRoot;
use ghost_traits::evictor::StakingEvictor;
use pallet_session::historical as pallet_session_historical; use pallet_session::historical as pallet_session_historical;
use sp_runtime::{ use sp_runtime::{
curve::PiecewiseLinear, curve::PiecewiseLinear,
testing::{TestXt, UintAuthorityId}, testing::{TestXt, UintAuthorityId},
traits::{AtLeast32BitUnsigned, ConvertInto}, traits::ConvertInto,
Permill, Permill,
}; };
use sp_staking::{ use sp_staking::{
@ -20,8 +19,8 @@ use sp_staking::{
use sp_runtime::BuildStorage; use sp_runtime::BuildStorage;
use crate::{self as slow_clap}; use crate as slow_clap;
use crate::{Config, ExposureListener}; use crate::Config;
type Block = frame_system::mocking::MockBlock<Runtime>; type Block = frame_system::mocking::MockBlock<Runtime>;
@ -37,14 +36,17 @@ frame_support::construct_runtime!(
); );
parameter_types! { parameter_types! {
pub static EvicatedValidators: Vec<u64> = vec![]; pub static Validators: Option<Vec<u64>> = Some(vec![
pub static FixedValidators: Vec<u64> = vec![0, 1, 2, 3]; 1,
2,
3,
]);
} }
pub struct TestSessionManager; pub struct TestSessionManager;
impl pallet_session::SessionManager<u64> for TestSessionManager { impl pallet_session::SessionManager<u64> for TestSessionManager {
fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> { fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
Some(FixedValidators::get()) Validators::mutate(|l| l.take())
} }
fn end_session(_: SessionIndex) {} fn end_session(_: SessionIndex) {}
fn start_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {}
@ -52,14 +54,17 @@ impl pallet_session::SessionManager<u64> for TestSessionManager {
impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager { impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> { fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
Some(FixedValidators::get().iter().map(|l| (*l, *l)).collect()) Validators::mutate(|l| {
l.take()
.map(|validators| validators.iter().map(|v| (*v, *v)).collect())
})
} }
fn end_session(_: SessionIndex) {} fn end_session(_: SessionIndex) {}
fn start_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {}
} }
type IdentificationTuple = (u64, u64); type IdentificationTuple = (u64, u64);
type Offence = crate::SlowClapOffence<IdentificationTuple>; type Offence = crate::ThrottlingOffence<IdentificationTuple>;
parameter_types! { parameter_types! {
pub static Offences: Vec<(Vec<u64>, Offence)> = vec![]; pub static Offences: Vec<(Vec<u64>, Offence)> = vec![];
@ -85,7 +90,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
let mut result = sp_io::TestExternalities::new(t); let mut result = sp_io::TestExternalities::new(t);
result.execute_with(|| { result.execute_with(|| {
for i in 0..=3 { for i in 1..=3 {
System::inc_providers(&i); System::inc_providers(&i);
assert_eq!( assert_eq!(
Session::set_keys(RuntimeOrigin::signed(i), i.into(), vec![],), Session::set_keys(RuntimeOrigin::signed(i), i.into(), vec![],),
@ -132,7 +137,6 @@ parameter_types! {
parameter_types! { parameter_types! {
pub static MockAverageSessionLength: Option<u64> = None; pub static MockAverageSessionLength: Option<u64> = None;
pub const MaxNetworks: u32 = 5;
} }
impl ghost_networks::Config for Runtime { impl ghost_networks::Config for Runtime {
@ -142,7 +146,6 @@ impl ghost_networks::Config for Runtime {
type RegisterOrigin = EnsureRoot<Self::AccountId>; type RegisterOrigin = EnsureRoot<Self::AccountId>;
type UpdateOrigin = EnsureRoot<Self::AccountId>; type UpdateOrigin = EnsureRoot<Self::AccountId>;
type RemoveOrigin = EnsureRoot<Self::AccountId>; type RemoveOrigin = EnsureRoot<Self::AccountId>;
type MaxNetworks = MaxNetworks;
type WeightInfo = (); type WeightInfo = ();
} }
@ -161,7 +164,6 @@ parameter_types! {
pub static ExistentialDeposit: u64 = 2; pub static ExistentialDeposit: u64 = 2;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const HistoryDepth: u32 = 10; pub const HistoryDepth: u32 = 10;
pub const EpochDuration: u64 = 80;
} }
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
@ -172,61 +174,6 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = (); type WeightInfo = ();
} }
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
Balance: AtLeast32BitUnsigned + From<u64>,
{
fn get_accounts_by_indexes(
indexes_iterator: impl Iterator<Item = usize>,
) -> sp_std::prelude::Vec<u64> {
let all_validators = FixedValidators::get();
indexes_iterator
.filter_map(|i| all_validators.get(i).copied())
.collect()
}
fn get_account_by_index(index: usize) -> Option<u64> {
FixedValidators::get().get(index).copied()
}
fn get_total_exposure() -> Balance {
1_000_000_000u64.into()
}
fn get_validator_exposure(validator: &u64) -> Balance {
match validator {
0 => 250_000_000u64,
1 => 200_000_000u64,
2 => 250_000_000u64,
3 => 300_000_000u64,
_ => 0,
}
.into()
}
}
impl Config for Runtime { impl Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type AuthorityId = UintAuthorityId; type AuthorityId = UintAuthorityId;
@ -237,15 +184,13 @@ impl Config for Runtime {
type BlockNumberProvider = System; type BlockNumberProvider = System;
type ReportUnresponsiveness = OffenceHandler; type ReportUnresponsiveness = OffenceHandler;
type DisabledValidators = Session; type DisabledValidators = Session;
type ExposureListener = TestExposureListener;
type StakingEvictor = TestStakingEvictor;
type MaxAuthorities = ConstU32<5>; type MaxAuthorities = ConstU32<5>;
type ApplauseThreshold = ConstU32<500_000_000>; type ApplauseThreshold = ConstU32<50>;
type OffenceThreshold = ConstU32<0>;
type UnsignedPriority = ConstU64<{ 1 << 20 }>; type UnsignedPriority = ConstU64<{ 1 << 20 }>;
type HistoryDepth = HistoryDepth; type HistoryDepth = HistoryDepth;
type MinAuthoritiesNumber = ConstU32<1>; type MinAuthoritiesNumber = ConstU32<2>;
type EpochDuration = EpochDuration;
type WeightInfo = (); type WeightInfo = ();
} }
@ -264,7 +209,32 @@ pub fn advance_session() {
let now = System::block_number().max(1); let now = System::block_number().max(1);
System::set_block_number(now + 1); System::set_block_number(now + 1);
Session::rotate_session(); Session::rotate_session();
let session_index = Session::current_index(); let session_index = Session::current_index();
let authorities = Session::validators()
.into_iter()
.map(UintAuthorityId)
.collect();
SlowClap::set_test_authorities(session_index, authorities);
assert_eq!(session_index, (now / Period::get()) as u32);
}
pub fn advance_session_with_authority(authority: u64) {
let now = System::block_number().max(1);
System::set_block_number(now + 1);
Session::rotate_session();
let session_index = Session::current_index();
SlowClap::set_test_authorities(
session_index,
vec![
UintAuthorityId::from(authority),
UintAuthorityId::from(69),
UintAuthorityId::from(420),
UintAuthorityId::from(1337),
],
);
assert_eq!(session_index, (now / Period::get()) as u32); assert_eq!(session_index, (now / Period::get()) as u32);
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_slow_clap` //! Autogenerated weights for `ghost_slow_clap`
//! //!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-02-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! DATE: 2025-11-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000` //! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz` //! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -48,29 +48,24 @@ use core::marker::PhantomData;
pub trait WeightInfo { pub trait WeightInfo {
fn slow_clap() -> Weight; fn slow_clap() -> Weight;
fn commit_block()-> Weight; fn self_applause()-> Weight;
fn try_offend_validators(validators_len: u32, offenders_len: u32) -> Weight;
} }
impl WeightInfo for () { /// Weight functions for `ghost_slow_clap`.
/// Storage: `GhostNetworks::Networks` (r:1 w:0) pub struct SubstrateWeight<T>(PhantomData<T>);
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `GhostSlowClaps::LatestExecutedBlock` (r:1 w:1)
/// Proof: `GhostSlowClaps::LatestExecutedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::ApplauseDetails` (r:1 w:1)
/// Proof: `GhostSlowClaps::ApplauseDetails` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::Authorities` (r:1 w:0) /// Storage: `GhostSlowClaps::Authorities` (r:1 w:0)
/// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::DisabledAuthorityIndexes` (r:1 w:0) /// Storage: `GhostSlowClaps::ReceivedClaps` (r:1 w:1)
/// Proof: `GhostSlowClaps::DisabledAuthorityIndexes` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::ReceivedClaps` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::TotalExposure` (r:1 w:0) /// Storage: `GhostSlowClaps::ClapsInSession` (r:1 w:1)
/// Proof: `GhostSlowClaps::TotalExposure` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::ClapsInSession` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Session::Validators` (r:1 w:0) /// Storage: `GhostSlowClaps::ApplausesForTransaction` (r:1 w:1)
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::ApplausesForTransaction` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Staking::CurrentEra` (r:1 w:0) /// Storage: `GhostNetworks::NullifyNeeded` (r:1 w:0)
/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Proof: `GhostNetworks::NullifyNeeded` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Staking::ErasStakersOverview` (r:1 w:0) /// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1) /// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1)
/// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1) /// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1)
@ -81,70 +76,101 @@ impl WeightInfo for () {
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn slow_clap() -> Weight { fn slow_clap() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `1417` // Measured: `355`
// Estimated: `4882` // Estimated: `3820`
// Minimum execution time: 305_397_000 picoseconds. // Minimum execution time: 213_817_000 picoseconds.
Weight::from_parts(310_207_000, 0) Weight::from_parts(216_977_000, 0)
.saturating_add(Weight::from_parts(0, 4882)) .saturating_add(Weight::from_parts(0, 3820))
.saturating_add(RocksDbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(6)) .saturating_add(T::DbWeight::get().writes(7))
} }
/// Storage: `Session::CurrentIndex` (r:1 w:0) /// Storage: `GhostSlowClaps::ReceivedClaps` (r:1 w:0)
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::ReceivedClaps` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::LatestExecutedBlock` (r:1 w:0) /// Storage: `GhostSlowClaps::Authorities` (r:1 w:0)
/// Proof: `GhostSlowClaps::LatestExecutedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::ApplausesForTransaction` (r:1 w:1)
/// Proof: `GhostSlowClaps::ApplausesForTransaction` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NullifyNeeded` (r:1 w:0)
/// Proof: `GhostNetworks::NullifyNeeded` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::Networks` (r:1 w:0) /// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::BlockCommitments` (r:1 w:1) /// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1)
/// Proof: `GhostSlowClaps::BlockCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn commit_block() -> Weight { /// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1)
/// Proof: `GhostNetworks::BridgedImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::AccumulatedCommission` (r:1 w:1)
/// Proof: `GhostNetworks::AccumulatedCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn self_applause() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `701` // Measured: `655`
// Estimated: `4166` // Estimated: `4120`
// Minimum execution time: 88_007_000 picoseconds. // Minimum execution time: 210_676_000 picoseconds.
Weight::from_parts(89_662_000, 0) Weight::from_parts(212_905_000, 0)
.saturating_add(Weight::from_parts(0, 4166)) .saturating_add(Weight::from_parts(0, 4120))
.saturating_add(RocksDbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().reads(9))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes(5))
} }
/// Storage: `Staking::ActiveEra` (r:1 w:0) }
/// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
/// Storage: `Staking::ErasStakersOverview` (r:100000 w:0) impl WeightInfo for () {
/// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `GhostSlowClaps::Authorities` (r:1 w:0)
/// Storage: `Staking::ErasStakers` (r:100000 w:0) /// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Staking::ErasStakers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `GhostSlowClaps::ReceivedClaps` (r:1 w:1)
/// Storage: `Session::Validators` (r:1 w:0) /// Proof: `GhostSlowClaps::ReceivedClaps` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `GhostSlowClaps::ClapsInSession` (r:1 w:1)
/// Storage: `Offences::ConcurrentReportsIndex` (r:1 w:1) /// Proof: `GhostSlowClaps::ClapsInSession` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Offences::ConcurrentReportsIndex` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `GhostSlowClaps::ApplausesForTransaction` (r:1 w:1)
/// Storage: `Offences::Reports` (r:97959 w:97959) /// Proof: `GhostSlowClaps::ApplausesForTransaction` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Offences::Reports` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `GhostNetworks::NullifyNeeded` (r:1 w:0)
/// Storage: `Staking::SlashRewardFraction` (r:1 w:0) /// Proof: `GhostNetworks::NullifyNeeded` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Proof: `Staking::SlashRewardFraction` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) /// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) /// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1)
/// Storage: `Staking::Invulnerables` (r:1 w:0) /// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1)
/// Storage: `Staking::SlashingSpans` (r:97959 w:97959) /// Proof: `GhostNetworks::BridgedImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `GhostNetworks::AccumulatedCommission` (r:1 w:1)
/// Storage: `Staking::DisabledValidators` (r:1 w:1) /// Proof: `GhostNetworks::AccumulatedCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Proof: `Staking::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1)
/// The range of component `n` is `[1, 100000]`. /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// The range of component `d` is `[0, 100000]`. fn slow_clap() -> Weight {
fn try_offend_validators(n: u32, d: u32, ) -> Weight { // Proof Size summary in bytes:
// Proof Size summary in bytes: // Measured: `355`
// Measured: `674` // Estimated: `3820`
// Estimated: `1886 + d * (2567 ±0)` // Minimum execution time: 213_817_000 picoseconds.
// Minimum execution time: 13_294_008_000 picoseconds. Weight::from_parts(216_977_000, 0)
Weight::from_parts(13_307_595_000, 0) .saturating_add(Weight::from_parts(0, 3820))
.saturating_add(Weight::from_parts(0, 1886)) .saturating_add(RocksDbWeight::get().reads(10))
// Standard Error: 1_146_439 .saturating_add(RocksDbWeight::get().writes(7))
.saturating_add(Weight::from_parts(29_632_292, 0).saturating_mul(n.into())) }
// Standard Error: 1_146_441 /// Storage: `GhostSlowClaps::ReceivedClaps` (r:1 w:0)
.saturating_add(Weight::from_parts(14_654_685, 0).saturating_mul(d.into())) /// Proof: `GhostSlowClaps::ReceivedClaps` (`max_values`: None, `max_size`: None, mode: `Measured`)
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) /// Storage: `GhostSlowClaps::Authorities` (r:1 w:0)
.saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(d.into()))) /// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) /// Storage: `GhostSlowClaps::ApplausesForTransaction` (r:1 w:1)
.saturating_add(Weight::from_parts(0, 2567).saturating_mul(d.into())) /// Proof: `GhostSlowClaps::ApplausesForTransaction` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NullifyNeeded` (r:1 w:0)
/// Proof: `GhostNetworks::NullifyNeeded` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1)
/// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1)
/// Proof: `GhostNetworks::BridgedImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::AccumulatedCommission` (r:1 w:1)
/// Proof: `GhostNetworks::AccumulatedCommission` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn self_applause() -> Weight {
// Proof Size summary in bytes:
// Measured: `655`
// Estimated: `4120`
// Minimum execution time: 210_676_000 picoseconds.
Weight::from_parts(212_905_000, 0)
.saturating_add(Weight::from_parts(0, 4120))
.saturating_add(RocksDbWeight::get().reads(9))
.saturating_add(RocksDbWeight::get().writes(5))
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-sudo" name = "ghost-sudo"
version = "0.0.3" version = "0.0.2"
description = "Port of the sudo pallet because of dependencies issue" description = "Port of the sudo pallet because of dependencies issue"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_sudo` //! Autogenerated weights for `ghost_sudo`
//! //!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! DATE: 2025-07-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000` //! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz` //! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -28,7 +28,7 @@
// --chain=casper-dev // --chain=casper-dev
// --steps=50 // --steps=50
// --repeat=20 // --repeat=20
// --pallet=ghost_sudo // --pallet=ghost-sudo
// --extrinsic=* // --extrinsic=*
// --wasm-execution=compiled // --wasm-execution=compiled
// --heap-pages=4096 // --heap-pages=4096
@ -61,10 +61,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn set_key() -> Weight { fn set_key() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 38_841_000 picoseconds. // Minimum execution time: 40_014_000 picoseconds.
Weight::from_parts(39_982_000, 0) Weight::from_parts(40_856_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes(1))
@ -73,10 +73,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn sudo() -> Weight { fn sudo() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 42_412_000 picoseconds. // Minimum execution time: 44_086_000 picoseconds.
Weight::from_parts(43_446_000, 0) Weight::from_parts(45_920_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads(1))
} }
@ -84,10 +84,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn sudo_as() -> Weight { fn sudo_as() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 43_006_000 picoseconds. // Minimum execution time: 44_106_000 picoseconds.
Weight::from_parts(43_886_000, 0) Weight::from_parts(44_650_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads(1))
} }
@ -95,10 +95,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn remove_key() -> Weight { fn remove_key() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 35_731_000 picoseconds. // Minimum execution time: 36_416_000 picoseconds.
Weight::from_parts(36_443_000, 0) Weight::from_parts(37_373_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes(1))
@ -111,10 +111,10 @@ impl WeightInfo for () {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn set_key() -> Weight { fn set_key() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 38_841_000 picoseconds. // Minimum execution time: 40_014_000 picoseconds.
Weight::from_parts(39_982_000, 0) Weight::from_parts(40_856_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))
@ -123,10 +123,10 @@ impl WeightInfo for () {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn sudo() -> Weight { fn sudo() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 42_412_000 picoseconds. // Minimum execution time: 44_086_000 picoseconds.
Weight::from_parts(43_446_000, 0) Weight::from_parts(45_920_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
} }
@ -134,10 +134,10 @@ impl WeightInfo for () {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn sudo_as() -> Weight { fn sudo_as() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 43_006_000 picoseconds. // Minimum execution time: 44_106_000 picoseconds.
Weight::from_parts(43_886_000, 0) Weight::from_parts(44_650_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
} }
@ -145,10 +145,10 @@ impl WeightInfo for () {
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) /// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
fn remove_key() -> Weight { fn remove_key() -> Weight {
// Proof Size summary in bytes: // Proof Size summary in bytes:
// Measured: `98` // Measured: `165`
// Estimated: `1517` // Estimated: `1517`
// Minimum execution time: 35_731_000 picoseconds. // Minimum execution time: 36_416_000 picoseconds.
Weight::from_parts(36_443_000, 0) Weight::from_parts(37_373_000, 0)
.saturating_add(Weight::from_parts(0, 1517)) .saturating_add(Weight::from_parts(0, 1517))
.saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1)) .saturating_add(RocksDbWeight::get().writes(1))

View File

@ -1,7 +1,6 @@
[package] [package]
name = "ghost-traits" name = "ghost-traits"
version = "0.4.6" version = "0.3.23"
description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more."
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -9,24 +8,12 @@ homepage.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
num-traits = { workspace = true }
frost-core = { workspace = true }
rand_chacha = { workspace = true }
frame-support = { workspace = true } frame-support = { workspace = true }
sp-arithmetic = { workspace = true }
sp-runtime = { workspace = true } sp-runtime = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
[features] [features]
default = ["std"] default = ["std"]
std = [ std = [
"frame-support/std", "frame-support/std",
"num-traits/std",
"sp-arithmetic/std",
"rand_chacha/std",
"frost-core/std",
"sp-runtime/std", "sp-runtime/std",
"sp-core/std",
"sp-std/std",
] ]

View File

@ -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>;
}

View File

@ -1,3 +0,0 @@
pub trait StakingEvictor<AccountId> {
fn try_evict_validator(who: &AccountId) -> bool;
}

View File

@ -1,128 +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 signature_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>;
}
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>;
}
pub trait EvmGovernanceRegistrar<NetworkId, EvmAddress, GovernanceAction> {
fn register_evm_governance_action(
network_id: NetworkId,
target_address: EvmAddress,
governance_action: GovernanceAction
) -> frame_support::dispatch::DispatchResult;
}

View File

@ -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;
}

View File

@ -1,6 +1,3 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod bounded_bitmap;
pub mod exodus;
pub mod hashing;
pub mod networks; pub mod networks;

View File

@ -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: AsRef<[u8]>;
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>;
}

View File

@ -1,26 +1,10 @@
use frame_support::{pallet_prelude::*, storage::PrefixIterator}; use frame_support::{pallet_prelude::*, storage::PrefixIterator};
use sp_std::vec::Vec;
use sp_runtime::{ use sp_runtime::{
offchain::http::PendingRequest,
traits::{AtLeast32BitUnsigned, Member}, traits::{AtLeast32BitUnsigned, Member},
DispatchResult, DispatchResult,
}; };
pub trait NetworkDataBasicHandler { pub trait NetworkDataBasicHandler {
type NetworkCurve: Parameter
+ Member
+ Default
+ Copy
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
type NetworkType: Parameter
+ Member
+ Default
+ Copy
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
type NetworkId: Parameter type NetworkId: Parameter
+ Member + Member
+ AtLeast32BitUnsigned + AtLeast32BitUnsigned
@ -32,45 +16,28 @@ pub trait NetworkDataBasicHandler {
} }
pub trait NetworkDataInspectHandler<Network>: 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 get(n: &Self::NetworkId) -> Option<Network>;
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>; fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
fn iter_indexes() -> impl Iterator<Item = Self::NetworkId>; fn is_nullification_period() -> bool;
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> { pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {
fn register(chain_id: Self::NetworkId, network: Network) -> DispatchResult; fn register(chain_id: Self::NetworkId, network: Network) -> DispatchResult;
fn remove(chain_id: Self::NetworkId) -> DispatchResult; fn remove(chain_id: Self::NetworkId) -> DispatchResult;
fn register_incoming(chain_id: &Self::NetworkId, amount: Balance) -> Result<Balance, ()>; fn increase_gatekeeper_amount(
fn register_outgoing(chain_id: &Self::NetworkId, amount: Balance) -> Result<Balance, ()>; chain_id: &Self::NetworkId,
} amount: &Balance,
) -> Result<Balance, ()>;
fn decrease_gatekeeper_amount(
chain_id: &Self::NetworkId,
amount: &Balance,
) -> Result<Balance, ()>;
pub trait NetworkRpcResolver<W, B, H, I, S> { fn accumulate_outgoing_imbalance(amount: &Balance) -> Result<Balance, ()>;
fn get_block_request_body(&self, id: I) -> Vec<u8>; fn accumulate_incoming_imbalance(amount: &Balance) -> Result<Balance, ()>;
fn get_hash_request_body(
&self, fn accumulate_commission(commission: &Balance) -> Result<Balance, ()>;
id: I, fn nullify_commission();
block: B, fn trigger_nullification();
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)>;
} }

View File

@ -1,79 +0,0 @@
[package]
name = "ghost-weaver"
version = "0.0.8"
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",
]

View File

@ -1,389 +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/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
hex::decode("4635b1794a22bfbe6c5c5eba17b693f4aaf0e348")
.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, { crate::types::UTXO_MAX_HISTORY_DEPTH }>,
) -> Result<(), BenchmarkError> {
let session = 69;
let max_history_depth = a as usize;
let (network_id, _, _) = prepare_pallet::<T>(1);
// Real world tx at block #481838
// Block https://blockstream.info/block/0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
let historical_hashes = vec![
hex::decode("000000204a9037b3465b3c657997cddcffed602a949ee139cbdc1b010000000000000000669c3e2472384bc95993f154aada38d6b2113d8d32069e32e2724ba40915a1802a669e59e93c011801c8c735").unwrap(),
hex::decode("02000020926b3b1cfec537afaa88f002a45a5a5c3dc25c0664dae7000000000000000000af0f270bf3f868d47089f395197809c360f1506636c960d96afed68bbd72d0b4496d9e59e93c01183ac94b67").unwrap(),
hex::decode("0000002093b3d5716570bdd5afd75a1c7d87873bacc062936dda630000000000000000009ab437e7c66b10a59b67127f290d24f381aba4e03d5e16c062a356eea4d608dea56d9e59e93c0118264f310e").unwrap(),
hex::decode("0200002026309618b52c68271702f57fbe684326663374eb6360ec00000000000000000023c441f913d1c77fb635544f9061fa761f876ce7d93ccbef5dc7f88e84891cb1aa6e9e59e93c01185bce3c9b").unwrap(),
hex::decode("00000020a3776fc8ad2be759cd4bd3d60eaf8d8225c869864490520000000000000000006a8e7d27c71b9886b090a7422e7b5c12bb2c88255af0273d2724b69bb8c1a817b3709e59e93c01186a257ab3").unwrap(),
hex::decode("1200002076d65fb74b1285c9b4d809645f6bb6f8f076a36295d9d400000000000000000062c8a7f4f7aa43726f753304ac9bc071877ef81273fa5f9e2f8e6f2df92f1211eb739e59e93c0118b2f12aee").unwrap(),
hex::decode("02000020762d95fbba0466c983446595dd608487ffafaa71e123d9000000000000000000e639b7f88ac7d1187bf9e8c82e8e504eabb2af5f0f05beaaefa09bff7d7314d6a07a9e59e93c0118be840af6").unwrap(),
hex::decode("020000203e5a55d70de3f462efd40e8780adee7dfdb451b57eda230100000000000000003efb1342b5fe12376a09f89a1dbb775e5855f9a1accc41b40929ec911ec1efd6f07f9e59e93c011829615752").unwrap(),
hex::decode("020000206cac3182c85d630fe6938284a88177b1265f8875738b0a0000000000000000006b9d69ba608cc06e8d519373c2d0a78a62b0bcffd150e37f79f31e97a8af1af4568c9e59e93c01185f5c7c32").unwrap(),
hex::decode("02000020e2f550d7404324de1bd8c83a79a1c0133635a34269120700000000000000000057819057a8a85c8f1cafb13e5eed7d6739e13d855d63421c9cef5bf1a98101a33b939e59e93c0118ee220467").unwrap(),
hex::decode("12000020ce9c0848d500c2fea3efd38f4e2822d8962555a1d68b2f000000000000000000e38b12110e6b8978155dad2724cf0ce4d8ab827712f8e750ce7be253227a10bdc4969e59e93c0118dc3403b6").unwrap(),
hex::decode("020000202431a9c869c26ce0e76b1ed6c6543be9b7fff0ace4735d00000000000000000073811d01111496203c08c0e5941fee2c7791e1b7cfdc852258864c873df8fa6b6c9e9e59e93c01187a4ed56c").unwrap(),
hex::decode("12000020808101492d7f3237125b79288a39cf6f71bc279e45b5c9000000000000000000d7d57e05a60bae464dced00c83eff37b447fcb07e95fadce2f610267146c622fa2a39e59e93c0118919a5f2a").unwrap(),
hex::decode("020000207272193a5971c9c5b8440ade64d14a0de46baa43c3faa3000000000000000000e7b1dbce250ea8e3e44e601fb26cb814e6e6e7b3b3e87e6239f6a745e3c9194780a79e59e93c011849f1593d").unwrap(),
hex::decode("020000202e6cfa8b6c2ee74f0648137131bf782c6756a7f8462f4100000000000000000056fb99126c1a78c143d86e89787de7bfc08f5492bd26df1c4314280a34e64be797a79e59e93c0118ae471ff4").unwrap(),
hex::decode("00000020be74317e078482f6a5a3723044aa62cb858b82dffd1be300000000000000000051111cc62fdcd7de11a577853ee3e4832394963da62e5bae92173ae2f366e4dabba89e59e93c01185adb0d11").unwrap(),
hex::decode("000000209cd483b9091395be33ed7712c0e8c3695b35ed8f62e71b01000000000000000046df5dea1198544e5bbbbe9db155f97f79716cd811f8d208c06be7603b70c742a4ac9e59e93c01180c032031").unwrap(),
hex::decode("000000207936ae0f3ffbf1673598053377e6283e3a27d9fb023e440000000000000000002ee47a3ec50659fbfb117eb7c21ed26b493908bb9f8d172ed19cea85e402e66b57b59e59e93c01188178d5bf").unwrap(),
hex::decode("0000002094fb4f7b930b3c540db44b2dfc909ef5baccba57b435ee0000000000000000006dd2cc7164d9e6d2d0400609cf5d76531391ca93f879bbbcfafbf560b6b031e8cbb79e59e93c0118374c86df").unwrap(),
hex::decode("00000020e4085486f70e220273bff0fa6fc767eb74b69d2c978d26000000000000000000c991b50232e52220d94177b6ee0b606c52c010037070fc101945e160ca24786c38b99e59e93c0118c1d0e6a4").unwrap(),
hex::decode("020000200a5d7c8f4134ebd1e3aa19ad554dd8fd3281c94f8fda1b000000000000000000a95d611bdd5d90d2710a62915ebb1592d1bf224b104e3b8f5b289fe873d021eaf1bd9e59e93c0118d5e7ef59").unwrap(),
hex::decode("00000020d7cafeddd0815ba95c6e90afc16970e0514729c0a7cd600000000000000000001b3886b882cfaca6d59367c1406f9b7556fc22c190b11ff414b8b2f0269c968aeec19e59e93c0118434c5f61").unwrap(),
hex::decode("020000204384ba6db53aa87a604259c9f9c6844b99ced37e29db0b0100000000000000000c24d07c38afc18bd830aebc2476564df8fa64dfcc8d44dd68ed7df3fc5d206795c49e59e93c0118b415f46f").unwrap(),
hex::decode("000000209986db62bd16a5de375470344aeec6d17ea322972d75bb000000000000000000d5c8ccb89f92e3b16e3a230d941d7ccc946b91fddae922c08c3245823210f47f43c89e59e93c011843c47fea").unwrap(),
hex::decode("000000201e8afdebfade79e0a483e56f2fb87a277a513769add0410000000000000000003b8b9fc7fb33f2b8c8520dcf76eb751017c06d0ed4bac49c2ebdd036abd9ee76a4c99e59e93c0118b7b6dbcd").unwrap(),
hex::decode("000000200a5fa5f3d854fecd0c8fea9a71879443347058ddec1c0f0000000000000000009d3249cb64dda39bf0ed9ab4b665780f6c6aa72d35460bd972f1212a32e84c75a5c99e59e93c0118ae216161").unwrap(),
hex::decode("020000201cdefc545aa71150e97a9763785dbc2855710f33ba3f1e010000000000000000e072dd1b8043b5dd632d4a91769cb91ca55c0343dc3d8cbbbf838c6efb1940d229d09e59e93c011853549672").unwrap(),
hex::decode("00000020375d4cbf7b4d0550803dcf160ebd264bcb594d3c82c56700000000000000000058dee1ace217c483b05544f9e5d77da2857b5b443380d22761ff14869fd8ea473dd39e59e93c01187e31b1b4").unwrap(),
hex::decode("02000020560be37ca005f2c792755368aaf63b5a664ea36c2c69be00000000000000000026cda0eeaf84644b2aa9d072e5b5bf14c8c8b707a671e4b2232c7e569fc2a07763d69e59e93c0118e2b558a6").unwrap(),
hex::decode("02000020546da8a3fde6bd1f52247bc8a6cd0251e772eae3b5556600000000000000000029b4e20ecbc778975e189bd28631e94b2809422d8efef15b7e440f624e79d9964fda9e59e93c01184f91c402").unwrap(),
hex::decode("020000200bee1075fc01582e0b453bfacfe49add055d6cbccea7a500000000000000000020e6280b3d966f5af7290a164087a6c8cb177b41ec7be91035d0f5c45f1434f129dc9e59e93c0118cb35d326").unwrap(),
hex::decode("00000020f07f65f1ed56e656a942eb424dbc76d78cde25acf1e7f30000000000000000006c6419dc5a46235b11f9a14f472ec166f7ee40f4ba8f031294c9ec8a20471bd6ffdd9e59e93c0118f55c1c76").unwrap(),
hex::decode("020000201c30d0ecfff1a55c6531ce992467b0a02d25d6a545120e0100000000000000004349f28fae9da9ced1744f9988d6c7ffffe10892e2ee2a0a3228f2555c13639461e19e59e93c011802b33340").unwrap(),
hex::decode("02000020f47f05efbfddee4a1d0a0c502f8a9604ef2236e60b02c8000000000000000000ad0f318ca8bb97a73a3490ac79c8cd1922ec53084493a5d2dd7c6bbe64889b1766e29e59e93c0118184be748").unwrap(),
hex::decode("02000020a9446ad105a3a4434273e72d9d47f23544bd2d4223d4fc00000000000000000081078f171421ea27808a8bf0e74d9679d9e756197a832d17e44cad3a67e5b8104be79e59e93c011822774361").unwrap(),
hex::decode("0200002060e290b87ee42f2d9497b8d588a144c8ff3013d4036ee4000000000000000000cf51f2b3e82a3da5adbf8baa8da3ce0dde7d9bc0f68ea7d241bfe7ddb6a6a5302be89e59e93c011841dd5d0e").unwrap(),
hex::decode("0000002037e7cd68d910094e9963ae1953173141bd1dd22756bf1801000000000000000098416fb809d13de6c54ccce4927b167e9d561db613df3cd05fd2345daa1ceee5b5e99e59e93c0118a9f6e6fd").unwrap(),
hex::decode("00000020cd116024d228583ad1f60c649452ff34224cba9943288f000000000000000000b9c1df740343350581e7b896f3e8edb4ed5a04d3f6fbccff4f69fa272916b110ffea9e59e93c011875c11682").unwrap(),
hex::decode("0000002050599ca8da62a8cfd3a07144f555651df0036933cf72c0000000000000000000485624901693271d8c6e343e603c2ce8e90b471cca311f023ba85e010a0e21913cec9e59e93c0118945a3cda").unwrap(),
hex::decode("020000205ca35aa16b669d252de566ff111a2800250bfdfae5e72300000000000000000099a99f1cb9f487794d70dd2dabdb63542aad056ff20cf5e435d1a350a5dd8f7435f09e59e93c01182d21bc70").unwrap(),
hex::decode("02000020751510083e009cc429c10ce2a1f7ce1f95c87f4716275c000000000000000000d1c1b3b2a58a4d7780fd7855cb168a3187fbb48766a77e2237e61a987f7a04a0a4f49e59e93c01188c23e65e").unwrap(),
hex::decode("02000020ca376c104bded3b6f2ecd502c4626b4e4ba77ede0f2817010000000000000000007ed4eebdd8353dbaf9d2038858dd5d075efbc8d88ecf8390c9bd4629561739b6f49e59e93c0118680992f1").unwrap(),
hex::decode("0000002054ddd7cc1845abc2f45fb6fdd477fc060c621cfc3f64a300000000000000000027b24757e89286c84b438fdd7221d5eeec72dc3214f96bfcbb034480b8d17dfcf2f79e59e93c01189e1f97f6").unwrap(),
hex::decode("00000020f49dc697e664b8b0c2029098b02ffef095d48c1e477145000000000000000000c644ff082cb1fa4ecce54bed1b73568f1d822871004222d5a72f3cc498e2f44763f99e59e93c0118f9f8e680").unwrap(),
hex::decode("000000206549bb716f4d1f739f6ec0eec4e7bd579d13a08f32ec910000000000000000004d9a9c3c118ada3bc0bf92527bcf58b9dd9a2ebfcd32e863ddf55bf4b8ea51acd3fa9e59e93c0118dd521ee6").unwrap(),
hex::decode("000000209a368fcdfa9401ade089f9caffa5f4247b8d98df587907000000000000000000b4f37f6ec854a54768e754813ffb7e8a28cbf1e96c83d591b991d3f18e314c2b35fc9e59e93c01180d8068c8").unwrap(),
hex::decode("02000020452cab805c77683d7311ca8678c7597791da79fe540a090100000000000000006445c736195151df49c936a6f0d981b6a13cfe2786da44e49972753dc90af2e452fd9e59e93c0118f0572502").unwrap(),
hex::decode("020000200d95d828b4772e62a3fda7c5105a5e62ae59bfa41b897c000000000000000000762cef2e97c31814210393e4acb278c155fe040b711635c6e91ee500e53754431f049f59e93c0118969361d6").unwrap(),
hex::decode("00000020649b9a19c0145467f3a1a58e1bcc05a65ec1056ef93688000000000000000000dd4ccf726384ebdc3ccc54cb2fb333e1d53929c8ab31d53e67b53cd77d61454894079f59e93c0118bed9774f").unwrap(),
hex::decode("020000206f3f9474e424ca652456920f944c7b17b3d240ad37aa3b0000000000000000009a92e0f32aeb11ff2edd48fb838cbb0a3e25c77110c903821f339f7af5b241fb540a9f59e93c0118b5e8548c").unwrap(),
hex::decode("000000200aa99abfd7da0f5f5448aa667e6de3e077c883614be8b200000000000000000075761a440b33470907d040f1a155297ca051031a51f8fa86354759b07bd1f664a10a9f59e93c01184b5fa896").unwrap(),
hex::decode("020000206bd57fe048b0bfc457f7d5d2391e01726fe292ab1fcb18010000000000000000172bba412df81c10668c127231b0aa6d0bb956e5e8fc0e8d049bdf2ad5ae7fb7ad0a9f59e93c0118503dab85").unwrap(),
hex::decode("0000002001930a8eaedf739fcee4ff0b813f3c30e9893adc7c2f750000000000000000008c070fd6e6fb402464b941679aa9d4729039649e6d0291503f707b0e18f15e43050d9f59e93c011853eff7b1").unwrap(),
hex::decode("0000002032065359c9d1ec45b0e44777179e815c5dea5df6d2b80d000000000000000000e0df9831a1340b0f43e9c1cb184b3c908088c22b3f7c0e83753899fadd7b19a4160f9f59e93c0118ae79fdbb").unwrap(),
hex::decode("020000208790994382f84e67407b33e82f0a8e268d5728cdf51113000000000000000000012a6be8a94ed2b80d931826d1c3e9041b0db02d526bdb01f29cdafe867e8e1952169f59e93c0118baa84dfa").unwrap(),
hex::decode("000000206ee4581dd794f9a4fdfe832fe065309357d674b8128834010000000000000000888e623880da62cf81367c8b77e3e2ce52747b2371e82f4abd0f1ba8e41f84fc78189f59e93c011811f10c19").unwrap(),
hex::decode("00000020077535a944b5339304140abe45ae8074fc2608647123210100000000000000005ed755f1b7abccc300cfa05d5912b2735fd5af94005c483363750315352d91a0971b9f59e93c01186f9d5e9f").unwrap(),
hex::decode("000000202c66b86db7a058c3c44539ddeffb4f1b57015f9b2b8fd5000000000000000000e9b0a033bc22b16c9947bb1cf8666c8b7e31ab98572a12df526c7941eab4325a4f209f59e93c0118d5062214").unwrap(),
hex::decode("02000020f22aab764863f3f9e88a769ac022585b6ffe1bbad8ee510000000000000000000ee1ae23c89a4e6a19d737f0bf177d3a208bd00e1d0ad4cae38227e86ddfb36250229f59e93c01182447fb2c").unwrap(),
hex::decode("02000020d1570bf5d1ac720a7452201a3287325bc5dae8524739b30000000000000000007a05c7cd9b70624c6a5d60bdd06839aba4fcc8e7252edd243b65c14243074437ce239f59e93c0118f63384cf").unwrap(),
hex::decode("02000020aca96f50dac6c74043cc30b2ab63a11af71348913c99070000000000000000006dda0c090803e1207eb4d5151bfc572fec8c5869014dcd528255280fc3c6be0ff02b9f59e93c0118aaeafdd9").unwrap(),
hex::decode("0000002062b7c2b4bfdac0e47f8a510e05e56ec3a2aa159a198b210100000000000000005d1c0f705fb6dda30d8787b483ec14391dca7dba3bc927b5c5d2d6ce5aa605f8e42c9f59e93c01182b168198").unwrap(),
hex::decode("02000020ed0a2c738b93a2020850d1e6e696c409c7e1f386e36f5d000000000000000000ad0ab3a5dc8fa3680ec23d44ad6ae25c34ff4b8880c50ec5930ac8e2e539a59247319f59e93c0118a250018b").unwrap(),
hex::decode("020000209ddcae121db975e6dd5e81c170baf5b3c282ab20394bde00000000000000000080f2facb6a18fafe52015165da8389eeaf7c3ecefb7407bc64746b4848d237a096319f59e93c0118832b1484").unwrap(),
hex::decode("120000209eb0ab39e2c28b15ea5af8d29384b0cd75e0fd8ca333800000000000000000009e664c6c013e556a0e533ea58cdb611fd18f004780f2d8fa9e960f531077723ed3369f59e93c01183ed29cd1").unwrap(),
hex::decode("0000002004b9908261d98fc854b3a60e7c6550be0e80f1faca2e9200000000000000000051ab7c2206c0af2fd0109857e2813667b20c60b44eba0429d12d2bf445b62dd18b399f59e93c011866f2e5ec").unwrap(),
hex::decode("000000208aa7de3780cc6675d5dba2bf9cc9a282affe39a6f7150500000000000000000097f2a23d7efd9dfe84826085d7c60aa3af1838853b39e0de3eb2479070f86ad8b73e9f59e93c01189c2b7650").unwrap(),
hex::decode("020000200446629b6bc0dcacc173df9cb47f001784913e61aff4db0000000000000000003118dcc3cbfd4f0d70955243b0f82945303bc0c1065d81dcb804cc960b2e229406409f59e93c011882e333c3").unwrap(),
hex::decode("020000209dd0c92839267422e8a4c18abc1d098219bdc9cbcee6c500000000000000000020449bc225a5eb4fa2599bb828ad08315ff6070cdae1fcd54f4c12af5a738e78e4429f59e93c0118a43c58f5").unwrap(),
hex::decode("00000020a8ce692a8a4feb0aa11b111173578f62fe62a1f9b7d1250000000000000000008da525d2694931ef00938ad1adf80dc1d98961da742899a3e8e9a9f9d52d139d5d479f59e93c0118299a5e44").unwrap(),
hex::decode("020000209d0fd9053369245ce38bb78a5615f28c35e11a5973c5f700000000000000000063b3af6eb819369034653030bde01fdb932c387de6df52de2706247d85e459cc114d9f59e93c01184a0be1da").unwrap(),
hex::decode("020000202edbe7bc738f7738539edbe8ef297845d6dd3172929008010000000000000000c168d44f58778e26a32f2cae4ad24438ef9a224247560597f90807150f12ce2c954d9f59e93c011837f3cd54").unwrap(),
hex::decode("0000002015fa542454cfb0ae97e23ad4b4ce5703b3108716c01357000000000000000000a806a100d3b35f96c39ca66e22a1beda6f3f6fe713d42dc2e6ffeb28dd8c28e9565f9f59e93c011806b57820").unwrap(),
hex::decode("020000205ec54cb5b7b605cfc74c84f8ba30b366967477158e7eaa00000000000000000003bba352d0ff1f1da8d6236d56537f04f010c244241cfada8413d411bb05f2acbf5f9f59e93c0118c1ddb052").unwrap(),
hex::decode("0200002099e2bf03160945de2c32994587f206fc03a8729873f71b0100000000000000000a41ec1170bd9ae021715e237a8bceb081bbf7fe54c710bc53d5e847cf0e42f83e659f59e93c0118d5368f54").unwrap(),
hex::decode("02000020f7039f439dc89d835d02760938b27f38aa23ca450edfe7000000000000000000d53db2eefe53fe8f7b0bbfda2f6b0fdcdf05b3db319c6d0f8b1c6de724cdfad4bd669f59e93c011886b1e482").unwrap(),
hex::decode("02000020849f13fa4db5a9df69b6c7432787aa46ab907a73eb8958000000000000000000cce6f694fb27b616b555dc7102ec152bde972c81e2872a3e563ef42012aff052cb6b9f59e93c0118e0c9a5a7").unwrap(),
hex::decode("02000020a2483433891049526ac33b6ea00686a35559e02754612e01000000000000000089f3b6be3169c4a876aca2ffe4356c3d0360318b0944361b50877c4c18684d0a466e9f59e93c0118d708e05a").unwrap(),
hex::decode("0200002005b1a1dde1c62d56a012b4f03d8b728a2a94f673e32654000000000000000000d02cfa1f6e7718bcc995a9ae1058dd104b46f5f1602fa04342976a33b2d9de4fed6e9f59e93c0118565c6ad1").unwrap(),
hex::decode("0200002058a8c2c6e79a29d7cae4638b45250afcba2c78931a1a4e000000000000000000047a409d56ecb504f03f625f02debe5e4f8e4afb2e7db585da0e6267e5a9c1241b749f59e93c01183cc51dda").unwrap(),
hex::decode("020000208ea76979f75c77af7770c510ba0b29d10929a78a67a1ab0000000000000000000025f4241f566b426d0482decd6e50433e599e876a5eb19e077198490341c633d1749f59e93c0118eea47b4e").unwrap(),
hex::decode("0200002017cbe57c7bdd58d8dd4a330e75134f505d23337545a0c3000000000000000000a0c7805564032c4837f6aadf9ca1f7c0c05e43a15b5c85b24113edf5463a3c2b63759f59e93c01180d829750").unwrap(),
hex::decode("000000201daf8e737418b092385d91052dc9bf25c78db4947eb55b00000000000000000060a707c9f3152e692ded4eac22e1b94b5ad2ec5054146f252656824385e648a8b8759f59e93c01182341df8c").unwrap(),
hex::decode("0000002096c2dcf962dbfa7ad045b4b8b8b7a4886cad2379ebaea6000000000000000000af2bd41304d6282d189dd7dfa2a9b4e4f9d6dbec56fbec8ca89a79c04f8120a3b1779f59e93c01181ab38dd1").unwrap(),
hex::decode("0200002030656d4c0fd65650a9816435d319a28b65b7565ef68e1100000000000000000026b47d3d67ea2ac89e74a31b9db03d4d3055efa98bb8368e12699a10406e49e4437b9f59e93c01185a356dc0").unwrap(),
hex::decode("02000020d595c94c828b7f0d7833781a9c7c7c698966f251ee3420000000000000000000b182443ec96ca031c8e2fbb6492a1a51ae6cc80e886122d29d01b69269e91ca03a7c9f59e93c0118bfd8da6e").unwrap(),
hex::decode("0200002018ea7a48e4f65e887ae7f5f1fc1395d25c5e454432995c0000000000000000008533a46a3c5b3e2ade0cf423d12d400b62586f8b8bea62e6a075d6b8c976ff6e8f7e9f59e93c01180f245714").unwrap(),
hex::decode("00000020bfdc2e0d03ac8d66bb2a18da3b9d03574e538c5e837a1d010000000000000000ac88acc66f208e84153c725753bdae77fc6d19ef0a640b6d1caf12cf8b851aeabf7f9f59e93c011805fc019a").unwrap(),
hex::decode("00000020831e7f6ff11c94025c12c7ee0246309c60243cfa79c6340100000000000000002a4d15a07780dd0685e4f517ee3fe822c19c1f88c97fbef7d4cc9125156244de95809f59e93c0118032ce04b").unwrap(),
hex::decode("0000002010fa7fe6e27ce9ad5f51bf00d5078a9e577f2d08115c1f00000000000000000062d112144604b9f9b83e4f421b234fdb17d0d7bd71590b2a1994ac73f0d9fb2759879f59e93c011870e590f2").unwrap(),
hex::decode("0000002065fee6168588d22fbe99ac61f5767f7d1f38408d4a15390000000000000000002ae612abb95ed62f051af05b0e942fd1763ef6f3011662c71d4f4d9c984c6da1dd899f59e93c01181231b844").unwrap(),
hex::decode("0200002024bf07643212552bd0d4fa4779762994eda338868ba20b010000000000000000700b14840728bb630ee467a705dacba32ef040e39245d4e18e0381a1fb42e5b2e2899f59e93c0118687dc2a8").unwrap(),
hex::decode("02000020fd21bdf774f4ab391ee951404c7c3d9df1f20a21fbd6be000000000000000000e948d27a0e531f5cbab3a5c5cb3f33cddf8228215ca3d934336f26cdecc8a587aa979f59e93c011835ccc946").unwrap(),
hex::decode("02000020153d617dd664fa4f49aa0ef145227331b4aef23a9beed0000000000000000000283f8529ed8419bbd45076bf1ef58a2f232c4cd869aa5e9a8687a932ea6b8cab2a9d9f59e93c011884f44a28").unwrap(),
hex::decode("0200002094a5ac9376865366f4494ff6782e9e0558eb485671451101000000000000000031dbb81b18060f9afa4a82a55391911102a01122b6e25bbad2227d036aa402cf69a19f59e93c0118a38dcf53").unwrap(),
hex::decode("1200002020311f7c3ea7fabfb3da39c120d7256421b8ba1f7c9d2700000000000000000043b494d744397958f9fcbdbc78e2fa238a65ce4a8c99c8cae41094bf5f620274dcab9f59e93c01187257ce43").unwrap(),
hex::decode("02000020be1808de4c0bc3a8cca96651411de3bcb04b03ca9cd5ad00000000000000000061248b66ec93f45b75d1fe61bc62994b769ac8fc6a5140c2e1fcd8ed3ad8734bf9ab9f59e93c0118c46a224d").unwrap(),
hex::decode("020000206de1e86d50d4043ea7b06394598eb0d39316de61ebdb1d010000000000000000cbad54eff729d2e964a3b97e754a01da55cdbc8924788f415ad0e5e04f030bfb6baf9f59e93c0118bc7d5352").unwrap(),
hex::decode("12000020971fb5337db4cfcdabe3d0a19414e22f200d0dcfa60ae9000000000000000000f1e07bba0d854c76d60dfbdab247d68ebfc80ba5e95d4d83997178653fdbdcc5d1b09f59e93c01180b0f8fab").unwrap(),
hex::decode("00000020b915dca0172c00dff21d436900f81fa55c4d0c94789708000000000000000000c0d80eedeab0a2e5ac800e4e0800b9a67a51d8095ff013977c56a5ee6a82564b91b19f59e93c0118626d6930").unwrap(),
hex::decode("0200002038b29a8148601f802389e59448bd915254374cd89e3e07000000000000000000000f9be6ad775720838d044faabaa507c1d27374710fe36b66e7e855f275603c11b59f59e93c01186830dc10").unwrap(),
hex::decode("00000020f66bbe99a28e70f00ed13fae994a12950025129ee29dbb0000000000000000006e2e590253d6d4de2cb44f95292681e8a8dfe425ba3a231aefb0e239241d338747b99f59e93c01186621bcd3").unwrap(),
hex::decode("020000200b449c16f78cc9f3f56e7eb84c8c535b29230866ccc410010000000000000000b201b9bc39851f34c3d68a80453de339560947fa76f0d3d28aaa0fe6f3c09f0870bc9f59e93c0118a6f6bf4b").unwrap(),
hex::decode("020000209073c42915b4ec2f93cb625299befbbe115867f9ffd6a400000000000000000065e326a16c150c95b145deff23e31b829ec224e16d62f426037fa0a9f515d96c11bf9f59e93c01189c3db0ca").unwrap(),
hex::decode("0200002000a52727e49fa397f8f211204c5a0bc40497b9b142c311000000000000000000ac88a88861b6c454b4dfc4bb8e4cd2fb475cacbb003c80bb4dcc44d89e324ef4ddbf9f59e93c01184ca16dab").unwrap(),
hex::decode("00000020643522aca18b6dd72993667995c1a0ed0db40a8ccfb014010000000000000000536ef1c8b3b657824e7dcf7650cb37bf8916773d0961ea46039880e81150dce130c19f59e93c01189304ead8").unwrap(),
hex::decode("000000209f39c386609cf5fcde82901e09e3c21614d6c91bf1bf500000000000000000006a2196cfa5da0d2bfcddbafb8ef33c3535e2c6abfbbf0b9be5668a7d382d182b95c79f59e93c0118d7c51033").unwrap(),
hex::decode("020000204171fce850d6b9972bc84b5c129097700fcc940801daed000000000000000000c802782b023172b77fd0d67868e2c6fff4691b9081385c0b1fa8ee9b0edb9488afc89f59e93c0118dc47db2e").unwrap(),
hex::decode("000000208229fd4875d341096fe972b42ec09c37ba5b3f5761307a000000000000000000f6d6b2238c3510a57ea4c85efa9ce8131a2e0e18b5db6ccb3ba62cfdb360d79a7ecd9f59e93c011878faaac7").unwrap(),
hex::decode("0200002016209868f23cee45d08d78e0a0a407cd2f44163e44cb3901000000000000000093d2238bcdc26dc75023b9c5118928bad163e39c2f71df8e95d6edd751ba79e648cf9f59e93c01188440a013").unwrap(),
hex::decode("000000209207740837717a41d191055edaca417441ba8a518a008d000000000000000000ca7617ee77e441b43320206c73b0ab694e1a6e5cae68de543906bc78884ec96f54d19f59e93c0118ea3cdbf7").unwrap(),
hex::decode("0200002095de633721183adca50ee64eb432278067f271ed5c6d110000000000000000008d58a1f0310aaa879af6d1567042c56316d0944a3745a48477eee8c0a9b160e0ded59f59e93c011840274171").unwrap(),
hex::decode("00000020eac6f48951848edb142e7d2cb65e5325e168eff217a9b100000000000000000018a5f3609c392597179460b0eb4f552150ea919674c07110541b3b937d61895bbcd69f59e93c0118990775cc").unwrap(),
hex::decode("00000020cd852383fdc3580f03d70427842597998ff05d642c301700000000000000000016d1f41abbeb84cf1260a156bb107f1831e1d4043087a62b91a802b6504ae82100de9f59e93c0118a2caa3e0").unwrap(),
hex::decode("02000020ba3564570e67b5e9258a0741a9b0a8c7eb21fc531e47340000000000000000008d51759065e5b563aa22534da2fe013cb78081b109a430f5224f2e3de395c31423de9f59e93c0118f387b5b2").unwrap(),
hex::decode("000000205dd7d70d5b3b8654915a80a502636d762de4349b7ea35e0000000000000000005fad44d833d5963038adccc421a1f027e3ecdd321d94ce2981958a92d543a9823fde9f59e93c011809f75b68").unwrap(),
hex::decode("02000020f6a223280df450a99052b9644fdbbb17c873cc650079c2000000000000000000ce4ecb69781542236a03b3f1ac1bcf33ff0c5392ea1e10810092c71607ed9b5cadec9f59e93c011831acafd8").unwrap(),
hex::decode("00000020e572b677fdbc2c58ccfd064be23380d1a48880e2f57630010000000000000000de94574c2ba3c13464037089196614b4c7d3cb4d5af1fd8c42b7dc48099ddb29ecef9f59e93c0118718ceb94").unwrap(),
hex::decode("020000202b8b12967db07d4ebb88aa85ef66116c191a93e99b27120100000000000000003fd47c683e1e17d680a9110af97f0ca4c645760d1a2b9934dce5f2e88be5093c7af29f59e93c01185714a38b").unwrap(),
hex::decode("000000207880fe8eb9ce3c0868742ae98e71b18e23bc94f62ab02a010000000000000000817fb1fa49e3aff325c1b0e445629e06d31699b565daebf741b1b731d1e3db4a8cfc9f59e93c0118fc165574").unwrap(),
hex::decode("00000020eae605b0e27d01a4a967f8b2788222bdf7d06c3f2cffb70000000000000000004647da086d8d604990e4e95a832430372fa388532599f5f2dc2c4248d6c62019c1fc9f59e93c0118346c62f4").unwrap(),
hex::decode("00000020fb5de90e254a00c4f2e88b478d927e97f8cc7bcde99de7000000000000000000d31910146254c1034ee847eeb991d7a758be6b475bcfa2039ed8b8746160c6365bff9f59e93c01184568f141").unwrap(),
hex::decode("0000002001d8d41aac3bc8be7ec45a469db006bfd484e174c86d1400000000000000000080707a5e00bf7ee43650b1428d3fb7aa3ae10edce68fc1c44c0d50042251eee8c102a059e93c0118c28a0a25").unwrap(),
hex::decode("00000020f4d340137354701e5acbaed498ebd833cb0bf6d4d4c0a400000000000000000058972fc8cad5065ec473d657070631c769714d5245f2a1c60819c0d58f2110d67306a059e93c01188ad1f723").unwrap(),
hex::decode("000000201a0c546ed7a8560f402810f7db7e9ed31bb9ee33cca548000000000000000000fbc835eee64458ccf98e4e9b2f464b74c4a8f19ca62d88e91d370a5140ca849f8609a059e93c0118f85d071c").unwrap(),
hex::decode("000000202ed3ce68191cf5f2a141e1beda144282aa3cb91c86bb3a0100000000000000006baff826721270cbb61b6e87a200878944de3bc288b89c1ad9f4660a90b0de592d0da059e93c01180913db03").unwrap(),
hex::decode("0000002037fefbaa4a417acddcff01b67e375aa274a5159b00cd250000000000000000000ad0731c0295fa7ad74b78ab37dda1760d98feb225c38932ebed3b3ab86010219d15a059e93c01180029355d").unwrap(),
hex::decode("020000208d3517e8c5ac9576de6376dbe18bbc3f68b714bd90ff06000000000000000000f09393c9f8cdb4fa5e45a556d7aa3ee3abc08ff175fab17c321144c1540da3aa9316a059e93c0118394eb993").unwrap(),
hex::decode("0000002093b6ad64d95790be03f40782e42dd217d91a062514b482000000000000000000b4ce3863a62be400d10c4b06f31f8d805c52df6bbcc9d672fbf1ed110394f015f916a059e93c01188f006b8b").unwrap(),
hex::decode("000000206af6d3062235cfbc8dbac1f5f33b790b48eb025e945677000000000000000000e0b4b06f31170c9cc54e623247a18d3dcb5b37a97799abadf5be22cac18ab650a818a059e93c01182a2eadc0").unwrap(),
hex::decode("00000020e44d7e3e4e7ed258fd9dabb368bd20f42a5134ac63a9eb00000000000000000047da7bf618553524026b5f6054372b9fc26e3f561345290b4b2775f65214d76a7d22a059e93c011889243aa1").unwrap(),
hex::decode("000000200428d486033251c65fc8921aeda5d627b4974b2ed8710a01000000000000000026049ae797b8658ddff66af57c00fff50d83d123ecf2d70c5f7b6f40537aabbe9526a059e93c0118568368f3").unwrap(),
hex::decode("000000204025364ce1f01cdfaf82e8c16665cb47a1b8156ce2c5be000000000000000000c4a56c66a75c261587536605fd4442dcfc190a4ddf5a13d6a063110e140bc5177e2ea059e93c0118fe232164").unwrap(),
hex::decode("0000002072dd4401dd1ee65956efb1ef3e3c468611d2c4cbb9dfae000000000000000000f6d476f9b69b756b87fb6596d16409bc5d40803489c5d52ffbbb03d12d594a174b30a059e93c0118ad048658").unwrap(),
hex::decode("020000200f2e93d616b03ffa6d2377bb6c8a5eb31385fdb30f5ac7000000000000000000f6187a2f8e73a661cc90ed86546b62f45ab68a174e56d08d2d93ae12eda9b181ed32a059e93c01184d5d246d").unwrap(),
hex::decode("020000207f3d929b96f2121056e0cad6a3363316d648a56a3baa2d0000000000000000001ba48a3c40fd114080ca67441bbf6c1b787b7caf4e3f19a124627493f0e3334e6234a059e93c0118b34d5e41").unwrap(),
hex::decode("0000002091c6841e0adf8d4bea707e3ab1e412b45bbfc80068b37a000000000000000000c273a936450ebd6f0ec9f16b04a955ccadd42fa446fb4ba0ac8cbd2874af988a2838a059e93c011889d44f30").unwrap(),
hex::decode("000000208a4af4109c84e53ca1ca671dc1ba75a55a2886f8eafd55000000000000000000e5b46a9cb2b8a975ab7c742fa57d942f5e52d0b92eff25954e916718f0eaaee1343aa059e93c0118e9904b69").unwrap(),
hex::decode("020000202d6cf055320a0e2781b77d1c0525e2897cd01cb03c9118010000000000000000fc8093d44e1279783a4d478a53bddcb55b58c4d06d51ea4bd8aeda28b66b2e7b423aa059e93c0118bc883a1e").unwrap(),
hex::decode("020000208d5c1f0e78fc6b87fba844848dd41a37bedebe409ebb0f0100000000000000008b34b89a7e9b732d940cb77f642cf9c9eabff198f369aa94220ebcdf07d3e138ab3aa059e93c0118e0078029").unwrap(),
hex::decode("02000020c639651d0a5acf604c4feac92e0879c16154ca9cad8c940000000000000000001802b2fb1bf4dd20a96a68aa416cd86325201b813ac9b3081d025e06a8632a2cfc3ca059e93c011864300336").unwrap(),
hex::decode("000000202ed62872636d3ae7803d9515f3a4360c0f096b08d9f8380100000000000000008476fe527a397800280d359dad7720ef220f2d749665fa8f3a5767a85d81a39f273fa059e93c0118ef87a752").unwrap(),
hex::decode("000000206bf7dfa1d97a1bfca63d76e41644ddbf34e1df525ff657000000000000000000cb05597fae3e31ae3a0dc3696bad38552e47b3d87987111e2ee602a8112f9495463fa059e93c0118d8578079").unwrap(),
hex::decode("0000002085dfc2bc2e4753ea4fa2079c69acc42a6d9da90b26530c00000000000000000069a1ec768f8b395c3efd9c04a7f11273b10589e1bfd4fa145fe26dc9cc603655d048a059e93c0118122e33dd").unwrap(),
];
let ancestry_headers_vec: Vec<Vec<u8>> = historical_hashes
.iter()
.take(max_history_depth)
.cloned()
.collect();
let gettxproof_bytes = hex::decode("00000020375a14cfcf5730daa8ff8d7a5bd2cf59fba797e289c2e0000000000000000000529cd017146361b57e6b7353b16d8d38d32f92ee842039dd779d35bfbfa2f3e53f649e59e93c01187900ea9fb40600000cfbd7b718845d123b1ae1a7fd3bce9c97acba097dc912c4ac67a920395db07ba4eef685960869ba70e0ca38890565c97dc11f1d7ae538e7b509069f81e38eff1a2e4a6b66fc4233d7d0580af4d195c7f0b274849f19d405dfc6b930bb9edd3f4a4c1f1e9fa3a336c50e8ea16039e4db4bef7ff5946e3a019937bdf626c2336d0e3926a83a575218f84e6dc55ffb9d0997a41e4b4d5425a85f22160c9f52362a95021a573ff96bb429bd80a8ec9dcb899971553efe91b6a0fe57c4694c3e8da8dc7f1babd514f2746da1ed06f08664baed853c334e0a3917d0c859b19ee14f448f77e624822070db3b606d5092027ca9ecf7e0243e6c151081021ebb3ddcdd28c1b8f9fdcffebd1962590477675f3d29d52602a1e7e941f772a3bbff42cf2eb6e74a1124bf424f767e294ae6253a21602c7c6b0381743dd6dcd57b025983e0e34375b24f67ac8beb00713d2f70594bbf797d5d94c061cb9965e6458f2f017f983d7ce37361fa13fda372f595dc1f22895ab93339457c72c480fed1c4d1164a242303ff3600").expect("Bytes32 from UTXO should be valid");
let getrawtransaction_bytes = hex::decode("010000000299a43fae581f80baf44e021ddf7b11995f184db282d6e48c22248b9fe79c43bb010000008b483045022100e9db01784dcd879c65d66127c2d52a4f9244321d15796893d6dccadaa1146e3d02203d9bb80f8df8b66692d3d9517f7cbb898d7cc40a30b175432322e1956c9b973a0141048097e4f8bb8ee104d5c2ece11d42f7e4719df8ddba459ecadecaba87d233c81a3c14830371392dd8ed030ef970450c0b7af48d9db4ba20c046896481602eb9dbffffffffb3fd05bc6000a5a0384e9a613d452e9307568df0a964907068e00403a0a0e09e020000006a47304402205bb1cbad4b29421648c1e22cac32a56aa06c933fe655559823fffb71ffebbc0b022021648d970560aa5b2031560b52a0fd06305995a58a457663324c18c1a9b0bd0b8121020d6e50b2660af27933c42bc1395fe93df90ffac5e2a989f6a134919fb8cf8075ffffffff030000000000000000296a2769642b5bb666ac1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e7c150000000000001976a914c1c69d91a5719a23ba2094d89a4df3eadfa4aef788acefea2309000000001976a9144635b1794a22bfbe6c5c5eba17b693f4aaf0e34888ac00000000").expect("Bytes32 from UTXO should be valid");
let last_header = ancestry_headers_vec.last().unwrap();
let first_sha = sp_io::hashing::sha2_256(&last_header[..80]);
let mut native_le = sp_io::hashing::sha2_256(&first_sha);
native_le.reverse();
let dynamic_trusted_root = H256::from(native_le);
LoomStates::<T>::insert(&network_id, &session, dynamic_trusted_root);
let utxo_thread_proof = UtxoThreadProof::new(
gettxproof_bytes,
getrawtransaction_bytes,
ancestry_headers_vec,
);
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/0x81acdf2da6b50d806c96aecd9a498a5359e373c0#readContract
// getRoot: session 0, atBlock 11660400
let root_string =
hex::decode("28d1e6b476b03a464f74d531d532572db29d6b57349e4f17f84e92843f6c4ee7")
.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("c5354289f0397c916949373043eb1d5ea99447f67fe014324d96c58ef02a2d11").expect("Proof should be valid")),
H256::from_slice(&hex::decode("da1fbdfefa1e9aedfedcfc48fb17cb0e731529f7a3695114e0a9caa1989dd420").expect("Proof should be valid")),
H256::from_slice(&hex::decode("1c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f").expect("Proof should be valid")),
H256::from_slice(&hex::decode("5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f").expect("Proof should be valid")),
H256::from_slice(&hex::decode("5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35f").expect("Proof should be valid")),
H256::from_slice(&hex::decode("d364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd071").expect("Proof should be valid")),
H256::from_slice(&hex::decode("68d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849f").expect("Proof should be valid")),
H256::from_slice(&hex::decode("de9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040").expect("Proof should be valid")),
];
let slots = vec![H256::from_slice(
&hex::decode("2e474dabf492786b3505d7b7cf0bf07c9dbce92c91982fa7344c77e24fd4310d")
.expect("Slot should be valid hex"),
)];
let evm_thread_proof = EvmThreadProof::new(0, 1, receiver_account, transfer_amount, 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
);
}

Some files were not shown because too many files have changed in this diff Show More