Compare commits

..

2 Commits

Author SHA1 Message Date
510e96a7d2
Kitsune2
Signed-off-by: Kitsune <kitsuneghost@hotmail.com>
2025-01-08 13:10:05 +01:00
c056b96545
Kitsune2
Signed-off-by: Kitsune <kitsuneghost@hotmail.com>
2025-01-08 12:20:33 +01:00
253 changed files with 13282 additions and 52574 deletions

1252
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ homepage.workspace = true
[workspace.package]
license = "GPL-3.0-only"
authors = ["571nky", "57r37ch", "f4750"]
version = "0.9.3"
version = "0.7.180"
edition = "2021"
homepage = "https://ghostchain.io"
repository = "https://git.ghostchain.io/ghostchain/ghost-node"
@ -67,21 +67,17 @@ serial_test = { version = "2.0.0" }
color-eyre = { version = "0.6.1" }
bs58 = { version = "0.5.0" }
prometheus-parse = { version = "0.2.2" }
k256 = { version = "0.13.0", default-features = false }
rustc-hex = { version = "2.1.0", default-features = false }
log = { version = "0.4", default-features = false }
num-traits = { version = "0.2.17", default-features = false }
libsecp256k1 = { version = "0.7", default-features = false }
bip39 = { package = "parity-bip39", version = "2.0.1" }
sha3 = { version = "0.10", default-features = false }
sha2 = { version = "0.10.7", default-features = false }
serde = { version = "1.0.197", default-features = false }
serde_derive = { version = "1.0.117", default-features = false }
serde_json = { version = "1.0.114", default-features = false }
blake2-rfc = { version = "0.2.18", default-features = false }
impl-serde = { version = "0.4.0", default-features = false }
hex = { version = "0.4.3", default-features = false }
strum = { version = "0.28.0", default-features = false }
metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false }
scale-value = { version = "0.16.0" }
@ -89,13 +85,6 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
scale-info = { version = "2.5.0", default-features = false }
subxt = { version = "0.37.0", default-features = false }
frost-secp256k1-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
frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -151,7 +140,6 @@ pallet-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -166,6 +154,8 @@ pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -193,6 +183,8 @@ generate-bags = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
# Substrate Client dependencies
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" }
@ -234,22 +226,14 @@ ghost-metrics = { path = "metrics", default-features = false }
ghost-rpc = { path = "rpc", default-features = false }
service = { package = "ghost-service", path = "service", default-features = false }
ghost-traits = { path = "pallets/traits", default-features = false }
ghost-helpers = { path = "pallets/helpers", default-features = false }
ghost-networks = { path = "pallets/networks", default-features = false }
ghost-governance = { path = "pallets/governance", default-features = false }
ghost-weaver = { path = "pallets/weaver", default-features = false }
ghost-exodus = { path = "pallets/exodus", default-features = false }
ghost-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-claims = { path = "pallets/claims", default-features = false }
ghost-slow-clap = { path = "pallets/slow-clap", default-features = false }
ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false }
casper-runtime = { path = "runtime/casper", default-features = false }
casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false }
primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false }
runtime-common = { package = "runtime-common", path = "runtime/common", default-features = false }
runtime-common = { package = "ghost-runtime-common", path = "runtime/common", default-features = false }
[dependencies]
color-eyre = { workspace = true }
@ -285,13 +269,8 @@ members = [
"runtime/casper",
"runtime/casper/constants",
"pallets/networks",
"pallets/governance",
"pallets/pools",
"pallets/sudo",
"pallets/exodus",
"pallets/exodus/runtime-api",
"pallets/helpers",
"pallets/weaver",
"pallets/claims",
"pallets/slow-clap",
"utils/bags-list",
"utils/chain-spec-builder",
"utils/generate-bags",
@ -344,12 +323,9 @@ crypto-mac = { opt-level = 3 }
curve25519-dalek = { opt-level = 3 }
ed25519-dalek = { opt-level = 3 }
flate2 = { opt-level = 3 }
frost-core = { opt-level = 3 }
frost-secp256k1-tr = { opt-level = 3 }
futures-channel = { opt-level = 3 }
hash-db = { opt-level = 3 }
hashbrown = { opt-level = 3 }
hkdf = { opt-level = 3 }
hmac = { opt-level = 3 }
httparse = { opt-level = 3 }
integer-sqrt = { opt-level = 3 }
@ -365,7 +341,6 @@ parking_lot = { opt-level = 3 }
parking_lot_core = { opt-level = 3 }
percent-encoding = { opt-level = 3 }
primitive-types = { opt-level = 3 }
rand_chacha = { opt-level = 3 }
ring = { opt-level = 3 }
rustls = { opt-level = 3 }
sha2 = { opt-level = 3 }

0
LICENSE.md Normal file → Executable file
View File

132
README.md Normal file → Executable file
View File

@ -56,12 +56,6 @@ an SSH server.
sudo apt install openssh-server
```
Start SSH Service using _systemctl_.
```bash
sudo systemctl start ssh
```
Enable SSH Service to Start on Boot: To ensure that the SSH service automatically
starts after a reboot, you need to enable it using _systemctl_.
@ -150,8 +144,23 @@ rustup default stable
rustup update
```
Add the nightly release and the nightly WebAssembly (wasm) targets
to your development environment by running the following commands:
```bash
rustup target add wasm32-unknown-unknown --toolchain stable
rustup update nightly
```
```bash
rustup target add wasm32-unknown-unknown --toolchain nightly
```
```bash
rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu
```
**_IF error try_**
```bash
rustup target add wasm32-unknown-unknown --toolchain default
```
Verify the configuration of your development environment by running
@ -160,6 +169,10 @@ the following command:
rustup show
```
```bash
rustup +nightly show
```
You should see something like this:
![images/img_9.png](images/img_9.png)
@ -170,6 +183,13 @@ Now run:
rustup component add rust-src --toolchain stable
```
**_IF error try_**
```bash
rustup component add rust-src --toolchain default
```
## Install GHOST
Check if Git is already installed:
@ -180,7 +200,7 @@ git --version
Make a GHOST Directory and go to it:
```bash
mkdir ~/ghost && cd ~/ghost
mkdir ghost && cd ghost
```
Clone GHOST Node Git:
@ -195,7 +215,7 @@ cd ghost-node
Compile the node template by running the following command:
```bash
./scripts/starter.sh --release
cargo build --release
```
The build can take up to 20 minutes depending on the specifications of your machine.
@ -203,6 +223,7 @@ The build can take up to 20 minutes depending on the specifications of your mach
![images/img_12.png](images/img_12.png)
# Boot Node Qualification
## Boot Node Qualification Test
@ -288,7 +309,7 @@ Only `port 30333` should be opened.
Press _CTRL+C_ to stop the node.
# Launching GHOST TestNet
# Launching GHOST TestNet 2.0
Switch to main GIT branch:
```bash
@ -316,7 +337,66 @@ To generate the node key use the following command:
./target/release/ghost key generate-node-key --bin --file=/etc/ghost/node-key
```
Feel free to backup the file on a separate storage device.
Generate Wallet Key file with the following command:
```bash
./target/release/ghost key generate | grep "Secret seed" | awk '{$1=$2=""; sub(/^[ \t]+/, ""); print}' > /etc/ghost/wallet-key
```
Display the wallet-key on the screen by using cat command:
```bash
./target/release/ghost key inspect $(cat /etc/ghost/wallet-key)
```
![images/img_22.png](images/img_22.png)
Feel free to back the file on a separate storage device.
Generate Stash Key file with the following command:
```bash
./target/release/ghost key generate | grep "Secret seed" | awk '{$1=$2=""; sub(/^[ \t]+/, ""); print}' > /etc/ghost/stash-key
```
Display the stash-key on the screen by using cat command:
```bash
./target/release/ghost key inspect $(cat /etc/ghost/stash-key)
```
![images/img_23.png](images/img_23.png)
Generate Session Key file with the following command:
```bash
./target/release/ghost key generate | grep "Secret seed" | awk '{$1=$2=""; sub(/^[ \t]+/, ""); print}' > /etc/ghost/session-key
```
You have generates 4 types of Session Keys:
1. Session Key AUDI
2. Session Key BABE
3. Session Key SLOW
4. Session Key GRAN
Now let's display them!
Display the session-key//audi on the screen by using cat command:
```bash
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//audi"
```
![images/img_24.png](images/img_24.png)
Display the session-key//babe on the screen by using cat command:
```bash
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//babe"
```
![images/img_25.png](images/img_25.png)
Display the session-key//slow on the screen by using cat command:
```bash
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//slow"
```
![images/img_26.png](images/img_26.png)
Display the session-key//gran on the screen by using cat command:
```bash
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//gran" --scheme=ed25519
```
![images/img_27.png](images/img_27.png)
## Build and start the ghost-node
@ -346,8 +426,9 @@ sha256sum /etc/ghost/casper.json
You should see:
```
4645c7d39a64424d823f659d408070f8c90426117e7af147b0360697b2db7f4f /etc/ghost/casper.json
6c1bab2e9c04043814b5e5e72984b00ac60150bd48cb16068495f1b49fbc5008
```
![images/img_29.png](images/img_29.png)
Create running `ghost-node` service that starts on system boot using `--unit-file` flag.
And we must set up the `ghost-node` launch command by setting arguments using
@ -372,7 +453,10 @@ To choose default options press Enter here:
![images/img_30.png](images/img_30.png)
If you want to validate from this node press Enter:
Currently, you have to qualify to become a validator node through [GHOST Whales](https://ghostchain.io/whales-apply).
If you were NOT included in the [ghosties file](https://git.ghostchain.io/ghostchain/ghost-node/src/branch/main/service/ghosties) then you cannot be a validator node,
and you can only be a full node so for `disable validator mode? [y/N]` type `y`.
If you were included in the ghosties file you can press Enter:
```
disable validator mode? [y/N]: y
```
@ -397,7 +481,7 @@ Official **Boot Node** addresses:
/dns/bootnode70.chain.ghostchain.io/tcp/30333/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW
```
A full list of available **Boot Nodes** can be found on [the guide page](https://blog.ghostchain.io/launching-ghost-testnet-3-1/#Boot_Node).
A full list of available **Boot Nodes** can be found on [the guide page](https://blog.ghostchain.io/launching-ghost-testnet-2-0/#Boot_Node).
![images/img_33.png](images/img_33.png)
@ -413,10 +497,21 @@ Type `y` and press _Enter_ for create dedicated user for running `ghost-node`:
![images/img_36.png](images/img_36.png)
**DO NOT start and DO NOT enable** `ghost-node.service` (press _Enter_):
**DO NOT start and enable** `ghost-node.service` press _Enter_:
![images/img_37.png](images/img_37.png)
Now you can check whether or not the keys on your GHOST Node match those
in the [ghosties file](https://git.ghostchain.io/ghostchain/ghost-node/src/branch/main/service/ghosties) on GHOST Git.
If you are running a **Validator Node** and if you have followed
the Generating Keys Part you will see all `[+]`.
If you are a running a **Full Node** you will see errors:
(Press _Enter_ for prompts)
```bash
./scripts/starter.sh --check-keys
```
Full Node:
![images/img_38.png](images/img_38.png)
@ -430,7 +525,7 @@ Start `ghost-node`:
sudo systemctl start ghost-node
```
Wait ~60 seconds!
Wait 60 seconds!
Check node is started:
```bash
@ -440,6 +535,11 @@ sudo systemctl status ghost-node
For exit press _CTRL+C_
In order to insert keys run the following flags `--check-keys` `--insert-keys`:
```bash
./scripts/starter.sh --check-keys --insert-keys
```
Restart `ghost-node`:
```bash
sudo systemctl restart ghost-node

0
cli/Cargo.toml Normal file → Executable file
View File

0
cli/build.rs Normal file → Executable file
View File

1
cli/src/cli.rs Normal file → Executable file
View File

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

176
cli/src/command.rs Normal file → Executable file
View File

@ -1,10 +1,14 @@
use crate::cli::{Cli, Subcommand};
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
use futures::future::TryFutureExt;
use sc_cli::SubstrateCli;
use service::{self, IdentifyVariant};
#[cfg(feature = "runtime-benchmarks")]
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
use service::{
self, IdentifyVariant,
benchmarking::{
benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder
},
};
use keyring::Sr25519Keyring;
pub use crate::{error::Error, service::BlockId};
#[cfg(feature = "pyroscope")]
@ -64,35 +68,28 @@ impl SubstrateCli for Cli {
#[cfg(feature = "casper-native")]
"casper" => Box::new(service::chain_spec::casper_config()?),
#[cfg(feature = "casper-native")]
"casper-dev" | "casper-development" => {
Box::new(service::chain_spec::casper_development_config()?)
}
"casper-dev" | "dev" | "development" => Box::new(service::chain_spec::casper_development_config()?),
#[cfg(feature = "casper-native")]
"casper-local" => {
Box::new(service::chain_spec::casper_local_testnet_config()?)
}
"casper-local" | "local" => Box::new(service::chain_spec::casper_local_testnet_config()?),
#[cfg(feature = "casper-native")]
"casper-staging" | "staging" => {
Box::new(service::chain_spec::casper_staging_testnet_config()?)
}
"casper-staging" | "staging" => Box::new(service::chain_spec::casper_staging_testnet_config()?),
#[cfg(not(feature = "casper-native"))]
name if name.starts_with("casper-") && !name.ends_with(".json") => Err(format!(
"`{}` only supported with `casper-native` feature enabled.",
name
))?,
name if name.starts_with("casper-") && !name.ends_with(".json") =>
Err(format!("`{}` only supported with `casper-native` feature enabled.", name))?,
#[cfg(feature = "casper-native")]
path => {
let path = std::path::PathBuf::from(path);
let chain_spec = Box::new(service::GenericChainSpec::from_json_file(path.clone())?)
as Box<dyn service::ChainSpec>;
let chain_spec = Box::new(
service::GenericChainSpec::from_json_file(path.clone())?
) as Box<dyn service::ChainSpec>;
if chain_spec.is_casper() {
Box::new(service::CasperChainSpec::from_json_file(path)?)
} else {
chain_spec
}
}
},
})
}
}
@ -112,7 +109,10 @@ fn set_ss58_version(spec: &Box<dyn service::ChainSpec>) {
sp_core::crypto::set_default_ss58_version(ss58_version);
}
fn run_node_inner<F>(cli: Cli, logger_hook: F) -> Result<()>
fn run_node_inner<F>(
cli: Cli,
logger_hook: F,
) -> Result<()>
where
F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
{
@ -123,16 +123,12 @@ where
set_ss58_version(chain_spec);
runner.run_node_until_exit(move |mut config| async move {
config.offchain_worker.enabled = true;
config.offchain_worker.indexing_enabled = true;
runner.run_node_until_exit(move |config| async move {
let hwbench = (!cli.run.no_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(&database_path);
sc_sysinfo::gather_hwbench(Some(database_path))
}))
.flatten();
})).flatten();
let database_source = config.database.clone();
let task_manager = service::build_full(
@ -142,8 +138,7 @@ where
telemetry_worker_handle: None,
hwbench,
},
)
.map(|full| full.task_manager)?;
).map(|full| full.task_manager)?;
if let Some(path) = database_source.path() {
sc_storage_monitor::StorageMonitorService::try_spawn(
@ -172,9 +167,7 @@ pub fn run() -> Result<()> {
let agent = pyro::Pyroscope::builder(
"http://".to_owned() + address.to_string().as_str(),
"ghost".to_owned(),
)
.backend(pprof_backend(PprofConfig::new().sample_rate(113)))
.build()?;
).backend(pprof_backend(PprofConfig::new().sample_rate(113))).build()?;
Some(agent.start()?)
} else {
@ -183,7 +176,7 @@ pub fn run() -> Result<()> {
#[cfg(not(feature = "pyroscope"))]
if cli.run.pyroscope_server.is_some() {
return Err(Error::PyroscopeNotCompiledIn);
return Err(Error::PyroscopeNotCompiledIn)
}
match &cli.subcommand {
@ -191,7 +184,7 @@ pub fn run() -> Result<()> {
Some(Subcommand::BuildSpec(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
Ok(runner.sync_run(|config| cmd.run(config.chain_spec, config.network))?)
}
},
Some(Subcommand::CheckBlock(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
@ -199,13 +192,11 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec);
runner.async_run(|mut config| {
let (client, _, import_queue, task_manager) = service::new_chain_ops(&mut config)?;
Ok((
cmd.run(client, import_queue).map_err(Error::SubstrateCli),
task_manager,
))
let (client, _, import_queue, task_manager) =
service::new_chain_ops(&mut config)?;
Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager))
})
}
},
Some(Subcommand::ExportBlocks(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
@ -213,14 +204,11 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| {
let (client, _, _, task_manager) = service::new_chain_ops(&mut config)?;
Ok((
cmd.run(client, config.database)
.map_err(Error::SubstrateCli),
task_manager,
))
let ( client, _, _, task_manager ) =
service::new_chain_ops(&mut config)?;
Ok((cmd.run(client, config.database).map_err(Error::SubstrateCli), task_manager))
})?)
}
},
Some(Subcommand::ExportState(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
@ -228,14 +216,11 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| {
let (client, _, _, task_manager) = service::new_chain_ops(&mut config)?;
Ok((
cmd.run(client, config.chain_spec)
.map_err(Error::SubstrateCli),
task_manager,
))
let ( client, _, _, task_manager ) =
service::new_chain_ops(&mut config)?;
Ok((cmd.run(client, config.chain_spec).map_err(Error::SubstrateCli), task_manager))
})?)
}
},
Some(Subcommand::ImportBlocks(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
@ -243,17 +228,15 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| {
let (client, _, import_queue, task_manager) = service::new_chain_ops(&mut config)?;
Ok((
cmd.run(client, import_queue).map_err(Error::SubstrateCli),
task_manager,
))
let ( client, _, import_queue, task_manager ) =
service::new_chain_ops(&mut config)?;
Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager))
})?)
}
},
Some(Subcommand::PurgeChain(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
Ok(runner.sync_run(|config| cmd.run(config.database))?)
}
},
Some(Subcommand::Revert(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
@ -261,58 +244,68 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| {
let (client, backend, _, task_manager) = service::new_chain_ops(&mut config)?;
let (client, backend, _, task_manager) =
service::new_chain_ops(&mut config)?;
let aux_revert = Box::new(|client, backend, blocks| {
service::revert_backend(client, backend, blocks).map_err(|err| match err {
service::revert_backend(client, backend, blocks).map_err(|err| {
match err {
service::Error::Blockchain(err) => err.into(),
err => sc_cli::Error::Application(err.into()),
}
})
});
Ok((
cmd.run(client, backend, Some(aux_revert))
.map_err(Error::SubstrateCli),
task_manager,
cmd.run(client, backend, Some(aux_revert)).map_err(Error::SubstrateCli),
task_manager
))
})?)
},
#[cfg(feature = "runtime-benchmarks")]
Some(Subcommand::Benchmark(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec;
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| {
let (client, backend, _, _) = service::new_chain_ops(&mut config)?;
let (client, backend, _, _,) =
service::new_chain_ops(&mut config)?;
let db = backend.expose_db();
let storage = backend.expose_storage();
cmd.run(config, client.clone(), db, storage)
.map_err(Error::SubstrateCli)
cmd.run(config, client.clone(), db, storage).map_err(Error::SubstrateCli)
}),
BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| {
let (client, _, _, _) = service::new_chain_ops(&mut config)?;
let (client, _, _, _,) =
service::new_chain_ops(&mut config)?;
cmd.run(client.clone()).map_err(Error::SubstrateCli)
}),
// These commands are very similar and can be handled in nearly the same way
BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
use service::benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder};
BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) =>
runner.sync_run(|mut config| {
let (client, _, _, _) = service::new_chain_ops(&mut config)?;
let (client, _, _, _) =
service::new_chain_ops(&mut config)?;
let inherent_data = benchmark_inherent_data()
.map_err(|e| format!("generating inherent data: {:?}", e))?;
let remark_builder =
RemarkBuilder::new(client.clone(), config.chain_spec.identify_chain());
let remark_builder = RemarkBuilder::new(
client.clone(),
config.chain_spec.identify_chain(),
);
match cmd {
BenchmarkCmd::Extrinsic(cmd) => {
let tka_builder = TransferKeepAliveBuilder::new(
client.clone(),
keyring::Sr25519Keyring::Alice.to_account_id(),
Sr25519Keyring::Alice.to_account_id(),
config.chain_spec.identify_chain(),
);
@ -323,34 +316,35 @@ pub fn run() -> Result<()> {
cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory)
.map_err(Error::SubstrateCli)
}
BenchmarkCmd::Overhead(cmd) => cmd
.run(
},
BenchmarkCmd::Overhead(cmd) => cmd.run(
config,
client.clone(),
inherent_data,
Vec::new(),
&remark_builder,
)
.map_err(Error::SubstrateCli),
).map_err(Error::SubstrateCli),
_ => unreachable!("Ensured by the outside match; qed"),
}
})
}
}),
BenchmarkCmd::Pallet(cmd) => {
set_ss58_version(chain_spec);
if cfg!(feature = "runtime-benchmarks") {
runner.sync_run(|config| {
cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>(
Some(config.chain_spec),
).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| {
cmd.run(
&config,
ghost_machine_primitives::GHOST_NODE_REFERENCE_HARDWARE.clone(),
)
cmd.run(&config, ghost_machine_primitives::GHOST_NODE_REFERENCE_HARDWARE.clone())
.map_err(Error::SubstrateCli)
}),
// Note: this allows to implement additional new benchmark
@ -358,12 +352,12 @@ pub fn run() -> Result<()> {
#[allow(unreachable_patterns)]
_ => Err(Error::CommandNotImplemented),
}
}
},
Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
Some(Subcommand::ChainInfo(cmd)) => {
let runner = cli.create_runner(cmd)?;
Ok(runner.sync_run(|config| cmd.run::<service::Block>(&config))?)
}
},
}?;
#[cfg(feature = "pyroscope")]

0
cli/src/error.rs Normal file → Executable file
View File

4
cli/src/lib.rs Normal file → Executable file
View File

@ -6,7 +6,9 @@ mod command;
mod error;
#[cfg(feature = "service")]
pub use service::{self, Block, CoreApi, IdentifyVariant, ProvideRuntimeApi, TFullClient};
pub use service::{
self, Block, CoreApi, IdentifyVariant, ProvideRuntimeApi, TFullClient,
};
#[cfg(feature = "cli")]
pub use cli::*;

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-client-cli"
version = "0.1.4"
version = "0.1.3"
description = "Ghost CLI interface"
license.workspace = true
authors.workspace = true

View File

@ -3,10 +3,13 @@
use bip39::Mnemonic;
use clap::Parser;
use itertools::Itertools;
use sc_cli::{with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag};
use sc_cli::{
with_crypto_scheme, KeystoreParams, OutputTypeFlag,
CryptoSchemeFlag, Error,
};
use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::print_from_uri;
/// The `generate` command
#[derive(Debug, Clone, Parser)]

View File

@ -1,13 +1,14 @@
//! Implementation of the `inspect` subcommand
use crate::commands::utils::{print_from_public, print_from_uri};
use crate::params::NetworkSchemeFlag;
use clap::Parser;
use sc_cli::{
utils::read_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag,
};
use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
use std::str::FromStr;
use sc_cli::{
with_crypto_scheme, KeystoreParams, OutputTypeFlag, CryptoSchemeFlag, Error,
utils::read_uri,
};
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::{print_from_public, print_from_uri};
/// The `inspect` command
#[derive(Debug, Parser)]
@ -235,16 +236,8 @@ mod tests {
array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
// They should still be valid, because we check the base secret key.
check_cmd(
&seed_with_password_and_derivation,
&valid_public_hex_with_password,
true,
);
check_cmd(
&seed_with_password_and_derivation,
&valid_accountid_with_password,
true,
);
check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true);
// And these should be invalid.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);

View File

@ -1,7 +1,10 @@
//! Key related CLI utilities
use super::{generate::GenerateCmd, inspect_key::InspectKeyCmd};
use sc_cli::{Error, GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, SubstrateCli};
use sc_cli::{
GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, Error,
SubstrateCli,
};
/// Key utilities for the cli.
#[derive(Debug, clap::Subcommand)]

View File

@ -1,13 +1,11 @@
mod generate;
mod inspect_key;
mod key;
mod utils;
mod generate;
mod vanity;
mod inspect_key;
mod utils;
pub use self::{
key::KeySubcommand, vanity::VanityCmd, inspect_key::InspectKeyCmd,
generate::GenerateCmd,
inspect_key::InspectKeyCmd,
key::KeySubcommand,
utils::{print_from_public, print_from_uri, unwrap_or_default_ss58_name},
vanity::VanityCmd,
utils::{unwrap_or_default_ss58_name, print_from_uri, print_from_public},
};

View File

@ -1,15 +1,16 @@
use sc_cli::{
utils::{PublicFor, SeedFor},
OutputType,
};
use serde_json::json;
use sc_cli::{
OutputType,
utils::{PublicFor, SeedFor},
};
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
use sp_core::{
crypto::{
unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
unwrap_or_default_ss58_version,
Ss58Codec, ExposeSecret, Ss58AddressFormat, SecretString,
},
hexdisplay::HexDisplay,
};
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
pub fn print_from_uri<Pair>(
uri: &str,
@ -42,7 +43,7 @@ pub fn print_from_uri<Pair>(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
}
},
OutputType::Text => {
println!(
"Secret phrase: {}\n \
@ -58,12 +59,9 @@ pub fn print_from_uri<Pair>(
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public()
.into()
.into_account()
.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
}
},
}
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
@ -84,7 +82,7 @@ pub fn print_from_uri<Pair>(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
}
},
OutputType::Text => {
println!(
"Secret Key URI `{}` is account:\n \
@ -96,20 +94,13 @@ pub fn print_from_uri<Pair>(
SS58 Address: {}",
uri,
network_id,
if let Some(seed) = seed {
format_seed::<Pair>(seed)
} else {
"n/a".into()
},
if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public()
.into()
.into_account()
.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
}
},
}
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
let network_override = network_override.unwrap_or(network);
@ -129,7 +120,7 @@ pub fn print_from_uri<Pair>(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
}
},
OutputType::Text => {
println!(
"Public Key URI `{}` is account:\n \
@ -145,7 +136,7 @@ pub fn print_from_uri<Pair>(
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
}
},
}
} else {
println!("Invalid phrase/URI given");
@ -179,11 +170,8 @@ where
"ss58Address": public_key.to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
}
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
},
OutputType::Text => {
println!(
"Network ID/Version: {}\n \
@ -197,7 +185,7 @@ where
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
}
},
}
Ok(())
@ -228,8 +216,5 @@ fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where
PublicFor<P>: Into<MultiSigner>,
{
format!(
"0x{}",
HexDisplay::from(&public_key.into().into_account().as_ref())
)
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
}

View File

@ -2,19 +2,21 @@
use clap::Parser;
use rand::{rngs::OsRng, RngCore};
use sc_cli::{utils::format_seed, with_crypto_scheme, CryptoSchemeFlag, Error, OutputTypeFlag};
use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
use sp_core::crypto::{
unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec,
};
use sp_runtime::traits::IdentifyAccount;
use sc_cli::{
with_crypto_scheme, Error, OutputTypeFlag, CryptoSchemeFlag,
utils::format_seed,
};
use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag;
/// The `vanity` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "vanity",
about = "Generate a seed that provides a vanity address"
)]
#[command(name = "vanity", about = "Generate a seed that provides a vanity address")]
pub struct VanityCmd {
/// Desired pattern
#[arg(long, value_parser = assert_non_empty_string)]
@ -82,17 +84,14 @@ where
}
let p = Pair::from_seed(&seed);
let ss58 = p
.public()
.into_account()
.to_ss58check_with_version(network_override);
let ss58 = p.public().into_account().to_ss58check_with_version(network_override);
println!("{:?}", ss58);
let score = calculate_score(desired, &ss58);
if score > best || desired.len() < 2 {
best = score;
if best >= top {
println!("best: {} == top: {}", best, top);
return Ok(format_seed::<Pair>(seed.clone()));
return Ok(format_seed::<Pair>(seed.clone()))
}
}
done += 1;
@ -117,11 +116,11 @@ fn next_seed(seed: &mut [u8]) {
match s {
255 => {
*s = 0;
}
},
_ => {
*s += 1;
break;
}
break
},
}
}
}
@ -133,7 +132,7 @@ fn calculate_score(_desired: &str, key: &str) -> usize {
let snip_size = _desired.len() - truncate;
let truncated = &_desired[0..snip_size];
if let Some(pos) = key.find(truncated) {
return (47 - pos) + (snip_size * 48);
return (47 - pos) + (snip_size * 48)
}
}
0
@ -165,13 +164,11 @@ mod tests {
#[test]
fn test_generation_with_single_char() {
let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
assert!(
sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check()
.contains("ab")
);
.contains("ab"));
}
#[test]
@ -179,13 +176,11 @@ mod tests {
let seed =
generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into())
.unwrap();
assert!(
sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())
.contains("ab")
);
.contains("ab"));
}
#[test]

View File

@ -1,5 +1,5 @@
pub mod commands;
pub mod params;
pub mod commands;
pub use commands::KeySubcommand;
pub use commands::VanityCmd;

View File

@ -21,21 +21,15 @@ pub struct InnerSs58AddressFormat(Ss58AddressFormat);
impl InnerSs58AddressFormat {
#[inline]
pub fn custom(prefix: u16) -> Self {
Self {
0: Ss58AddressFormat::custom(prefix),
}
Self { 0: Ss58AddressFormat::custom(prefix) }
}
}
impl std::fmt::Display for InnerSs58AddressFormat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} network",
ALL_POSSIBLE_IDS
write!(f, "{} network", ALL_POSSIBLE_IDS
.binary_search(&u16::from(self.0))
.expect("always be found")
)
.expect("always be found"))
}
}
@ -50,13 +44,13 @@ impl<'a> TryFrom<&'a str> for InnerSs58AddressFormat {
}
}
pub fn parse_s58_prefix_address_format(x: &str) -> Result<Ss58AddressFormat, String> {
pub fn parse_s58_prefix_address_format(x: &str,) -> Result<Ss58AddressFormat, String> {
match InnerSs58AddressFormat::try_from(x) {
Ok(format_registry) => Ok(format_registry.0.into()),
Err(_) => Err(format!(
"Unable to parse variant. Known variants: {:?}",
&ALL_POSSIBLE_NAMES
)),
))
}
}

0
core-primitives/Cargo.toml Normal file → Executable file
View File

0
core-primitives/src/lib.rs Normal file → Executable file
View File

0
file_header.txt Normal file → Executable file
View File

BIN
images/img_29.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -22,11 +22,7 @@ impl Metronome {
/// Create a new metronome source with a defined cycle duration.
pub fn new(cycle: Duration) -> Self {
let period = cycle.into();
Self {
period,
delay: Delay::new(period),
state: MetronomeState::Snooze,
}
Self { period, delay: Delay::new(period), state: MetronomeState::Snooze }
}
}
@ -40,14 +36,14 @@ impl futures::Stream for Metronome {
let val = self.period;
self.delay.reset(val);
self.state = MetronomeState::Snooze;
}
},
MetronomeState::Snooze => {
if !Pin::new(&mut self.delay).poll(cx).is_ready() {
break;
break
}
self.state = MetronomeState::SetAlarm;
return Poll::Ready(Some(()));
}
return Poll::Ready(Some(()))
},
}
}
Poll::Pending

View File

@ -2,16 +2,19 @@
use codec::Decode;
use core_primitives::{
metrics_definitions::{CounterDefinition, CounterVecDefinition, HistogramDefinition},
metrics_definitions::{
CounterDefinition, CounterVecDefinition, HistogramDefinition,
},
RuntimeMetricLabelValues, RuntimeMetricOp, RuntimeMetricUpdate,
};
use prometheus_endpoint::{
register, Counter, CounterVec, Histogram, HistogramOpts, Opts, PrometheusError, Registry, U64,
register, Counter, CounterVec, Histogram, HistogramOpts, Opts, Registry,
PrometheusError, U64,
};
use std::{
collections::hash_map::HashMap,
sync::{Arc, Mutex, MutexGuard},
};
}
#[derive(Clone, Debug)]
pub struct Metrics {
@ -29,9 +32,7 @@ impl RuntimeMetricsProvider {
pub fn register_countervec(&self, countervec: CounterVecDefinition) {
self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap
.entry(countervec.name.to_owned())
.or_insert(register(
hashmap.entry(countervec.name.to_owned()).or_insert(register(
CounterVec::new(
Opts::new(countervec.name, countervec.description),
countervec.labels,
@ -45,7 +46,10 @@ impl RuntimeMetricsProvider {
pub fn register_counter(&self, counter: CounterDefinition) {
self.with_counters_lock_held(|mut hashmap| {
hashmap.entry(counter.name.to_owned()).or_insert(register(
Counter::new(counter.name, counter.description)?,
Counter::new(
counter.name,
counter.description,
)?,
&self.0,
)?);
Ok(())
@ -56,7 +60,8 @@ impl RuntimeMetricsProvider {
self.with_histograms_lock_held(|mut hashmap| {
hashmap.entry(hist.name.to_owned()).or_insert(register(
Histogram::with_opts(
HistogramOpts::new(hist.name, hist.description).buckets(hist.buckets.to_vec()),
HistogramOpts::new(hist.name, hist.description)
.buckets(hist.buckets.to_vec()),
)?,
&self.0,
)?);
@ -64,12 +69,15 @@ impl RuntimeMetricsProvider {
})
}
pub fn inc_counter_vec_by(&self, name: &str, value: u64, labels: &RuntimeMetricsProvider) {
pub fn inc_counter_vec_by(
&self,
name: &str,
value: u64,
labels: &RuntimeMetricsProvider,
) {
self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned()).and_modify(|counter_vec| {
counter_vec
.with_label_values(&labels.as_str_vec())
.inc_by(value)
counter_vec.with_label_values(&labels.as_str_vec()).inc_by(value)
});
Ok(())
})
@ -77,8 +85,7 @@ impl RuntimeMetricsProvider {
pub fn inc_counter_by(&self, name: &str, value: u64) {
self.with_counters_lock_held(|mut hashmap| {
hashmap
.entry(name.to_owned())
hashmap.entry(name.to_owned())
.and_modify(|counter_vec| counter_vec.inc_by(value));
Ok(())
})
@ -86,9 +93,8 @@ impl RuntimeMetricsProvider {
pub fn observe_histogram(&self, name: &str, value: u128) {
self.with_histograms_lock_held(|mut hashmap| {
hashmap
.entry(name.to_owned())
.and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0));
hashmap.entry(name.to_owned())
and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0));
Ok(())
})
}
@ -97,36 +103,21 @@ impl RuntimeMetricsProvider {
where
F: FnOnce(MutexGuard<'_, HashMap<String, Counter<U64>>>) -> Result<(), PrometheusError>,
{
let _ = self
.1
.counters
.lock()
.map(do_something)
.or_else(|error| Err(error));
let _ = self.1.counters.lock().map(do_something).or_else(|error| Err(error));
}
fn with_counter_vecs_lock_held<F>(&self, do_something: F)
where
F: FnOnce(MutexGuard<'_, HashMap<String, CounterVec<U64>>>) -> Result<(), PrometheusError>,
{
let _ = self
.1
.counter_vecs
.lock()
.map(do_something)
.or_else(|error| Err(error));
let _ = self.1.counter_vecs.lock().map(do_something).or_else(|error| Err(error));
}
fn with_histograms_lock_held<F>(&self, do_something: F)
where
F: FnOnce(MutexGuard<'_, HashMap<String, Histogram>>) -> Result<(), PrometheusError>,
{
let _ = self
.1
.histograms
.lock()
.map(do_something)
.or_else(|error| Err(error));
let _ = self.1.histograms.lock().map(do_something).or_else(|error| Err(error));
}
}
@ -140,7 +131,7 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
.unwrap_or(&String::default())
.ne("metrics")
{
return;
return
}
if let Some(update_op_bs58) = event.values.string_values.get("params") {
@ -150,7 +141,7 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
.as_slice(),
) {
Ok(update_op) => self.parse_metric_update(update_op),
Err(_) => {}
Err(_) => {},
}
}
}
@ -159,15 +150,12 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
impl RuntimeMetricsProvider {
fn parse_metric_update(&self, update: RuntimeMetricUpdate) {
match update.op {
RuntimeMetricOp::IncrementCounterVec(value, ref labels) => {
self.inc_counter_vec_by(update.metric_name(), value, labels)
}
RuntimeMetricOp::IncrementCounter(value) => {
self.inc_counter_by(update.metric_name(), value)
}
RuntimeMetricOp::ObserveHistogram(value) => {
self.observe_histogram(update.metric_name(), value)
}
RuntimeMetricOp::IncrementCounterVec(value, ref labels) =>
self.inc_counter_vec_by(update.metric_name(), value, labels),
RuntimeMetricOp::IncrementCounter(value) =>
self.inc_counter_by(update.metric_name(), value),
RuntimeMetricOp::ObserveHistogram(value) =>
self.observe_histogram(update.metric_name(), value),
}
}
@ -190,7 +178,7 @@ impl RuntimeMetricsProvider {
pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
|logger_builder, config| {
if config.prometheus_registry().is_none() {
return;
return
}
let registry = config.prometheus_registry().cloned().unwrap();

View File

@ -1,5 +1,5 @@
use ghost_test_service::{node_config, run_validator_node, test_prometheus_config};
use hyper::{Client, Uri};
use ghost_test_service::{node_config, run_validator_node, test_prometheus_config};
use keyring::AccountKeyring::*;
use std::collections::HashMap;
@ -7,13 +7,8 @@ const DEFAULT_PROMETHEUS_PORT: u16 = 9616;
#[tokio::test(flavor = "multi_thread")]
async fn runtime_can_publish_metrics() {
let mut alice_config = node_config(
|| {},
tokio::runtime::Handle::current(),
Alice,
Vec::new(),
true,
);
let mut alice_config =
node_config(|| {}, tokio::runtime::Handle::current(), Alice, Vec::new(), true);
// Enable prometheus metrics for Alice.
alice_config.prometheus_config = Some(test_prometheus_config(DEFAULT_PROMETHEUS_PORT));
@ -31,13 +26,8 @@ async fn runtime_can_publish_metrics() {
// Start validator Alice
let alice = run_validator_node(alice_config, None);
let bob_config = node_config(
|| {},
tokio::runtime::Handle::current(),
Bob,
vec![alice.addr.clone()],
true,
);
let bob_config =
node_config(|| {}, tokio::runtime::Handle::current(), Bob, vec![alice.addr.clone()], true);
// Start validator Bob
let _bob = run_validator_node(bob_config, None);
@ -58,25 +48,19 @@ async fn scrape_prometheus_metrics(metrics_uri: &str) -> HashMap<String, u64> {
.expect("GET request failed");
let body = String::from_utf8(
hyper::body::to_bytes(res)
.await
.expect("can't get body as bytes")
.to_vec(),
)
.expect("body is not an UTF8 string");
hyper::body::to_bytes(res).await.expect("can't get body as bytes").to_vec(),
).expect("body is not an UTF8 string");
let lines: Vec<_> = body.lines().map(|s| Ok(s.to_owned())).collect();
prometheus_parse::Scrape::parse(lines.into_iter())
.expect("Scraper failed to parse Prometheus metrics")
.samples
.into_iter()
.filter_map(
|prometheus_parse::Sample { metric, value, .. }| match value {
.filter_map(|prometheus_parse::Sample { metric, value, .. }| match value {
prometheus_parse::Value::Counter(value) => Some((metric, value as u64)),
prometheus_parse::Value::Gauge(value) => Some((metric, value as u64)),
prometheus_parse::Value::Untyped(value) => Some((metric, value as u64)),
_ => None,
},
)
})
.collect()
}

View File

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

View File

@ -0,0 +1,56 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
#[instance_benchmarks]
mod benchmarks {
use super::*;
use pallet_ranked_collective::Pallet as Club;
use frame_support::dispatch::RawOrigin;
#[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,
);
}

313
pallets/claims/src/lib.rs Normal file
View File

@ -0,0 +1,313 @@
#![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::*;
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
pub use pallet::*;
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
use sp_runtime::traits::{CheckedSub, CheckedDiv, BlockNumberProvider};
use sp_std::prelude::*;
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{format, string::String};
pub mod weights;
pub use crate::weights::WeightInfo;
mod tests;
mod mock;
mod benchmarking;
mod secp_utils;
/// 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(())
}
}

221
pallets/claims/src/mock.rs Normal file
View File

@ -0,0 +1,221 @@
#![cfg(test)]
use super::*;
pub use crate as ghost_claims;
use frame_support::{
parameter_types, derive_impl,
traits::{PollStatus, Polling, WithdrawReasons, ConstU16, ConstU64},
};
use frame_system::EnsureRootWithSuccess;
use sp_runtime::{BuildStorage, traits::Convert};
pub use pallet_ranked_collective::{TallyOf, Rank};
pub mod eth_keys {
use crate::{
mock::Test,
EcdsaSignature, EthereumAddress,
};
use hex_literal::hex;
use codec::Encode;
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,37 @@
#![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)
}

274
pallets/claims/src/tests.rs Normal file
View File

@ -0,0 +1,274 @@
#![cfg(test)]
use mock::{
new_test_ext, ghost_claims,
Test, System, Balances, Club, Vesting, Claims, RuntimeOrigin, RuntimeEvent,
eth_keys::{
alice_account_id, total_claims, first_eth_public_known,
first_eth_public_key, first_account_id, first_signature,
second_eth_public_known, second_eth_public_key, second_account_id,
second_signature, third_eth_public_known, third_eth_public_key,
third_account_id, third_signature, fourth_eth_public_known,
fourth_eth_public_key, fourth_account_id, fourth_signature,
wrong_signature, bob_account_id,
}
};
use hex_literal::hex;
use frame_support::{assert_err, assert_ok};
use super::*;
#[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,9 @@
use frame_support::weights::Weight;
pub trait WeightInfo {
fn claim() -> Weight;
}
impl WeightInfo for () {
fn claim() -> Weight { Weight::zero() }
}

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

18
pallets/networks/Cargo.toml Normal file → Executable file
View File

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

290
pallets/networks/src/benchmarking.rs Normal file → Executable file
View File

@ -6,51 +6,42 @@ use crate::Pallet as GhostNetworks;
use frame_benchmarking::v1::{benchmarks, BenchmarkError};
use sp_runtime::Saturating;
use ghost_helpers::networks::{
MAX_ENDPOINT_LEN, MAX_ENDPOINTS_LEN, MAX_GATEKEEPER_LEN,
MAX_SELECTOR_LEN,
};
const MAX_NAME_LEN: u32 = 20;
const MAX_ENDPOINT_LEN: u32 = 150;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
}
fn prepare_network<T: Config>(
n: u32,
m: u32,
k: u32,
) -> (T::NetworkId, NetworkData) {
n: u32, m: u32,
) -> (<T as module::Config>::NetworkId, NetworkData) {
let chain_id: <T as module::Config>::NetworkId = Default::default();
let chain_id = chain_id.saturating_add((n + m).into());
let mut gatekeeper = sp_std::vec![];
for i in 0..n {
gatekeeper.push((i % 256) as u8);
}
let mut gatekeeper = b"0x".to_vec();
for i in 0..40 { gatekeeper.push(i); }
let mut default_endpoints = sp_std::vec![];
for _ in 0..(k as usize) {
default_endpoints.push(sp_std::vec![0x69; m as usize]);
}
let mut topic_name = b"0x".to_vec();
for i in 0..64 { topic_name.push(i); }
let network = ghost_helpers::networks::NetworkDataBuilder::default()
.with_gatekeeper(gatekeeper)
.with_default_endpoints(default_endpoints)
.with_selector(vec![1u8; 4])
.with_network_type(NetworkType::Evm)
.with_network_curve(NetworkCurve::Secp256k1)
.with_rate_limit_delay(6)
.with_finality_delay(69)
.with_block_deviation(420)
.with_incoming_share(0)
.with_outgoing_share(0)
.build();
let network = NetworkData {
chain_name: sp_std::vec![0x69; n as usize],
default_endpoint: sp_std::vec![0x69; m as usize],
gatekeeper,
topic_name,
finality_delay: Some(69),
release_delay: Some(69),
network_type: NetworkType::Evm,
incoming_fee: 0,
outgoing_fee: 0,
};
(chain_id, network)
}
fn create_network<T: Config>(
chain_id: T::NetworkId,
chain_id: <T as module::Config>::NetworkId,
network: NetworkData,
) -> Result<Option<NetworkData>, BenchmarkError> {
let prev_network = match GhostNetworks::<T>::get(&chain_id) {
@ -59,11 +50,8 @@ fn create_network<T: Config>(
let authority = T::RegisterOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
GhostNetworks::<T>::register_network(
authority,
chain_id,
network.clone(),
)
.map_err(|_| BenchmarkError::Weightless)?;
authority.clone(), chain_id.clone(), network.clone()
).map_err(|_| BenchmarkError::Weightless)?;
network
}
};
@ -73,185 +61,161 @@ fn create_network<T: Config>(
benchmarks! {
register_network {
let i in 1 .. MAX_GATEKEEPER_LEN;
let i in 1 .. MAX_NAME_LEN;
let j in 1 .. MAX_ENDPOINT_LEN;
let k in 1 .. MAX_ENDPOINTS_LEN;
let (chain_id, network) = prepare_network::<T>(i, j, k);
let (chain_id, network) = prepare_network::<T>(i, j);
let authority = T::RegisterOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
assert_eq!(GhostNetworks::<T>::networks(chain_id), None);
assert!(GhostNetworks::<T>::network_indexes().is_empty());
}: _<T::RuntimeOrigin>(authority, chain_id, network.clone())
let prev_network = GhostNetworks::<T>::networks(chain_id.clone());
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone())
verify {
assert_last_event::<T>(Event::NetworkRegistered {chain_id: chain_id}.into());
assert_eq!(GhostNetworks::<T>::networks(chain_id), Some(network));
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
assert_last_event::<T>(Event::NetworkRegistered {
chain_id: chain_id.clone(), network,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_selector {
let raw_selector = sp_std::vec![0x42; MAX_SELECTOR_LEN as usize];
let selector = BoundedVec::<u8, ConstU32<MAX_SELECTOR_LEN>>::try_from(raw_selector)
.map_err(|_| BenchmarkError::Weightless)?;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
update_network_name {
let n in 1 .. MAX_NAME_LEN;
let name = sp_std::vec![0x42; n as usize];
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, selector)
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), name.clone())
verify {
assert_last_event::<T>(Event::NetworkSelectorUpdated { chain_id }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
assert_last_event::<T>(Event::NetworkNameUpdated {
chain_id: chain_id.clone(), chain_name: name,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_endpoint {
let n in 1 .. MAX_ENDPOINT_LEN;
let index = 0u32;
let raw_endpoint = sp_std::vec![0x42; n as usize];
let endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(raw_endpoint)
.map_err(|_| BenchmarkError::Weightless)?;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let endpoint = sp_std::vec![0x42; n as usize];
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, Some(index), Some(endpoint))
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), endpoint.clone())
verify {
assert_last_event::<T>(Event::NetworkEndpointUpdated {chain_id, index }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
assert_last_event::<T>(Event::NetworkEndpointUpdated {
chain_id: chain_id.clone(), default_endpoint: endpoint,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_finality_delay {
let delay = 1337;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let delay = Some(1337);
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, delay)
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
verify {
assert_last_event::<T>(Event::NetworkFinalityDelayUpdated {
chain_id, finality_delay: delay,
chain_id: chain_id.clone(), finality_delay: delay,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_rate_limit_delay {
let rate_limit = 1337;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
update_network_release_delay {
let delay = Some(1337);
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, rate_limit)
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
verify {
assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated {
chain_id, rate_limit_delay: rate_limit,
assert_last_event::<T>(Event::NetworkReleaseDelayUpdated {
chain_id: chain_id.clone(), release_delay: delay,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
}
update_network_block_deviation {
let block_deviation = 1337;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, block_deviation)
verify {
assert_last_event::<T>(Event::NetworkBlockDeviationUpdated { chain_id, block_deviation }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_type {
let network_type = NetworkType::Utxo;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, network_type.clone())
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network_type.clone())
verify {
assert_last_event::<T>(Event::NetworkTypeUpdated { chain_id, network_type }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
}
update_network_curve {
let network_curve = NetworkCurve::Ed25519;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, network_curve)
verify {
assert_last_event::<T>(Event::NetworkCurveUpdated { chain_id, network_curve }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
}
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_last_event::<T>(Event::NetworkTypeUpdated {
chain_id: chain_id.clone(), network_type: network_type.clone(),
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_outgoing_network_share {
let outgoing_share = 1337;
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
update_network_gatekeeper {
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);
let authority = T::UpdateOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id, network)?;
}: _<T::RuntimeOrigin>(authority, chain_id, outgoing_share)
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), gatekeeper.clone())
verify {
assert_last_event::<T>(Event::NetworkOutgoingShareUpdated { chain_id, outgoing_share }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
assert_last_event::<T>(Event::NetworkGatekeeperUpdated {
chain_id: chain_id.clone(), gatekeeper,
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
update_network_topic_name {
let topic_name = b"0x9876543219876543219876543219876543219876543219876543219876543219".to_vec();
let (chain_id, network) = prepare_network::<T>(1, 1);
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);
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);
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 {
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let (chain_id, network) = prepare_network::<T>(1, 1);
let authority = T::RemoveOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let maybe_network = create_network::<T>(chain_id, network)?;
assert_eq!(GhostNetworks::<T>::networks(chain_id), maybe_network);
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
}: _<T::RuntimeOrigin>(authority, chain_id)
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
}: _<T::RuntimeOrigin>(authority, chain_id.clone())
verify {
assert_last_event::<T>(Event::NetworkRemoved { chain_id }.into());
assert_eq!(GhostNetworks::<T>::networks(chain_id), None);
assert!(GhostNetworks::<T>::network_indexes().is_empty());
assert_last_event::<T>(Event::NetworkRemoved {
chain_id: chain_id.clone(),
}.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
}
impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test);

793
pallets/networks/src/lib.rs Normal file → Executable file

File diff suppressed because it is too large Load Diff

47
pallets/networks/src/mock.rs Normal file → Executable file
View File

@ -1,24 +1,19 @@
use crate::{self as ghost_networks};
use frame_support::{
construct_runtime, ord_parameter_types, parameter_types,
traits::{ConstU128, ConstU32, Everything},
};
use crate as ghost_networks;
use frame_system::EnsureSignedBy;
use frame_support::{
construct_runtime, ord_parameter_types, parameter_types, traits::{ConstU128, ConstU32, Everything}
};
pub use primitives::{
AccountId, Balance, BlockNumber, FreezeIdentifier, Hash, Nonce, ReserveIdentifier,
AccountId, Balance, Nonce, BlockNumber, Hash,
ReserveIdentifier, FreezeIdentifier,
};
use sp_runtime::{
curve::PiecewiseLinear,
traits::{AccountIdLookup, BlakeTwo256},
traits::{BlakeTwo256, AccountIdLookup},
BuildStorage,
};
parameter_types! {
pub const BlockHashCount: BlockNumber = 250;
pub static SlashDeferDuration: u32 = 0;
pub static Period: BlockNumber = 5;
pub static Offset: BlockNumber = 0;
pub static MaxControllersInDeprecationBatch: u32 = 5_000;
}
impl frame_system::Config for Test {
@ -69,22 +64,6 @@ impl pallet_balances::Config for Test {
type MaxFreezes = ConstU32<50>;
}
pallet_staking_reward_curve::build! {
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
min_inflation: 0_006_900,
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 const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const MaxNetworks: u32 = 3;
}
ord_parameter_types! {
pub const RegistererAccount: AccountId = AccountId::from([1u8; 32]);
pub const UpdaterAccount: AccountId = AccountId::from([2u8; 32]);
@ -94,16 +73,14 @@ ord_parameter_types! {
impl ghost_networks::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type NetworkId = u32;
type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>;
type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>;
type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>;
type MaxNetworks = MaxNetworks;
type WeightInfo = ();
type RegisterOrigin = EnsureSignedBy::<RegistererAccount, AccountId>;
type UpdateOrigin = EnsureSignedBy::<UpdaterAccount, AccountId>;
type RemoveOrigin = EnsureSignedBy::<RemoverAccount, AccountId>;
type WeightInfo = crate::weights::SubstrateWeight<Test>;
}
type Block = frame_system::mocking::MockBlock<Test>;
type Block = frame_system::mocking::MockBlockU32<Test>;
construct_runtime!(
pub enum Test {

1359
pallets/networks/src/tests.rs Normal file → Executable file

File diff suppressed because it is too large Load Diff

423
pallets/networks/src/weights.rs Normal file → Executable file
View File

@ -15,14 +15,14 @@
//! Autogenerated weights for `ghost_networks`
//!
//! 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: `[]`
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-11-29, 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
//! HOSTNAME: `ghost`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("casper-dev"), DB CACHE: 1024
// Executed Command:
// ./target/release/ghost
// ./target/production/ghost
// benchmark
// pallet
// --chain=casper-dev
@ -30,8 +30,8 @@
// --repeat=20
// --pallet=ghost_networks
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --header=./file_header.txt
// --output=./runtime/casper/src/weights/ghost_networks.rs
@ -49,204 +49,277 @@ use sp_std::marker::PhantomData;
/// Weight functions needed for ghost_networks
pub trait WeightInfo {
fn register_network(i: u32, j: u32, k: u32, ) -> Weight;
fn update_network_selector() -> Weight;
fn register_network(n: u32, m: u32, ) -> Weight;
fn update_network_name(n: u32, ) -> Weight;
fn update_network_endpoint(n: u32, ) -> Weight;
fn update_network_finality_delay() -> Weight;
fn update_network_rate_limit_delay() -> Weight;
fn update_network_block_deviation() -> Weight;
fn update_network_release_delay() -> Weight;
fn update_network_type() -> Weight;
fn update_network_curve() -> Weight;
fn update_network_decimals() -> Weight;
fn update_network_gatekeeper() -> Weight;
fn update_incoming_network_share() -> Weight;
fn update_outgoing_network_share() -> Weight;
fn update_network_topic_name() -> Weight;
fn update_incoming_network_fee() -> Weight;
fn update_outgoing_network_fee() -> Weight;
fn remove_network() -> Weight;
}
impl WeightInfo for () {
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`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::NetworkCurves` (r:1 w:1)
/// 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`)
/// 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 {
/// 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 Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
/// The range of component `n` is `[1, 20]`.
/// The range of component `m` is `[1, 150]`.
fn register_network(_n: u32, _m: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `6`
// Estimated: `3471`
// Minimum execution time: 57_039_000 picoseconds.
Weight::from_parts(53_368_140, 0)
.saturating_add(Weight::from_parts(0, 3471))
// Standard Error: 874
.saturating_add(Weight::from_parts(8_884, 0).saturating_mul(i.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))
// Measured: `76`
// Estimated: `2551`
// Minimum execution time: 32_086 nanoseconds.
Weight::from_parts(33_092_855, 2551)
.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_selector() -> Weight {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: 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: `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().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_496 nanoseconds.
Weight::from_parts(35_728_230, 2616)
// Standard Error: 2_591
.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, 128]`.
fn update_network_endpoint(n: u32, ) -> Weight {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: 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: `166`
// Estimated: `3631`
// Minimum execution time: 42_265_000 picoseconds.
Weight::from_parts(43_357_887, 0)
.saturating_add(Weight::from_parts(0, 3632))
// Standard Error: 457
.saturating_add(Weight::from_parts(13_745, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_666 nanoseconds.
Weight::from_parts(35_959_961, 2616)
// Standard Error: 381
.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`)
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_finality_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `166`
// Estimated: `3631`
// Minimum execution time: 43_008_000 picoseconds.
Weight::from_parts(43_917_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_860 nanoseconds.
Weight::from_parts(34_995_000, 2616)
.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 {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_release_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 43_104_000 picoseconds.
Weight::from_parts(43_981_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_860 nanoseconds.
Weight::from_parts(34_995_000, 2616)
.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_deviation() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 41_734_000 picoseconds.
Weight::from_parts(42_645_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.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 Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_type() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 42_080_000 picoseconds.
Weight::from_parts(42_404_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_976 nanoseconds.
Weight::from_parts(36_182_000, 2616)
.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`)
/// 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().writes(1))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_gatekeeper() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 42_643_000 picoseconds.
Weight::from_parts(43_190_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.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_share() -> Weight {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_topic_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 42_978_000 picoseconds.
Weight::from_parts(43_002_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.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_share() -> Weight {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_incoming_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `167`
// Estimated: `3632`
// Minimum execution time: 43_436_000 picoseconds.
Weight::from_parts(44_193_000, 0)
.saturating_add(Weight::from_parts(0, 3632))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.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`)
/// 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`)
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_outgoing_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn remove_network() -> Weight {
// Proof Size summary in bytes:
// Measured: `180`
// Estimated: `3645`
// Minimum execution time: 75_969_000 picoseconds.
Weight::from_parts(77_610_000, 0)
.saturating_add(Weight::from_parts(0, 3645))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(4))
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_336 nanoseconds.
Weight::from_parts(34_609_000, 2616)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
}
impl WeightInfo for () {
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
/// The range of component `n` is `[1, 20]`.
/// The range of component `m` is `[1, 150]`.
fn register_network(_n: u32, _m: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `76`
// Estimated: `2551`
// Minimum execution time: 32_086 nanoseconds.
Weight::from_parts(33_092_855, 2551)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: 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: `141`
// Estimated: `2616`
// Minimum execution time: 34_496 nanoseconds.
Weight::from_parts(35_728_230, 2616)
// Standard Error: 2_591
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: 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: `141`
// Estimated: `2616`
// Minimum execution time: 34_666 nanoseconds.
Weight::from_parts(35_959_961, 2616)
// Standard Error: 381
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_finality_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_860 nanoseconds.
Weight::from_parts(34_995_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_release_delay() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_860 nanoseconds.
Weight::from_parts(34_995_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_type() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_976 nanoseconds.
Weight::from_parts(36_182_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_gatekeeper() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_network_topic_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_incoming_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn update_outgoing_network_fee() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 34_768 nanoseconds.
Weight::from_parts(35_580_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
/// Storage: GhostNetworks Networks (r:1 w:1)
/// Proof Skipped: GhostNetworks Networks (max_values: None, max_size: None, mode: Measured)
fn remove_network() -> Weight {
// Proof Size summary in bytes:
// Measured: `141`
// Estimated: `2616`
// Minimum execution time: 33_336 nanoseconds.
Weight::from_parts(34_609_000, 2616)
.saturating_add(RocksDbWeight::get().reads(1))
.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()
}

12
pallets/slow-clap/Cargo.toml Normal file → Executable file
View File

@ -1,7 +1,7 @@
[package]
name = "ghost-slow-clap"
version = "0.4.29"
description = "DEPRECATED: Applause protocol for the EVM bridge."
version = "0.3.14"
description = "Applause protocol for the EVM bridge"
license.workspace = true
authors.workspace = true
edition.workspace = true
@ -27,14 +27,11 @@ sp-staking = { workspace = true }
sp-io = { workspace = true }
sp-std = { workspace = true }
pallet-balances = { workspace = true }
ghost-networks = { workspace = true }
ghost-traits = { workspace = true }
[dev-dependencies]
pallet-balances = { workspace = true }
pallet-session = { workspace = true }
pallet-staking = { workspace = true }
pallet-staking-reward-curve = { workspace = true }
pallet-session = { workspace = true, default-features = true }
[features]
default = ["std"]
@ -52,7 +49,6 @@ std = [
"sp-io/std",
"sp-std/std",
"pallet-session/std",
"pallet-staking/std",
"pallet-balances/std",
"ghost-networks/std",
]

View File

@ -4,6 +4,12 @@ use super::*;
use frame_benchmarking::v1::*;
use frame_system::RawOrigin;
use frame_support::traits::fungible::{Inspect, Mutate};
use crate::Pallet as SlowClap;
const MAX_CLAPS: u32 = 100;
const MAX_COMPANIONS: u32 = 20;
pub fn create_account<T: Config>() -> T::AccountId {
let account_bytes = Vec::from([1u8; 32]);
@ -11,135 +17,184 @@ pub fn create_account<T: Config>() -> T::AccountId {
.expect("32 bytes always construct an AccountId32")
}
pub fn prepare_evm_network<T: Config>(network_id: NetworkIdOf<T>) {
let network_data = NetworkData {
pub fn create_companions<T: Config>(
total: usize,
network_id: NetworkIdOf<T>,
user_account: T::AccountId,
fee: H256,
receiver: H160,
) -> Result<CompanionId, &'static str> {
T::NetworkDataHandler::register(network_id.into(), 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(),
default_endpoint:
"https://base-mainnet.core.chainstack.com/2fc1de7f08c0465f6a28e3c355e0cb14/".into(),
finality_delay: Some(0),
release_delay: Some(0),
network_type: Default::default(),
gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678".to_vec(),
incoming_fee: 0,
outgoing_fee: 0,
}).map_err(|_| BenchmarkError::Weightless)?;
let mut last_companion_id = 0;
for _ in 0..total {
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let balance = minimum_balance + minimum_balance;
let companion = Companion::<NetworkIdOf::<T>, BalanceOf::<T>> {
network_id: network_id.into(), receiver,
fee, amount: balance,
};
let _ = T::NetworkDataHandler::register(network_id, network_data.clone());
let _ = <<T as pallet::Config>::Currency>::mint_into(&user_account, balance);
let companion_id = SlowClap::<T>::current_companion_id();
let _ = SlowClap::<T>::propose_companion(
RawOrigin::Signed(user_account.clone()).into(),
network_id,
companion,
)?;
last_companion_id = companion_id;
}
Ok(last_companion_id)
}
pub fn create_claps<T: Config>(i: u32, j: u32) -> Result<
(
Vec<crate::Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>>,
<T::AuthorityId as RuntimeAppPublic>::Signature,
),
&'static str,
> {
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let amount = minimum_balance + minimum_balance;
let total_amount = amount.saturating_mul(j.into());
let network_id = NetworkIdOf::<T>::default();
let mut claps = Vec::new();
let mut companions = BTreeMap::new();
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>::put(bounded_authorities);
for index in 0..j {
companions.insert(
index.into(),
amount,
);
}
for _ in 0..i {
claps.push(Clap {
session_index: 1,
authority_index: 0,
network_id,
transaction_hash: H256::repeat_byte(1u8),
block_number: 69,
removed: true,
receiver: create_account::<T>(),
amount: total_amount,
companions: companions.clone(),
});
}
let authority_id = authorities
.get(0usize)
.expect("first authority should exist");
let encoded_claps = claps.encode();
let signature = authority_id.sign(&encoded_claps)
.ok_or("couldn't make signature")?;
Ok((claps, signature))
}
benchmarks! {
slow_clap {
let network_id = NetworkIdOf::<T>::default();
prepare_evm_network::<T>(network_id);
let k in 1 .. MAX_CLAPS;
let j in 1 .. MAX_COMPANIONS;
let receiver = H160::repeat_byte(69u8);
let fee = H256::repeat_byte(0u8);
let user_account: T::AccountId = whitelisted_caller();
let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
let (claps, signature) = create_claps::<T>(k, j)?;
let _ = create_companions::<T>(j as usize, network_id, user_account, fee, receiver)?;
}: _(RawOrigin::None, claps, signature)
verify {
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let receiver = create_account::<T>();
let amount = minimum_balance + minimum_balance;
let total_amount = (minimum_balance + minimum_balance).saturating_mul(j.into());
}
let session_index = T::ValidatorSet::session_index();
let transaction_hash = H256::repeat_byte(1u8);
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 clap = Clap {
session_index,
authority_index,
transaction_hash,
block_number: 69,
removed: false,
network_id,
receiver: receiver.clone(),
amount,
propose_companion {
let receiver = H160::repeat_byte(69u8);
let fee = H256::repeat_byte(0u8);
let user_account: T::AccountId = whitelisted_caller();
let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
// T::NetworkDataHandler::register(network_id.into(), NetworkData {
// chain_name: "Ethereum".into(),
// // https://nd-422-757-666.p2pify.com/0a9d79d93fb2f4a4b1e04695da2b77a7/
// default_endpoint:
// "https://base-mainnet.core.chainstack.com/2fc1de7f08c0465f6a28e3c355e0cb14/".into(),
// finality_delay: Some(50),
// release_delay: Some(100),
// network_type: Default::default(),
// gatekeeper: b"0x1234567891234567891234567891234567891234".to_vec(),
// topic_name: b"0x12345678912345678912345678912345678912345678912345678912345678".to_vec(),
// incoming_fee: 0,
// outgoing_fee: 0,
// }).map_err(|_| BenchmarkError::Weightless)?;
let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
let companion_id = SlowClap::<T>::current_companion_id();
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
let balance = minimum_balance + minimum_balance;
let companion = Companion::<NetworkIdOf::<T>, BalanceOf::<T>> {
network_id: network_id.into(), receiver,
fee, amount: balance,
};
let args_hash = Pallet::<T>::generate_unique_hash(&clap);
let authority_id = authorities
.get(authority_index as usize)
.expect("first authority should exist");
let signature = authority_id.sign(&clap.encode())
.ok_or("couldn't make signature")?;
let empty_bitmap = BitMap::new(1);
DisabledAuthorityIndexes::<T>::insert(session_index, empty_bitmap);
assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_none(), true);
}: _(RawOrigin::None, clap, signature)
let _ = <<T as pallet::Config>::Currency>::mint_into(&user_account, balance);
assert_eq!(SlowClap::<T>::current_companion_id(), companion_id);
}: _(RawOrigin::Signed(user_account), network_id.into(), companion)
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!(SlowClap::<T>::current_companion_id(), companion_id + 1);
}
commit_block {
let session_index = T::ValidatorSet::session_index();
let network_id = NetworkIdOf::<T>::default();
prepare_evm_network::<T>(network_id);
release_companion {
let receiver = H160::repeat_byte(69u8);
let fee = H256::repeat_byte(0u8);
let user_account: T::AccountId = whitelisted_caller();
let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
assert_eq!(SlowClap::<T>::release_blocks(companion_id), BlockNumberFor::<T>::default());
}: _(RawOrigin::Signed(user_account), network_id.into(), companion_id)
verify {
assert_ne!(SlowClap::<T>::release_blocks(companion_id), BlockNumberFor::<T>::default());
}
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,
kill_companion {
let receiver = H160::repeat_byte(69u8);
let fee = H256::repeat_byte(0u8);
let user_account: T::AccountId = whitelisted_caller();
let network_id = <<T as pallet::Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId::default();
let companion_id = create_companions::<T>(1, network_id, user_account.clone(), fee, receiver)?;
SlowClap::<T>::release_companion(
RawOrigin::Signed(user_account.clone()).into(),
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)
companion_id,
)?;
let block_shift =
<<T as pallet::Config>::NetworkDataHandler as NetworkDataInspectHandler<NetworkData>>::get(&network_id)
.unwrap()
.release_delay
.unwrap();
frame_system::Pallet::<T>::set_block_number((block_shift + 1).saturated_into());
}: _(RawOrigin::Signed(user_account), network_id.into(), companion_id)
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
);
assert_eq!(SlowClap::<T>::companions(network_id, companion_id), None);
assert_eq!(SlowClap::<T>::companion_details(companion_id), None);
assert_eq!(SlowClap::<T>::current_companion_id(), companion_id + 1);
}
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)
}
}

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