Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 510e96a7d2 | |||
| c056b96545 |
1252
Cargo.lock
generated
1252
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@ -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
0
LICENSE.md
Normal file → Executable file
132
README.md
Normal file → Executable file
132
README.md
Normal file → Executable 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:
|
||||
|
||||

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

|
||||
|
||||
|
||||
|
||||
# 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)
|
||||
```
|
||||

|
||||
|
||||
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)
|
||||
```
|
||||

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

|
||||
|
||||
Display the session-key//babe on the screen by using cat command:
|
||||
```bash
|
||||
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//babe"
|
||||
```
|
||||

|
||||
|
||||
Display the session-key//slow on the screen by using cat command:
|
||||
```bash
|
||||
./target/release/ghost key inspect "$(cat /etc/ghost/session-key)//slow"
|
||||
```
|
||||

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

|
||||
|
||||
## Build and start the ghost-node
|
||||
|
||||
@ -346,8 +426,9 @@ sha256sum /etc/ghost/casper.json
|
||||
|
||||
You should see:
|
||||
```
|
||||
4645c7d39a64424d823f659d408070f8c90426117e7af147b0360697b2db7f4f /etc/ghost/casper.json
|
||||
6c1bab2e9c04043814b5e5e72984b00ac60150bd48cb16068495f1b49fbc5008
|
||||
```
|
||||

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

|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
||||
@ -413,10 +497,21 @@ Type `y` and press _Enter_ for create dedicated user for running `ghost-node`:
|
||||
|
||||

|
||||
|
||||
**DO NOT start and DO NOT enable** `ghost-node.service` (press _Enter_):
|
||||
**DO NOT start and enable** `ghost-node.service` press _Enter_:
|
||||
|
||||

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

|
||||
@ -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
0
cli/Cargo.toml
Normal file → Executable file
0
cli/build.rs
Normal file → Executable file
0
cli/build.rs
Normal file → Executable file
1
cli/src/cli.rs
Normal file → Executable file
1
cli/src/cli.rs
Normal file → Executable 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
176
cli/src/command.rs
Normal file → Executable 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
0
cli/src/error.rs
Normal file → Executable file
4
cli/src/lib.rs
Normal file → Executable file
4
cli/src/lib.rs
Normal file → Executable 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::*;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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},
|
||||
};
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pub mod commands;
|
||||
pub mod params;
|
||||
pub mod commands;
|
||||
|
||||
pub use commands::KeySubcommand;
|
||||
pub use commands::VanityCmd;
|
||||
|
||||
@ -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
0
core-primitives/Cargo.toml
Normal file → Executable file
0
core-primitives/src/lib.rs
Normal file → Executable file
0
core-primitives/src/lib.rs
Normal file → Executable file
0
file_header.txt
Normal file → Executable file
0
file_header.txt
Normal file → Executable file
BIN
images/img_29.png
Normal file
BIN
images/img_29.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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",
|
||||
]
|
||||
56
pallets/claims/src/benchmarking.rs
Normal file
56
pallets/claims/src/benchmarking.rs
Normal 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(ðereum_secret_key);
|
||||
|
||||
let balance = CurrencyOf::<T, I>::minimum_balance();
|
||||
let pseudo_account: T::AccountId = Pallet::<T, I>::into_account_id(eth_address).unwrap();
|
||||
let _ = CurrencyOf::<T, I>::deposit_creating(&pseudo_account, balance);
|
||||
Total::<T, I>::put(balance);
|
||||
|
||||
let pseudo_rank = 5u16;
|
||||
let _ = Club::<T, I>::do_add_member_to_rank(
|
||||
pseudo_account.clone(),
|
||||
pseudo_rank,
|
||||
false,
|
||||
);
|
||||
|
||||
let user_account: T::AccountId = account("user", i, 0);
|
||||
let signature = crate::secp_utils::sig::<T, I>(ðereum_secret_key, &user_account.encode());
|
||||
|
||||
let prev_balance = CurrencyOf::<T, I>::free_balance(&user_account);
|
||||
let prev_rank = Club::<T, I>::rank_of(&user_account);
|
||||
|
||||
#[extrinsic_call]
|
||||
claim(RawOrigin::Signed(user_account.clone()), eth_address, signature);
|
||||
|
||||
assert_eq!(CurrencyOf::<T, I>::free_balance(&user_account), prev_balance + balance);
|
||||
assert_eq!(CurrencyOf::<T, I>::free_balance(&pseudo_account), balance - balance);
|
||||
|
||||
let rank = match prev_rank {
|
||||
Some(current_rank) if pseudo_rank <= current_rank => Some(current_rank),
|
||||
_ => Some(pseudo_rank),
|
||||
};
|
||||
assert_eq!(Club::<T, I>::rank_of(&user_account), rank);
|
||||
assert_eq!(Club::<T, I>::rank_of(&pseudo_account), None);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Test,
|
||||
);
|
||||
}
|
||||
313
pallets/claims/src/lib.rs
Normal file
313
pallets/claims/src/lib.rs
Normal 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(
|
||||
ðereum_signature,
|
||||
&data,
|
||||
).ok_or(Error::<T, I>::InvalidEthereumSignature)?;
|
||||
|
||||
ensure!(recovered_address == ethereum_address,
|
||||
Error::<T, I>::InvalidEthereumAddress);
|
||||
|
||||
Self::do_claim(who, ethereum_address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(data.len() * 2);
|
||||
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
|
||||
for &b in data.iter() {
|
||||
push_nibble(b / 16);
|
||||
push_nibble(b % 16);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn ethereum_signable_message(what: &[u8]) -> Vec<u8> {
|
||||
let prefix = T::Prefix::get();
|
||||
let mut l = prefix.len() + what.len();
|
||||
let mut rev = Vec::new();
|
||||
while l > 0 {
|
||||
rev.push(b'0' + (l % 10) as u8);
|
||||
l /= 10;
|
||||
}
|
||||
let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
|
||||
v.extend(rev.into_iter().rev());
|
||||
v.extend_from_slice(prefix);
|
||||
v.extend_from_slice(what);
|
||||
v
|
||||
}
|
||||
|
||||
fn recover_ethereum_address(s: &EcdsaSignature, what: &[u8]) -> Option<EthereumAddress> {
|
||||
let msg = keccak_256(&Self::ethereum_signable_message(what));
|
||||
let mut res = EthereumAddress::default();
|
||||
res.0
|
||||
.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn into_account_id(address: EthereumAddress) -> Result<T::AccountId, codec::Error> {
|
||||
let mut data = [0u8; 32];
|
||||
data[0..4].copy_from_slice(b"evm:");
|
||||
data[4..24].copy_from_slice(&address.0[..]);
|
||||
|
||||
let hash = sp_core::keccak_256(&data);
|
||||
T::AccountId::decode(&mut &hash[..])
|
||||
}
|
||||
|
||||
fn do_claim(receiver: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult {
|
||||
let donor = Self::into_account_id(ethereum_address).ok()
|
||||
.ok_or(Error::<T, I>::AddressDecodingFailed)?;
|
||||
|
||||
let balance_due = CurrencyOf::<T, I>::free_balance(&donor);
|
||||
ensure!(balance_due >= CurrencyOf::<T, I>::minimum_balance(),
|
||||
Error::<T, I>::NoBalanceToClaim);
|
||||
|
||||
let new_total = Total::<T, I>::get()
|
||||
.checked_sub(&balance_due)
|
||||
.ok_or(Error::<T, I>::PotUnderflow)?;
|
||||
|
||||
CurrencyOf::<T, I>::transfer(
|
||||
&donor,
|
||||
&receiver,
|
||||
balance_due,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)?;
|
||||
|
||||
let max_amount = T::MaximumWithdrawAmount::get();
|
||||
if balance_due > max_amount {
|
||||
let vesting_balance = balance_due
|
||||
.checked_sub(&max_amount)
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
let vesting_blocks = T::VestingBlocks::get();
|
||||
let per_block_balance = vesting_balance
|
||||
.checked_div(&vesting_blocks.into())
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
T::VestingSchedule::add_vesting_schedule(
|
||||
&receiver,
|
||||
vesting_balance,
|
||||
per_block_balance,
|
||||
T::BlockNumberProvider::current_block_number(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let rank = if let Some(rank) = <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::rank_of(&donor) {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_remove_member_from_rank(&donor, rank)?;
|
||||
let new_rank = match <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::rank_of(&receiver) {
|
||||
Some(current_rank) if current_rank >= rank => current_rank,
|
||||
Some(current_rank) if current_rank < rank => {
|
||||
for _ in 0..rank - current_rank {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_promote_member(receiver.clone(), None, false)?;
|
||||
}
|
||||
rank
|
||||
},
|
||||
_ => {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(receiver.clone(), rank, false)?;
|
||||
rank
|
||||
},
|
||||
};
|
||||
<T as pallet::Config<I>>::MemberSwappedHandler::swapped(&donor, &receiver, new_rank);
|
||||
Some(new_rank)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Total::<T, I>::put(new_total);
|
||||
Self::deposit_event(Event::<T, I>::Claimed {
|
||||
receiver,
|
||||
donor,
|
||||
amount: balance_due,
|
||||
rank,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
221
pallets/claims/src/mock.rs
Normal file
221
pallets/claims/src/mock.rs
Normal 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
|
||||
}
|
||||
37
pallets/claims/src/secp_utils.rs
Normal file
37
pallets/claims/src/secp_utils.rs
Normal 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
274
pallets/claims/src/tests.rs
Normal 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,
|
||||
}));
|
||||
})
|
||||
}
|
||||
9
pallets/claims/src/weights.rs
Normal file
9
pallets/claims/src/weights.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn claim() -> Weight;
|
||||
}
|
||||
|
||||
impl WeightInfo for () {
|
||||
fn claim() -> Weight { Weight::zero() }
|
||||
}
|
||||
@ -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",
|
||||
]
|
||||
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-exodus-runtime-api"
|
||||
version = "0.0.0"
|
||||
description = "RPC runtime API for ghost EXODUS pallet"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true, features = ["derive"] }
|
||||
sp-api = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "sp-api/std"]
|
||||
@ -1,7 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
pub trait ExodusApi<NetworkId> {
|
||||
fn verifying_key(network_id: NetworkId) -> Vec<u8>;
|
||||
}
|
||||
}
|
||||
@ -1,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
|
||||
);
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
use ghost_helpers::networks::NetworkCurve;
|
||||
|
||||
use crate::AuthIndex;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExodusError {
|
||||
InvalidParticipantId,
|
||||
DeserializationError,
|
||||
SerializationError,
|
||||
InvalidMinSigners(AuthIndex, AuthIndex),
|
||||
InvalidMaxSigners(AuthIndex),
|
||||
DKGNotSupported(NetworkCurve),
|
||||
IncorrectNumberOfCoefficients,
|
||||
IncorrectNumberOfCommitments,
|
||||
IncorrectNumberOfPackages,
|
||||
IncorrectPackage,
|
||||
PackageNotFound,
|
||||
InvalidSecretShare,
|
||||
InvalidProofOfKnowledge,
|
||||
MissingCommitment,
|
||||
IncorrectCommitment,
|
||||
UnknownIdentifier,
|
||||
IdentityCommitment,
|
||||
DuplicatedIdentifier,
|
||||
IncorrectNumberOfIdentifiers,
|
||||
InvalidSignature,
|
||||
InvalidSignatureShare,
|
||||
InvalidNonceCommitments,
|
||||
EncryptionFailed,
|
||||
DecryptionFailed,
|
||||
HKDFFailed,
|
||||
InvalidNonceLength,
|
||||
MalformedSigningKey,
|
||||
NoStoredNetworks,
|
||||
OffchainTimeoutPeriod,
|
||||
BadSignature(AuthIndex),
|
||||
TransactionSubmissionFailed(AuthIndex),
|
||||
UnknownReceiverIndex(AuthIndex),
|
||||
CouldNotConstructMessage(AuthIndex),
|
||||
NothingToWrite,
|
||||
UnknownPrefix,
|
||||
UnexpectedRound,
|
||||
InvalidMerkleProof,
|
||||
InvalidIdentityElement,
|
||||
NoActiveAuthorities,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ExodusError {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match *self {
|
||||
ExodusError::InvalidParticipantId => write!(fmt, "Participant index could not be parsed to identifier."),
|
||||
ExodusError::DeserializationError => write!(fmt, "Error during deserializing value."),
|
||||
ExodusError::SerializationError => write!(fmt, "Error serializing value."),
|
||||
ExodusError::InvalidMinSigners(min, max) => write!(fmt, "Number of min_signers ({min}) must be at least 2 and not larger than max_signers ({max})."),
|
||||
ExodusError::InvalidMaxSigners(max_signers) => write!(fmt, "Number of max_signers must be at least 2, {max_signers} given."),
|
||||
ExodusError::DKGNotSupported(ref curve) => write!(fmt, "The ciphersuite {curve:?} does not support DKG."),
|
||||
ExodusError::IncorrectNumberOfCoefficients => write!(fmt, "Incorrect number of coefficients; should be equal to min signers."),
|
||||
ExodusError::IncorrectNumberOfCommitments => write!(fmt, "Incorrect number of commitments; should be equal to min signers."),
|
||||
ExodusError::InvalidNonceCommitments => write!(fmt, "Hiding nonce commitments indexes should match binding nonce commitments."),
|
||||
ExodusError::InvalidProofOfKnowledge => write!(fmt, "The proof of knowledge is not valid."),
|
||||
ExodusError::IncorrectNumberOfPackages => write!(fmt, "Incorrect number of packages; should be equal to max signers."),
|
||||
ExodusError::IncorrectPackage => write!(fmt, "The incorrect package was specified."),
|
||||
ExodusError::PackageNotFound => write!(fmt, "Round 1 package not found for Round 2 participant."),
|
||||
ExodusError::InvalidSecretShare => write!(fmt, "Invalid secret share."),
|
||||
ExodusError::MissingCommitment => write!(fmt, "The Signing Package must contain the participant's Commitments."),
|
||||
ExodusError::IncorrectCommitment => write!(fmt, "The participant's commitment is incorrect."),
|
||||
ExodusError::UnknownIdentifier => write!(fmt, "Unknown identifier."),
|
||||
ExodusError::IdentityCommitment => write!(fmt, "Commitment equals the identity."),
|
||||
ExodusError::DuplicatedIdentifier => write!(fmt, "Duplicated identifier."),
|
||||
ExodusError::IncorrectNumberOfIdentifiers => write!(fmt, "Incorrect number of identifiers."),
|
||||
ExodusError::InvalidSignature => write!(fmt, "Invalid signature."),
|
||||
ExodusError::InvalidSignatureShare => write!(fmt, "Invalid signature share."),
|
||||
ExodusError::EncryptionFailed => write!(fmt, "Encryption failed."),
|
||||
ExodusError::DecryptionFailed => write!(fmt, "Decryption failed."),
|
||||
ExodusError::HKDFFailed => write!(fmt, "HKDF failed."),
|
||||
ExodusError::InvalidNonceLength => write!(fmt, "Encryption nonce has invalid length."),
|
||||
ExodusError::MalformedSigningKey => write!(fmt, "Malformed signing key encoding."),
|
||||
ExodusError::NoStoredNetworks => write!(fmt, "No stored networks to be worked on."),
|
||||
ExodusError::OffchainTimeoutPeriod => write!(fmt, "Offchain request should be in-flight."),
|
||||
ExodusError::BadSignature(auth_index) => write!(fmt, "Signature could not be created from {auth_index}."),
|
||||
ExodusError::TransactionSubmissionFailed(auth_index) => write!(fmt, "Could not submit transaction from {auth_index}."),
|
||||
ExodusError::UnknownReceiverIndex(receiver_index) => write!(fmt, "Unknown receiver index {receiver_index} found."),
|
||||
ExodusError::CouldNotConstructMessage(auth_index) => write!(fmt, "Could not construct message during OCW from {auth_index}."),
|
||||
ExodusError::NothingToWrite => write!(fmt, "Nothing to write into local storage."),
|
||||
ExodusError::UnknownPrefix => write!(fmt, "Unknown prefix used for local storage."),
|
||||
ExodusError::UnexpectedRound => write!(fmt, "Unexpected round provided."),
|
||||
ExodusError::InvalidMerkleProof => write!(fmt, "Invalid merkle proof provided."),
|
||||
ExodusError::InvalidIdentityElement => write!(fmt, "Invalid identity element provided."),
|
||||
ExodusError::NoActiveAuthorities => write!(fmt, "No active authorities for signing exodus transactions."),
|
||||
ExodusError::Unknown => write!(fmt, "Unknown error."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
use crate::{AuthIndex, ExodusError, NetworkCurve};
|
||||
|
||||
use ghost_traits::exodus::IdentifierConverter;
|
||||
|
||||
impl IdentifierConverter<AuthIndex, ExodusError> for NetworkCurve {
|
||||
fn convert_identifier_to_index(
|
||||
identifier_bytes: &[u8],
|
||||
) -> Result<AuthIndex, ExodusError> {
|
||||
const SIZE: usize = core::mem::size_of::<AuthIndex>();
|
||||
|
||||
let start = identifier_bytes.len().checked_sub(SIZE)
|
||||
.ok_or(ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let bytes_array = identifier_bytes.get(start..)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.ok_or(ExodusError::InvalidParticipantId)?;
|
||||
|
||||
AuthIndex::from_be_bytes(bytes_array)
|
||||
.checked_sub(1)
|
||||
.ok_or(ExodusError::InvalidParticipantId)
|
||||
}
|
||||
|
||||
fn non_zero_index(index: AuthIndex) -> Result<AuthIndex, ExodusError> {
|
||||
index.checked_add(1).ok_or(ExodusError::InvalidParticipantId)
|
||||
}
|
||||
}
|
||||
@ -1,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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
use sp_std::{
|
||||
vec::Vec,
|
||||
result::Result,
|
||||
marker::PhantomData,
|
||||
};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use ghost_traits::exodus::EllipticCurveDiffieHellman;
|
||||
|
||||
use hkdf::Hkdf as KeyDerivationFunction;
|
||||
use sha2::Sha256 as Hashing;
|
||||
|
||||
use chacha20poly1305::{
|
||||
aead::{self, Aead, KeyInit},
|
||||
Key as EncryptionKey,
|
||||
Nonce as EncryptionNonce,
|
||||
ChaCha20Poly1305 as EncryptionCipher,
|
||||
};
|
||||
|
||||
use crate::pallet::Config;
|
||||
use crate::{AuthIndex, EncryptionData, ExodusError, NetworkCurve, DkgIndex};
|
||||
|
||||
impl<T: Config> EllipticCurveDiffieHellman<AuthIndex, EncryptionCipher, EncryptionData<T>, DkgIndex, ExodusError> for NetworkCurve {
|
||||
fn ecdh_encrypt_package<R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
aad: &[u8],
|
||||
msg: &[u8],
|
||||
mut rng: R,
|
||||
) -> Result<EncryptionData<T>, ExodusError> {
|
||||
let payload = aead::Payload { msg, aad };
|
||||
|
||||
let mut nonce_bytes = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = EncryptionNonce::from_slice(&nonce_bytes);
|
||||
|
||||
let ciphertext = cipher.encrypt(&nonce, payload)
|
||||
.map_err(|_| ExodusError::EncryptionFailed)?;
|
||||
|
||||
EncryptionData::try_new(ciphertext, nonce.as_slice())
|
||||
}
|
||||
|
||||
fn ecdh_decrypt_package(
|
||||
&self,
|
||||
cipher: &EncryptionCipher,
|
||||
encrypted_data: &EncryptionData<T>,
|
||||
aad: &[u8],
|
||||
) -> Result<Vec<u8>, ExodusError> {
|
||||
let nonce = EncryptionNonce::from_iter(
|
||||
encrypted_data.nonce.iter().cloned()
|
||||
);
|
||||
let payload = aead::Payload { msg: &encrypted_data.ciphertext, aad };
|
||||
cipher.decrypt(&nonce, payload).map_err(|_| ExodusError::DecryptionFailed)
|
||||
}
|
||||
|
||||
fn ecdh_prepare_additional_info(
|
||||
&self,
|
||||
sender_index: AuthIndex,
|
||||
receiver_index: AuthIndex,
|
||||
dkg_index: DkgIndex,
|
||||
_marker: PhantomData<EncryptionData<T>>,
|
||||
) -> Vec<u8> {
|
||||
let mut additional_info = match self {
|
||||
NetworkCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(),
|
||||
NetworkCurve::Ed25519 => b"EXODUS-ED25519-DKG-V1".to_vec(),
|
||||
};
|
||||
|
||||
additional_info.extend_from_slice(&sender_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&receiver_index.to_be_bytes());
|
||||
additional_info.extend_from_slice(&dkg_index.to_be_bytes());
|
||||
|
||||
additional_info
|
||||
}
|
||||
|
||||
fn ecdh_get_cipher_from_keys(
|
||||
&self,
|
||||
public: &[u8],
|
||||
secret: &[u8],
|
||||
additional_info: &[u8],
|
||||
_marker: PhantomData<EncryptionData<T>>,
|
||||
) -> Result<EncryptionCipher, ExodusError> {
|
||||
with_ciphersuite!(self, |f| {
|
||||
let secret_package = f::keys::dkg::round1::SecretPackage::deserialize(secret)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
let public_package = f::keys::dkg::round1::Package::deserialize(public)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let coefficients = secret_package.coefficients();
|
||||
let local_sk = coefficients.first()
|
||||
.ok_or(ExodusError::IncorrectNumberOfCoefficients)?;
|
||||
let remote_pk = public_package.commitment().coefficients().first()
|
||||
.ok_or(ExodusError::MissingCommitment)?.value();
|
||||
|
||||
let shared_secret_bytes = f::VerifyingKey::new(remote_pk * local_sk)
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let local_pk: f::VerifyingKey = f::SigningKey::from_scalar(*local_sk)
|
||||
.map_err(|_| ExodusError::MalformedSigningKey)?
|
||||
.into();
|
||||
|
||||
let local_pk_bytes = local_pk.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
let remote_pk_bytes = f::VerifyingKey::new(remote_pk)
|
||||
.serialize()
|
||||
.map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let mut keys = [local_pk_bytes, remote_pk_bytes];
|
||||
keys.sort();
|
||||
|
||||
let salt = keys.concat();
|
||||
let hk = KeyDerivationFunction::<Hashing>::new(Some(&salt), &shared_secret_bytes);
|
||||
|
||||
let mut encryption_key = EncryptionKey::default();
|
||||
hk.expand(additional_info, &mut encryption_key)
|
||||
.map_err(|_| ExodusError::HKDFFailed)?;
|
||||
|
||||
let cipher = EncryptionCipher::new_from_slice(&encryption_key)
|
||||
.expect("could not happen; length is already correct qed");
|
||||
|
||||
Ok(cipher)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result};
|
||||
|
||||
use frost_core::{Ciphersuite, Group, Identifier};
|
||||
use ghost_helpers::SubstrateBlake2Hasher;
|
||||
use ghost_traits::{
|
||||
exodus::{MerkleTreeBuilder, IdentifierConverter},
|
||||
hashing::GhostHasher,
|
||||
};
|
||||
|
||||
use crate::{AuthIndex, ExodusError, NetworkCurve};
|
||||
|
||||
impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
|
||||
type Hash = <SubstrateBlake2Hasher as GhostHasher>::Hash;
|
||||
|
||||
fn build_merkle_tree<C, V, F>(
|
||||
values: &BTreeMap<Identifier<C>, V>,
|
||||
serialize_fn: F,
|
||||
) -> Result<Vec<Self::Hash>, ExodusError>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
|
||||
{
|
||||
use ghost_helpers::merkle_tree::generate_tree;
|
||||
|
||||
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
|
||||
|
||||
let max_index = values.keys()
|
||||
.next_back()
|
||||
.and_then(|id| {
|
||||
let id_scalar = id.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
|
||||
})
|
||||
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
|
||||
|
||||
generate_tree::<SubstrateBlake2Hasher, _, _, _, _>(
|
||||
max_index,
|
||||
values.iter(),
|
||||
|(identifier, value)| {
|
||||
let id_scalar = identifier.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
|
||||
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
|
||||
let value_bytes = serialize_fn(value).map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
// NOTE: revisit
|
||||
// Is it possible to use [u8; CONSTANT] where constant is
|
||||
// purely dependant on provided generic.
|
||||
let mut preimage = Vec::<u8>::with_capacity(id_bytes.as_ref().len() + value_bytes.len());
|
||||
preimage.extend_from_slice(id_bytes.as_ref());
|
||||
preimage.extend_from_slice(value_bytes.as_ref());
|
||||
|
||||
Ok((index as usize, preimage))
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_merkle_proof_from_tree<C, V, F>(
|
||||
values: &BTreeMap<Identifier<C>, V>,
|
||||
merkle_tree: &[Self::Hash],
|
||||
authority_index: AuthIndex,
|
||||
serialize_fn: F,
|
||||
) -> Result<(Vec<u8>, Vec<Self::Hash>), ExodusError>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
|
||||
{
|
||||
use ghost_helpers::merkle_tree::generate_proof;
|
||||
|
||||
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
|
||||
|
||||
let non_zero_index = Self::non_zero_index(authority_index)?;
|
||||
let identifier = Identifier::try_from(non_zero_index)
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let value = values.get(&identifier).ok_or(ExodusError::InvalidParticipantId)?;
|
||||
let value_bytes = serialize_fn(&value).map_err(|_| ExodusError::SerializationError)?;
|
||||
|
||||
let max_index = values.keys()
|
||||
.next_back()
|
||||
.and_then(|id| {
|
||||
let id_scalar = id.to_scalar();
|
||||
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
|
||||
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
|
||||
})
|
||||
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
|
||||
|
||||
let proof = generate_proof::<SubstrateBlake2Hasher, _>(
|
||||
merkle_tree,
|
||||
max_index,
|
||||
authority_index,
|
||||
);
|
||||
|
||||
Ok((value_bytes, proof))
|
||||
}
|
||||
|
||||
fn verify_merkle_proof(
|
||||
&self,
|
||||
values: &[u8],
|
||||
merkle_proof: &[Self::Hash],
|
||||
merkle_root: Self::Hash,
|
||||
authority_index: AuthIndex,
|
||||
) -> Result<(), ExodusError> {
|
||||
use ghost_helpers::merkle_tree::verify_tree_proof;
|
||||
|
||||
let preimage = with_ciphersuite!(self, |f| {
|
||||
let non_zero_index = Self::non_zero_index(authority_index)?;
|
||||
let identifier = f::Identifier::try_from(non_zero_index)
|
||||
.map_err(|_| ExodusError::InvalidParticipantId)?;
|
||||
|
||||
let mut preimage = identifier.serialize();
|
||||
preimage.extend_from_slice(values);
|
||||
|
||||
Ok(preimage)
|
||||
})?;
|
||||
|
||||
let is_verified = verify_tree_proof::<SubstrateBlake2Hasher, _>(
|
||||
&preimage,
|
||||
merkle_proof,
|
||||
merkle_root,
|
||||
authority_index,
|
||||
);
|
||||
|
||||
is_verified.then(|| ()).ok_or(ExodusError::InvalidMerkleProof)
|
||||
}
|
||||
}
|
||||
@ -1,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
@ -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
@ -1,24 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
|
||||
use crate::{ExodusHash, ELEMENT_MAX_BYTES};
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ConsensusState<Bitmap> {
|
||||
pub element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
pub participants: Bitmap,
|
||||
pub merkle_root: ExodusHash,
|
||||
}
|
||||
|
||||
impl<Bitmap> ConsensusState<Bitmap> {
|
||||
pub fn new(
|
||||
element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
participants: Bitmap,
|
||||
merkle_root: ExodusHash,
|
||||
) -> Self {
|
||||
Self { element_bytes, participants, merkle_root }
|
||||
}
|
||||
}
|
||||
@ -1,295 +0,0 @@
|
||||
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use strum::EnumCount;
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
|
||||
use ghost_helpers::networks::NetworkType;
|
||||
use ghost_traits::bounded_bitmap::{
|
||||
BoundedBitmapGenerator, BoundedBitmapReader, BoundedBitmapWriter,
|
||||
};
|
||||
|
||||
use crate::{ELEMENT_MAX_BYTES, ExodusSession, DkgIndex};
|
||||
|
||||
type RotationSessions = BoundedVec<RotationSession, ConstU32<{ NetworkType::COUNT as u32 }>>;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct ReadyDkgAuthorities<AuthoritiesBitmap> {
|
||||
authorities_indexes: AuthoritiesBitmap,
|
||||
dkg_index: DkgIndex,
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap> ReadyDkgAuthorities<AuthoritiesBitmap>
|
||||
where
|
||||
AuthoritiesBitmap: BoundedBitmapReader,
|
||||
{
|
||||
pub fn contains_index<N>(&self, index: N) -> bool
|
||||
where N: UniqueSaturatedInto<usize>,
|
||||
{
|
||||
self.authorities_indexes.contains(index)
|
||||
}
|
||||
|
||||
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
|
||||
&self.authorities_indexes
|
||||
}
|
||||
|
||||
pub fn get_dkg_index(&self) -> DkgIndex {
|
||||
self.dkg_index
|
||||
}
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap, Block>
|
||||
From<&PendingDkgAuthorities<AuthoritiesBitmap, Block>>
|
||||
for ReadyDkgAuthorities<AuthoritiesBitmap>
|
||||
where
|
||||
Block: Copy,
|
||||
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
|
||||
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
|
||||
{
|
||||
fn from(pending: &PendingDkgAuthorities<AuthoritiesBitmap, Block>) -> Self {
|
||||
Self {
|
||||
authorities_indexes: pending.get_indexes().clone(),
|
||||
dkg_index: pending.get_dkg_index(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct RotationSession {
|
||||
pub exodus_session: ExodusSession,
|
||||
pub network_type: NetworkType
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct PendingDkgAuthorities<AuthoritiesBitmap, Block> {
|
||||
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
authorities_indexes: AuthoritiesBitmap,
|
||||
rotation_sessions: RotationSessions,
|
||||
dkg_state: DkgState<Block>,
|
||||
}
|
||||
|
||||
impl<AuthoritiesBitmap, Block> PendingDkgAuthorities<AuthoritiesBitmap, Block>
|
||||
where
|
||||
Block: Copy,
|
||||
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
|
||||
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
|
||||
{
|
||||
pub fn new_dkg(&mut self, authorities_len: usize) {
|
||||
self.authorities_indexes = AuthoritiesBitmap::empty(authorities_len);
|
||||
self.dkg_state.restart();
|
||||
self.rotation_sessions = Default::default();
|
||||
}
|
||||
|
||||
pub fn set_block(&mut self, block: Block) {
|
||||
self.dkg_state.set_block(block);
|
||||
}
|
||||
|
||||
pub fn set_verifying_key(
|
||||
&mut self,
|
||||
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
||||
) {
|
||||
self.verifying_key = verifying_key;
|
||||
}
|
||||
|
||||
pub fn nullify_indexes(&mut self) {
|
||||
self.verifying_key = Default::default();
|
||||
self.authorities_indexes.nullify();
|
||||
}
|
||||
|
||||
pub fn restart_dkg(&mut self) {
|
||||
self.verifying_key = Default::default();
|
||||
self.authorities_indexes.nullify();
|
||||
self.dkg_state.restart();
|
||||
self.rotation_sessions = Default::default();
|
||||
}
|
||||
|
||||
pub fn next_phase(&mut self) {
|
||||
self.dkg_state.next_phase()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersect_indexes(&mut self, mask: &AuthoritiesBitmap) {
|
||||
self.authorities_indexes &= mask;
|
||||
}
|
||||
|
||||
pub fn is_dkg_pending(&self) -> bool {
|
||||
self.dkg_state.is_dkg_pending()
|
||||
}
|
||||
|
||||
pub fn is_dkg_vacant(&self) -> bool {
|
||||
self.dkg_state.is_dkg_vacant()
|
||||
}
|
||||
|
||||
pub fn get_dkg_index(&self) -> DkgIndex {
|
||||
self.dkg_state.get_dkg_index()
|
||||
}
|
||||
|
||||
pub fn get_block(&self) -> Block {
|
||||
self.dkg_state.get_block()
|
||||
}
|
||||
|
||||
pub fn get_phase(&self) -> DkgPhase {
|
||||
self.dkg_state.get_phase()
|
||||
}
|
||||
|
||||
pub fn get_verifying_key(
|
||||
&self
|
||||
) -> BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>> {
|
||||
self.verifying_key.clone()
|
||||
}
|
||||
|
||||
pub fn is_zero_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round0
|
||||
}
|
||||
|
||||
pub fn is_first_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round1
|
||||
}
|
||||
|
||||
pub fn is_second_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round2
|
||||
}
|
||||
|
||||
pub fn is_third_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round3
|
||||
}
|
||||
|
||||
pub fn is_fourth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round4
|
||||
}
|
||||
|
||||
pub fn is_fifth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round5
|
||||
}
|
||||
|
||||
pub fn is_sixth_phase(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Round6
|
||||
}
|
||||
|
||||
pub fn is_dkg_finalized(&self) -> bool {
|
||||
self.dkg_state.get_phase() == DkgPhase::Finalized
|
||||
}
|
||||
|
||||
pub fn contains_index<N>(&self, index: N) -> bool
|
||||
where N: UniqueSaturatedInto<usize>,
|
||||
{
|
||||
self.authorities_indexes.contains(index)
|
||||
}
|
||||
|
||||
pub fn insert_index<N>(&mut self, i: N) -> Option<N>
|
||||
where N: Copy + TryInto<usize>
|
||||
{
|
||||
self.authorities_indexes.insert(i)
|
||||
}
|
||||
|
||||
pub fn try_push_rotation_session(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
network_type: NetworkType,
|
||||
) -> Option<()> {
|
||||
let rotation_session = RotationSession {
|
||||
exodus_session,
|
||||
network_type,
|
||||
};
|
||||
|
||||
self.rotation_sessions.try_push(rotation_session).ok()
|
||||
}
|
||||
|
||||
pub fn count_ones<N>(&self) -> N
|
||||
where u32: UniqueSaturatedInto<N>,
|
||||
{
|
||||
self.authorities_indexes.count_ones()
|
||||
}
|
||||
|
||||
pub fn highest_bit<N>(&self) -> Option<N>
|
||||
where usize: UniqueSaturatedInto<N>,
|
||||
{
|
||||
self.authorities_indexes.highest_bit()
|
||||
}
|
||||
|
||||
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
|
||||
&self.authorities_indexes
|
||||
}
|
||||
|
||||
pub fn iter_rotation_sessions(&self) -> impl Iterator<Item = &RotationSession> {
|
||||
self.rotation_sessions.iter()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct DkgState<Block> {
|
||||
block: Block,
|
||||
index: DkgIndex,
|
||||
phase: DkgPhase,
|
||||
}
|
||||
|
||||
impl<Block: Copy> DkgState<Block> {
|
||||
fn is_dkg_pending(&self) -> bool { self.phase.is_dkg_pending() }
|
||||
fn is_dkg_vacant(&self) -> bool { self.phase.is_dkg_vacant() }
|
||||
|
||||
fn get_dkg_index(&self) -> DkgIndex { self.index }
|
||||
fn get_phase(&self) -> DkgPhase { self.phase }
|
||||
fn get_block(&self) -> Block { self.block }
|
||||
|
||||
fn restart(&mut self) {
|
||||
self.phase = Default::default();
|
||||
self.index = self.index.checked_add(1)
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
fn next_phase(&mut self) {
|
||||
self.phase.next_phase()
|
||||
}
|
||||
|
||||
fn set_block(&mut self, block: Block) {
|
||||
self.block = block;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub enum DkgPhase {
|
||||
#[default]
|
||||
Vacant,
|
||||
Round0,
|
||||
Round1,
|
||||
Round2,
|
||||
Round3,
|
||||
Round4,
|
||||
Round5,
|
||||
Round6,
|
||||
Round7,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
impl DkgPhase {
|
||||
fn is_dkg_pending(&self) -> bool {
|
||||
match &self {
|
||||
Self::Finalized | Self::Round7 => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dkg_vacant(&self) -> bool {
|
||||
match &self {
|
||||
Self::Vacant => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_phase(&mut self) {
|
||||
*self = match *self {
|
||||
Self::Vacant => Self::Round0,
|
||||
Self::Round0 => Self::Round1,
|
||||
Self::Round1 => Self::Round2,
|
||||
Self::Round2 => Self::Round3,
|
||||
Self::Round3 => Self::Round4,
|
||||
Self::Round4 => Self::Round5,
|
||||
Self::Round5 => Self::Round6,
|
||||
Self::Round6 => Self::Round7,
|
||||
Self::Round7 => Self::Finalized,
|
||||
Self::Finalized => Self::Finalized,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use frame_support::BoundedVec;
|
||||
use crate::ExodusError;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, Ord, PartialOrd, RuntimeDebug, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct EncryptionData<T: crate::pallet::Config> {
|
||||
pub ciphertext: BoundedVec<u8, crate::CiphertextMaxBytes<T>>,
|
||||
pub nonce: [u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize],
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> EncryptionData<T> {
|
||||
pub fn try_new(
|
||||
ciphertext_vec: Vec<u8>,
|
||||
nonce_bytes: &[u8],
|
||||
) -> Result<Self, ExodusError> {
|
||||
if nonce_bytes.len() != crate::ENCRYPTION_NONCE_MAX_BYTES as usize {
|
||||
return Err(ExodusError::EncryptionFailed);
|
||||
}
|
||||
|
||||
let ciphertext =
|
||||
BoundedVec::<u8, crate::CiphertextMaxBytes<T>>::try_from(ciphertext_vec)
|
||||
.map_err(|_| ExodusError::EncryptionFailed)?;
|
||||
|
||||
let mut nonce = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
|
||||
nonce.copy_from_slice(nonce_bytes);
|
||||
|
||||
Ok(Self { ciphertext, nonce })
|
||||
}
|
||||
}
|
||||
@ -1,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,611 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use sp_std::{vec::Vec, collections::btree_map::BTreeMap};
|
||||
use sp_runtime::{
|
||||
RuntimeDebug, RuntimeAppPublic,
|
||||
traits::{AtLeast32BitUnsigned, Saturating},
|
||||
offchain::storage::StorageValueRef,
|
||||
};
|
||||
|
||||
use super::{DkgPackage, ExodusPackage, ExodusOk, PackageContext};
|
||||
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
|
||||
|
||||
fn create_storage_key(prefix: &[u8], index: &[u8], network: &[u8]) -> Vec<u8> {
|
||||
let mut key = crate::DB_STORAGE_PREFIX.to_vec();
|
||||
key.extend(prefix);
|
||||
key.extend(index);
|
||||
key.extend(network);
|
||||
key
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct PersistentData<BlockNumber, DataType> {
|
||||
pub release_block: BlockNumber,
|
||||
pub data: DataType,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub enum DkgStorageEnum {
|
||||
Round0 { package_hash: ExodusHash },
|
||||
Round1 {
|
||||
secret_package: Vec<u8>,
|
||||
public_package: Vec<u8>,
|
||||
},
|
||||
Round2 {
|
||||
secret_package: Vec<u8>,
|
||||
public_packages: BTreeMap<AuthIndex, Vec<u8>>,
|
||||
},
|
||||
Round3 { decrypted: BTreeMap<AuthIndex, Vec<u8>> },
|
||||
Round4,
|
||||
Round5 {
|
||||
secret_share: Vec<u8>,
|
||||
merkle_proof: Vec<ExodusHash>,
|
||||
},
|
||||
Round6,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusNonce(Vec<u8>);
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusGroup {
|
||||
pub self_binding_factor: Vec<u8>,
|
||||
pub binding_factors_proof: Vec<ExodusHash>,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusShare;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
|
||||
pub struct ExodusExile;
|
||||
|
||||
type ExodusNonces<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusNonce>>;
|
||||
type ExodusGroups<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusGroup>>;
|
||||
type ExodusShares<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusShare>>;
|
||||
type ExodusExiles<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusExile>>;
|
||||
|
||||
pub struct ExodusStorageWrapper<ExodusInnerType> {
|
||||
pub inner: ExodusInnerType,
|
||||
pub data_changed: bool,
|
||||
}
|
||||
|
||||
impl<ExodusInnerType> ExodusStorageWrapper<ExodusInnerType> {
|
||||
pub fn new(inner: ExodusInnerType) -> Self {
|
||||
Self { inner, data_changed: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExodusStorage<BlockNumber, NetworkCurve>
|
||||
where
|
||||
BlockNumber: Default,
|
||||
NetworkCurve: Default,
|
||||
{
|
||||
current_block: BlockNumber,
|
||||
block_longevity: BlockNumber,
|
||||
pub network_curve: NetworkCurve,
|
||||
pub authority_index: AuthIndex,
|
||||
}
|
||||
|
||||
impl<B, NC> ExodusStorage<B, NC>
|
||||
where
|
||||
B: AtLeast32BitUnsigned + Copy,
|
||||
B: Encode + Decode + Default,
|
||||
NC: Encode + Decode + Default + Copy,
|
||||
{
|
||||
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_block(mut self, current_block: B) -> Self {
|
||||
self.current_block = current_block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
|
||||
self.block_longevity = block_longevity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn read_local_storage(&self) -> ExodusRecords<B> {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
let exodus_nonces = StorageValueRef::persistent(&nonces_key)
|
||||
.get::<ExodusNonces<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
let exodus_shares = StorageValueRef::persistent(&shares_key)
|
||||
.get::<ExodusShares<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
let exodus_groups = StorageValueRef::persistent(&groups_key)
|
||||
.get::<ExodusGroups<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
let exodus_exiles = StorageValueRef::persistent(&exiles_key)
|
||||
.get::<ExodusExiles<B>>()
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
ExodusRecords {
|
||||
exodus_nonces: ExodusStorageWrapper::new(exodus_nonces),
|
||||
exodus_shares: ExodusStorageWrapper::new(exodus_shares),
|
||||
exodus_groups: ExodusStorageWrapper::new(exodus_groups),
|
||||
exodus_exiles: ExodusStorageWrapper::new(exodus_exiles),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_signed_call_with_context<T, A, F1, F2, F3>(
|
||||
&self,
|
||||
authority_key: &A,
|
||||
exodus_records: &mut ExodusRecords<B>,
|
||||
make_data: F1,
|
||||
submit_call: F2,
|
||||
insert_data: F3,
|
||||
) -> Result<(), ExodusError>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
A: RuntimeAppPublic,
|
||||
F1: FnOnce(PackageContext<NC>) -> Result<ExodusPackage<NC, T>, ExodusError>,
|
||||
F2: FnOnce(ExodusPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
|
||||
F3: FnOnce(&mut ExodusRecords<B>, B),
|
||||
{
|
||||
let context = PackageContext::default()
|
||||
.with_authority_index(self.authority_index)
|
||||
.with_network_curve(self.network_curve);
|
||||
|
||||
let package = make_data(context)?;
|
||||
|
||||
let signature = authority_key
|
||||
.sign(&package.encode())
|
||||
.ok_or(ExodusError::BadSignature(self.authority_index))?;
|
||||
|
||||
submit_call(package, signature)?;
|
||||
|
||||
let next_release_block = self.current_block.saturating_add(self.block_longevity);
|
||||
insert_data(exodus_records, next_release_block);
|
||||
|
||||
self.try_write_local_storage(exodus_records);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn force_clear_local_storage(&self) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
|
||||
StorageValueRef::persistent(&nonces_key).clear();
|
||||
StorageValueRef::persistent(&shares_key).clear();
|
||||
StorageValueRef::persistent(&groups_key).clear();
|
||||
StorageValueRef::persistent(&exiles_key).clear();
|
||||
}
|
||||
|
||||
pub fn try_write_local_storage(&self, records: &ExodusRecords<B>) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
|
||||
if records.exodus_nonces.data_changed {
|
||||
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&nonces_key).set(&records.exodus_nonces.inner);
|
||||
}
|
||||
|
||||
if records.exodus_shares.data_changed {
|
||||
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&shares_key).set(&records.exodus_shares.inner);
|
||||
}
|
||||
|
||||
if records.exodus_groups.data_changed {
|
||||
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&groups_key).set(&records.exodus_groups.inner);
|
||||
}
|
||||
|
||||
if records.exodus_exiles.data_changed {
|
||||
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
|
||||
StorageValueRef::persistent(&exiles_key).set(&records.exodus_exiles.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExodusRecords<BlockNumber>
|
||||
where
|
||||
BlockNumber: AtLeast32BitUnsigned,
|
||||
{
|
||||
exodus_nonces: ExodusStorageWrapper<ExodusNonces<BlockNumber>>,
|
||||
exodus_shares: ExodusStorageWrapper<ExodusShares<BlockNumber>>,
|
||||
exodus_groups: ExodusStorageWrapper<ExodusGroups<BlockNumber>>,
|
||||
exodus_exiles: ExodusStorageWrapper<ExodusExiles<BlockNumber>>,
|
||||
}
|
||||
|
||||
impl<BlockNumber> ExodusRecords<BlockNumber>
|
||||
where
|
||||
BlockNumber: AtLeast32BitUnsigned + Default,
|
||||
{
|
||||
pub fn get_secret_nonce(&self, exodus_session: &ExodusSession) -> Option<&[u8]> {
|
||||
self.exodus_nonces.inner
|
||||
.get(exodus_session)
|
||||
.map(|v| v.data.0.as_slice())
|
||||
}
|
||||
|
||||
pub fn get_group_record(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> Option<ExodusGroup> {
|
||||
self.exodus_groups.inner
|
||||
.get(exodus_session)
|
||||
.cloned()
|
||||
.map(|storage| storage.data)
|
||||
}
|
||||
|
||||
pub fn is_exodus_session_banned(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> bool {
|
||||
self.exodus_exiles.inner.contains_key(exodus_session)
|
||||
}
|
||||
|
||||
pub fn is_group_registered(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
) -> bool {
|
||||
self.exodus_groups.inner.contains_key(exodus_session)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_nonce(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber
|
||||
) -> bool {
|
||||
self.exodus_nonces.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(current_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_share(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber,
|
||||
) -> bool {
|
||||
self.exodus_shares.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(¤t_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_waiting_period_for_group(
|
||||
&self,
|
||||
exodus_session: &ExodusSession,
|
||||
current_block: &BlockNumber,
|
||||
) -> bool {
|
||||
self.exodus_groups.inner
|
||||
.get(exodus_session)
|
||||
.map(|data| data.release_block.gt(¤t_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn exclude_exodus_session(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
) {
|
||||
let exodus_exile = PersistentData {
|
||||
data: ExodusExile,
|
||||
release_block: Default::default(),
|
||||
};
|
||||
|
||||
self.exodus_exiles.inner.insert(exodus_session, exodus_exile);
|
||||
self.exodus_exiles.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_nonce(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
nonce_commitment: Vec<u8>,
|
||||
) {
|
||||
let exodus_nonce = PersistentData {
|
||||
data: ExodusNonce(nonce_commitment),
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_nonces.inner.insert(exodus_session, exodus_nonce);
|
||||
self.exodus_nonces.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_share(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
) {
|
||||
let exodus_share = PersistentData {
|
||||
data: ExodusShare,
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_shares.inner.insert(exodus_session, exodus_share);
|
||||
self.exodus_shares.data_changed = true;
|
||||
}
|
||||
|
||||
pub fn insert_exodus_group(
|
||||
&mut self,
|
||||
exodus_session: ExodusSession,
|
||||
next_release_block: BlockNumber,
|
||||
self_binding_factor: Vec<u8>,
|
||||
binding_factors_proof: Vec<ExodusHash>,
|
||||
) {
|
||||
let exodus_group = PersistentData {
|
||||
data: ExodusGroup { self_binding_factor, binding_factors_proof },
|
||||
release_block: next_release_block,
|
||||
};
|
||||
|
||||
self.exodus_groups.inner.insert(exodus_session, exodus_group);
|
||||
self.exodus_groups.data_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct DkgStorage<BlockNumber, NetworkCurve>
|
||||
where
|
||||
BlockNumber: Copy + Clone,
|
||||
NetworkCurve: Copy + Clone,
|
||||
{
|
||||
block_longevity: BlockNumber,
|
||||
current_block: BlockNumber,
|
||||
pub network_curve: NetworkCurve,
|
||||
pub authority_index: AuthIndex,
|
||||
}
|
||||
|
||||
impl<B, NC> DkgStorage<B, NC>
|
||||
where
|
||||
B: Saturating + Encode + Decode + Copy + Default + PartialOrd,
|
||||
NC: Encode + Copy + Default,
|
||||
{
|
||||
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_current_block(mut self, current_block: B) -> Self {
|
||||
self.current_block = current_block;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
|
||||
self.block_longevity = block_longevity;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_release_block(&self) -> B {
|
||||
self.current_block.saturating_add(self.block_longevity)
|
||||
}
|
||||
|
||||
pub fn get_dkg_result(
|
||||
&self,
|
||||
dkg_record: DkgRecord<B>,
|
||||
) -> Result<ExodusOk<NC>, ExodusError> {
|
||||
match dkg_record.prefix_slice {
|
||||
crate::DKG_ROUND0_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(0u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND1_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(1u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND2_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(2u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND3_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(3u8, self.authority_index, self.network_curve)),
|
||||
crate::DKG_ROUND4_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(4u8, self.authority_index, self.network_curve)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !dkg_record.prefix_slice.starts_with(crate::DKG_SECRET_PREFIX) {
|
||||
return Err(ExodusError::UnknownPrefix);
|
||||
}
|
||||
|
||||
match &dkg_record.maybe_stored_data {
|
||||
Some(PersistentData { data: DkgStorageEnum::Round5 { .. }, .. }) => {
|
||||
Ok(ExodusOk::DkgRoundRegistered(5u8, self.authority_index, self.network_curve))
|
||||
},
|
||||
Some(PersistentData { data: DkgStorageEnum::Round6 { .. }, .. }) => {
|
||||
Ok(ExodusOk::DkgRoundRegistered(6u8, self.authority_index, self.network_curve))
|
||||
},
|
||||
_ => Err(ExodusError::UnknownPrefix),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_local_storage<'a>(
|
||||
&self,
|
||||
prefix_slice: &'a [u8],
|
||||
authority_index: AuthIndex,
|
||||
) -> DkgRecord<'a, B> {
|
||||
let encoded_index = authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(prefix_slice, &encoded_index, &encoded_network);
|
||||
|
||||
let maybe_stored_data = StorageValueRef::persistent(&key)
|
||||
.get::<PersistentData<B, DkgStorageEnum>>()
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
DkgRecord { prefix_slice, maybe_stored_data, current_block: self.current_block }
|
||||
}
|
||||
|
||||
pub fn prepare_signed_call_with_context<T, A, F1, F2>(
|
||||
&self,
|
||||
authority_key: &A,
|
||||
dkg_record: &mut DkgRecord<B>,
|
||||
make_data: F1,
|
||||
submit_call: F2,
|
||||
) -> Result<(), ExodusError>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
A: RuntimeAppPublic,
|
||||
F1: FnOnce(PackageContext<NC>) -> Result<DkgPackage<NC, T>, ExodusError>,
|
||||
F2: FnOnce(DkgPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
|
||||
{
|
||||
let context = PackageContext::default()
|
||||
.with_authority_index(self.authority_index)
|
||||
.with_network_curve(self.network_curve);
|
||||
|
||||
let package = make_data(context)?;
|
||||
|
||||
let signature = authority_key
|
||||
.sign(&package.encode())
|
||||
.ok_or(ExodusError::BadSignature(self.authority_index))?;
|
||||
|
||||
submit_call(package, signature)?;
|
||||
|
||||
self.try_write_local_storage(dkg_record);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_write_local_storage(
|
||||
&self,
|
||||
dkg_record: &DkgRecord<B>,
|
||||
) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(
|
||||
dkg_record.prefix_slice,
|
||||
&encoded_index,
|
||||
&encoded_network,
|
||||
);
|
||||
|
||||
match &dkg_record.maybe_stored_data {
|
||||
Some(to_store) => StorageValueRef::persistent(&key).set(&to_store),
|
||||
None => StorageValueRef::persistent(&key).clear(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clear_local_storage<'a>(
|
||||
&self,
|
||||
prefix: &'a [u8],
|
||||
) {
|
||||
let encoded_index = self.authority_index.encode();
|
||||
let encoded_network = self.network_curve.encode();
|
||||
let key = create_storage_key(prefix, &encoded_index, &encoded_network);
|
||||
|
||||
StorageValueRef::persistent(&key).clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DkgRecord<'a, BlockNumber> {
|
||||
maybe_stored_data: Option<PersistentData<BlockNumber, DkgStorageEnum>>,
|
||||
current_block: BlockNumber,
|
||||
prefix_slice: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a, BlockNumber> DkgRecord<'a, BlockNumber>
|
||||
where
|
||||
BlockNumber: Default + Copy + PartialOrd,
|
||||
{
|
||||
pub fn is_waiting_period(&self) -> bool {
|
||||
self.maybe_stored_data
|
||||
.as_ref()
|
||||
.map(|data| data.release_block.gt(&self.current_block))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn storage_exists(&self) -> bool {
|
||||
self.maybe_stored_data.is_some()
|
||||
}
|
||||
|
||||
pub fn into_inner(&self) -> Option<&DkgStorageEnum> {
|
||||
self.maybe_stored_data.as_ref().map(|stored| &stored.data)
|
||||
}
|
||||
|
||||
pub fn into_inner_mut(&mut self) -> Option<&mut DkgStorageEnum> {
|
||||
self.maybe_stored_data.as_mut().map(|stored| &mut stored.data)
|
||||
}
|
||||
|
||||
pub fn try_update_max_signers<F>(
|
||||
&mut self,
|
||||
get_new_secret: F
|
||||
) -> Result<&Vec<u8>, ExodusError>
|
||||
where
|
||||
F: FnOnce(&[u8]) -> Result<Vec<u8>, ExodusError>,
|
||||
{
|
||||
enum RoundType<'a> {
|
||||
R1 { public_package: &'a Vec<u8> },
|
||||
R2 { public_packages: &'a BTreeMap<AuthIndex, Vec<u8>> },
|
||||
}
|
||||
|
||||
let (old_secret, round_type) = self.maybe_stored_data.as_ref()
|
||||
.map(|persistent| match &persistent.data {
|
||||
DkgStorageEnum::Round1 { secret_package, public_package } => {
|
||||
Ok((secret_package, RoundType::R1 { public_package }))
|
||||
},
|
||||
DkgStorageEnum::Round2 { secret_package, public_packages } => {
|
||||
Ok((secret_package, RoundType::R2 { public_packages }))
|
||||
},
|
||||
_ => Err(ExodusError::NothingToWrite),
|
||||
})
|
||||
.ok_or(ExodusError::NothingToWrite)??;
|
||||
|
||||
let new_secret = get_new_secret(old_secret)?;
|
||||
let new_data = match round_type {
|
||||
RoundType::R1 { public_package } => DkgStorageEnum::Round1 {
|
||||
secret_package: new_secret,
|
||||
public_package: public_package.clone(),
|
||||
},
|
||||
RoundType::R2 { public_packages } => DkgStorageEnum::Round2 {
|
||||
secret_package: new_secret,
|
||||
public_packages: public_packages.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
self.update_with_block(None, new_data)?;
|
||||
|
||||
self.maybe_stored_data.as_ref()
|
||||
.map(|persistent| match &persistent.data {
|
||||
DkgStorageEnum::Round1 { secret_package, .. } |
|
||||
DkgStorageEnum::Round2 { secret_package, .. } => Ok(secret_package),
|
||||
_ => Err(ExodusError::NothingToWrite),
|
||||
})
|
||||
.ok_or(ExodusError::NothingToWrite)?
|
||||
}
|
||||
|
||||
pub fn update_with_block(
|
||||
&mut self,
|
||||
maybe_release_block: Option<BlockNumber>,
|
||||
new_data: DkgStorageEnum,
|
||||
) -> Result<(), ExodusError> {
|
||||
use core::mem::discriminant as d;
|
||||
|
||||
let release_block = match &self.maybe_stored_data {
|
||||
Some(existing) => {
|
||||
if d(&existing.data) != d(&new_data) {
|
||||
return Err(ExodusError::NothingToWrite);
|
||||
}
|
||||
maybe_release_block.unwrap_or(existing.release_block)
|
||||
}
|
||||
None => maybe_release_block.unwrap_or_default(),
|
||||
};
|
||||
|
||||
self.maybe_stored_data = Some(PersistentData { release_block, data: new_data });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
mod exodus_request;
|
||||
mod local_storage;
|
||||
mod encryption;
|
||||
mod consensus;
|
||||
mod signature;
|
||||
mod packages;
|
||||
mod success;
|
||||
mod removal;
|
||||
mod nonce;
|
||||
mod roast;
|
||||
mod dkg;
|
||||
|
||||
pub use exodus_request::*;
|
||||
pub use local_storage::*;
|
||||
pub use encryption::*;
|
||||
pub use consensus::*;
|
||||
pub use signature::*;
|
||||
pub use packages::*;
|
||||
pub use success::*;
|
||||
pub use removal::*;
|
||||
pub use nonce::*;
|
||||
pub use roast::*;
|
||||
pub use dkg::*;
|
||||
@ -1,20 +0,0 @@
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use crate::ELEMENT_MAX_BYTES;
|
||||
|
||||
type NonceType = BoundedVec<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct ExodusSeparatedNonce {
|
||||
pub hiding: NonceType,
|
||||
pub binding: NonceType,
|
||||
}
|
||||
|
||||
impl ExodusSeparatedNonce {
|
||||
pub fn new(hiding: NonceType, binding: NonceType) -> Self {
|
||||
Self { hiding, binding }
|
||||
}
|
||||
}
|
||||
@ -1,466 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
|
||||
|
||||
pub trait PackageMetadata<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: Copy,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NetworkCurve>;
|
||||
|
||||
fn get_authority_index(&self) -> AuthIndex {
|
||||
self.context().get_authority_index()
|
||||
}
|
||||
fn get_network_curve(&self) -> NetworkCurve {
|
||||
self.context().get_network_curve()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub enum ExodusPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
NonceCommitment(ExodusNonceCommitment<NetworkCurve>),
|
||||
GroupCommitment(ExodusGroupCommitment<NetworkCurve>),
|
||||
SignatureShare(ExodusSignatureShare<NetworkCurve, T>),
|
||||
}
|
||||
|
||||
impl<NetworkCurve, T> ExodusPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub fn bytes_len(&self) -> u32 {
|
||||
let length = match self {
|
||||
Self::GroupCommitment(package) => package.group_commitment.len(),
|
||||
Self::SignatureShare(package) => {
|
||||
(package.binding_factors_proof.len() * ExodusHash::len_bytes())
|
||||
+ package.self_binding_factor.len()
|
||||
+ package.signature_share.len()
|
||||
},
|
||||
Self::NonceCommitment(package) => {
|
||||
package.hiding_commitment.len()
|
||||
+ package.binding_commitment.len()
|
||||
},
|
||||
};
|
||||
|
||||
length as u32
|
||||
}
|
||||
|
||||
pub fn get_exodus_session(&self) -> ExodusSession {
|
||||
match &self {
|
||||
Self::SignatureShare(package) => package.session,
|
||||
Self::NonceCommitment(package) => package.session,
|
||||
Self::GroupCommitment(package) => package.session,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub enum DkgPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
Round0(Round0Package<NetworkCurve>),
|
||||
Round1(Round1Package<NetworkCurve, T>),
|
||||
Round2(Round2Package<NetworkCurve, T>),
|
||||
Round3(Round3Package<NetworkCurve, T>),
|
||||
Round4(Round4Package<NetworkCurve, T>),
|
||||
Round5(Round5Package<NetworkCurve>),
|
||||
Round6(Round6Package<NetworkCurve, T>),
|
||||
}
|
||||
|
||||
impl<NetworkCurve, T> DkgPackage<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub fn bytes_len(&self) -> u32 {
|
||||
let length = match self {
|
||||
Self::Round0(_) => ExodusHash::len_bytes(),
|
||||
Self::Round1(round1) => round1.package.len(),
|
||||
Self::Round2(round2) => round2.bundle.bitmask.len(),
|
||||
Self::Round3(round3) => round3.indices.len(),
|
||||
Self::Round4(round4) => round4.bundle.bitmask.len(),
|
||||
Self::Round5(round5) => {
|
||||
round5.verifying_key.len()
|
||||
.saturating_add(ExodusHash::len_bytes())
|
||||
},
|
||||
Self::Round6(round6) => round6.merkle_proof.len(),
|
||||
};
|
||||
|
||||
length as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct PackageContext<NetworkCurve> {
|
||||
authority_index: AuthIndex,
|
||||
network_curve: NetworkCurve
|
||||
}
|
||||
|
||||
impl<NetworkCurve> PackageContext<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: Copy,
|
||||
{
|
||||
pub fn get_authority_index(&self) -> AuthIndex {
|
||||
self.authority_index
|
||||
}
|
||||
|
||||
pub fn get_network_curve(&self) -> NetworkCurve {
|
||||
self.network_curve
|
||||
}
|
||||
|
||||
pub fn with_network_curve(mut self, network_curve: NetworkCurve) -> Self {
|
||||
self.network_curve = network_curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
|
||||
self.authority_index = authority_index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct BundledPackages {
|
||||
pub blob: Vec<u8>,
|
||||
pub bitmask: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct EncryptionBundledPackages<T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>,
|
||||
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> EncryptionBundledPackages<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
let blob_len = self.blob.len();
|
||||
let bitmask_len = self.bitmask.len();
|
||||
|
||||
bitmask_len.saturating_add(blob_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for EncryptionBundledPackages<T> {
|
||||
type Error = ExodusError;
|
||||
|
||||
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
|
||||
let blob =
|
||||
BoundedVec::<u8, crate::EncryptedRound2BlobMaxBytes<T>>::try_from(source.blob)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Self { blob, bitmask })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct JustificationsBundledPackages<T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>,
|
||||
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> JustificationsBundledPackages<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
let blob_len = self.blob.len();
|
||||
let bitmask_len = self.bitmask.len();
|
||||
|
||||
bitmask_len.saturating_add(blob_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for JustificationsBundledPackages<T> {
|
||||
type Error = ExodusError;
|
||||
|
||||
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
|
||||
let blob =
|
||||
BoundedVec::<u8, crate::Round2BlobMaxBytes<T>>::try_from(source.blob)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Self { blob, bitmask })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ExodusNonceCommitment<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub hiding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub binding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct ExodusGroupCommitment<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub group_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub binding_factors_root: ExodusHash,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct ExodusSignatureShare<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub binding_factors_proof: BoundedVec<ExodusHash, crate::BindingFactorsProof<T>>,
|
||||
pub signature_share: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
|
||||
pub self_binding_factor: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
|
||||
pub session: ExodusSession,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct Round0Package<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub package_hash: ExodusHash,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round1Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub package: BoundedVec<u8, crate::Round1MaxBytes<T>>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round2Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub bundle: EncryptionBundledPackages<T>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round3Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub indices: BoundedVec<u8, crate::AccusedIndicesMaxBytes<T>>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round4Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub bundle: JustificationsBundledPackages<T>,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
pub struct Round5Package<NetworkCurve> {
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub merkle_root: ExodusHash,
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct Round6Package<NetworkCurve, T>
|
||||
where
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
context: PackageContext<NetworkCurve>,
|
||||
pub verifying_share: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
|
||||
pub merkle_proof: BoundedVec<ExodusHash, crate::MerkleProofMaxSize<T>>,
|
||||
}
|
||||
|
||||
impl<NetworkCurve> PackageContext<NetworkCurve> {
|
||||
pub fn with_exodus_nonce_commitment(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
hiding_commitment_vec: Vec<u8>,
|
||||
binding_commitment_vec: Vec<u8>,
|
||||
) -> Result<ExodusNonceCommitment<NetworkCurve>, ExodusError> {
|
||||
let hiding_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(hiding_commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(binding_commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusNonceCommitment { context: self, hiding_commitment, binding_commitment, session })
|
||||
}
|
||||
|
||||
pub fn with_exodus_group_commitment(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
commitment_vec: Vec<u8>,
|
||||
binding_factors_root: ExodusHash,
|
||||
) -> Result<ExodusGroupCommitment<NetworkCurve>, ExodusError> {
|
||||
let group_commitment =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(commitment_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusGroupCommitment {
|
||||
context: self,
|
||||
group_commitment,
|
||||
binding_factors_root,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_exodus_signature_share<T: crate::pallet::Config>(
|
||||
self,
|
||||
session: ExodusSession,
|
||||
signature_share_vec: Vec<u8>,
|
||||
binding_factors_proof_vec: Vec<ExodusHash>,
|
||||
self_binding_factor_vec: Vec<u8>,
|
||||
) -> Result<ExodusSignatureShare<NetworkCurve, T>, ExodusError> {
|
||||
let signature_share =
|
||||
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(signature_share_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let binding_factors_proof =
|
||||
BoundedVec::<ExodusHash, crate::BindingFactorsProof<T>>::try_from(binding_factors_proof_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let self_binding_factor =
|
||||
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(self_binding_factor_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(ExodusSignatureShare {
|
||||
context: self,
|
||||
binding_factors_proof,
|
||||
self_binding_factor,
|
||||
signature_share,
|
||||
session,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_dkg_round0(
|
||||
self,
|
||||
package_hash: ExodusHash,
|
||||
) -> Round0Package<NetworkCurve> {
|
||||
Round0Package { context: self, package_hash }
|
||||
}
|
||||
|
||||
pub fn with_dkg_round1<T: crate::pallet::Config>(
|
||||
self,
|
||||
package_vec: Vec<u8>,
|
||||
) -> Result<Round1Package<NetworkCurve, T>, ExodusError> {
|
||||
let package =
|
||||
BoundedVec::<u8, crate::Round1MaxBytes<T>>::try_from(package_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round1Package { context: self, package })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round2<T: crate::pallet::Config>(
|
||||
self,
|
||||
bundle_raw: BundledPackages,
|
||||
) -> Result<Round2Package<NetworkCurve, T>, ExodusError> {
|
||||
let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?;
|
||||
Ok(Round2Package { context: self, bundle })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round3<T: crate::pallet::Config>(
|
||||
self,
|
||||
indices_vec: Vec<u8>
|
||||
) -> Result<Round3Package<NetworkCurve, T>, ExodusError> {
|
||||
let indices =
|
||||
BoundedVec::<u8, crate::AccusedIndicesMaxBytes<T>>::try_from(indices_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round3Package { context: self, indices })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round4<T: crate::pallet::Config>(
|
||||
self,
|
||||
bundle_raw: BundledPackages,
|
||||
) -> Result<Round4Package<NetworkCurve, T>, ExodusError> {
|
||||
let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?;
|
||||
Ok(Round4Package { context: self, bundle })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round5(
|
||||
self,
|
||||
verifying_key_vec: Vec<u8>,
|
||||
merkle_root: ExodusHash,
|
||||
) -> Result<Round5Package<NetworkCurve>, ExodusError> {
|
||||
let verifying_key =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_key_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round5Package { context: self, verifying_key, merkle_root })
|
||||
}
|
||||
|
||||
pub fn with_dkg_round6<T: crate::pallet::Config>(
|
||||
self,
|
||||
verifying_share_vec: Vec<u8>,
|
||||
merkle_proof_vec: Vec<ExodusHash>,
|
||||
) -> Result<Round6Package<NetworkCurve, T>, ExodusError> {
|
||||
let verifying_share =
|
||||
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_share_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
let merkle_proof =
|
||||
BoundedVec::<ExodusHash, crate::MerkleProofMaxSize<T>>::try_from(merkle_proof_vec)
|
||||
.map_err(|_| ExodusError::DeserializationError)?;
|
||||
|
||||
Ok(Round6Package { context: self, verifying_share, merkle_proof })
|
||||
}
|
||||
}
|
||||
|
||||
impl<NC, T> PackageMetadata<NC> for ExodusPackage<NC, T>
|
||||
where
|
||||
NC: Copy,
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NC> {
|
||||
match self {
|
||||
Self::NonceCommitment(package) => &package.context,
|
||||
Self::GroupCommitment(package) => &package.context,
|
||||
Self::SignatureShare(package) => &package.context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<NC, T> PackageMetadata<NC> for DkgPackage<NC, T>
|
||||
where
|
||||
NC: Copy,
|
||||
T: crate::pallet::Config,
|
||||
{
|
||||
fn context(&self) -> &PackageContext<NC> {
|
||||
match self {
|
||||
Self::Round0(package) => &package.context,
|
||||
Self::Round1(package) => &package.context,
|
||||
Self::Round2(package) => &package.context,
|
||||
Self::Round3(package) => &package.context,
|
||||
Self::Round4(package) => &package.context,
|
||||
Self::Round5(package) => &package.context,
|
||||
Self::Round6(package) => &package.context,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
pub struct DkgRemovalResult {
|
||||
weight: Weight,
|
||||
cleared: bool,
|
||||
}
|
||||
|
||||
impl DkgRemovalResult {
|
||||
pub fn new<F>(try_clean_storage: F) -> Self
|
||||
where
|
||||
F: FnOnce() -> (Weight, bool)
|
||||
{
|
||||
let (weight, cleared) = try_clean_storage();
|
||||
Self { weight, cleared }
|
||||
}
|
||||
|
||||
pub fn fully_cleared(&self) -> bool {
|
||||
self.cleared
|
||||
}
|
||||
|
||||
pub fn used_weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
use codec::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub enum RoastStatus {
|
||||
#[default]
|
||||
NonceCommitments,
|
||||
GroupCommitments,
|
||||
PartialSignatures,
|
||||
}
|
||||
|
||||
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct RoastSessionState<Bitmap>
|
||||
where
|
||||
Bitmap: Default,
|
||||
{
|
||||
pub status: RoastStatus,
|
||||
pub nonce_committers: Bitmap,
|
||||
pub group_committers: Bitmap,
|
||||
pub partial_signers: Bitmap,
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
use scale_info::TypeInfo;
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::{BoundedVec, traits::ConstU32};
|
||||
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
|
||||
|
||||
type ExodusNonceT = BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>;
|
||||
type ExodusSignatureT = BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>;
|
||||
type Type<NetworkId, Balance> = super::ExodusRequestType<NetworkId, Balance>;
|
||||
|
||||
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct ExodusSignedMessage<NetworkId: Default, Balance: Default> {
|
||||
pub nonce: ExodusNonceT,
|
||||
pub signature: ExodusSignatureT,
|
||||
pub r#type: Type<NetworkId, Balance>,
|
||||
}
|
||||
|
||||
impl<NetworkId, Balance> ExodusSignedMessage<NetworkId, Balance>
|
||||
where
|
||||
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
|
||||
Balance: UniqueSaturatedInto<u128> + Copy + Default,
|
||||
{
|
||||
pub fn new<E, S>(
|
||||
nonce_iter: E,
|
||||
signature_iter: S,
|
||||
r#type: Type<NetworkId, Balance>
|
||||
) -> Option<Self>
|
||||
where
|
||||
E: Iterator<Item = u8> + ExactSizeIterator,
|
||||
S: Iterator<Item = u8> + ExactSizeIterator,
|
||||
{
|
||||
let mut nonce = ExodusNonceT::new();
|
||||
nonce.try_extend(nonce_iter).ok()?;
|
||||
|
||||
let mut signature = ExodusSignatureT::new();
|
||||
signature.try_extend(signature_iter).ok()?;
|
||||
|
||||
Some(Self { nonce, signature, r#type })
|
||||
}
|
||||
|
||||
pub fn get_message<'a>(
|
||||
&self,
|
||||
message_buffer: &'a mut [u8; crate::MAX_MESSAGE_SIZE as usize],
|
||||
) -> Option<&'a [u8]> {
|
||||
self.r#type.get_message(message_buffer)
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
use crate::{AuthIndex, ExodusSession, DkgIndex, RoastSession};
|
||||
|
||||
pub enum ExodusOk<NetworkCurve> {
|
||||
ExodusEmptyRequests(AuthIndex, NetworkCurve),
|
||||
ExodusWaitingState(AuthIndex, NetworkCurve),
|
||||
ExodusNonceCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusGroupCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusSignedPartially(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
|
||||
ExodusVerifyingShareMissed(AuthIndex, DkgIndex, NetworkCurve),
|
||||
|
||||
DkgNotPartOfRound(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundRegistered(u8, AuthIndex, NetworkCurve),
|
||||
DkgPreviousPurged(AuthIndex, NetworkCurve),
|
||||
DkgRoundsCleared(AuthIndex, NetworkCurve),
|
||||
DkgRoundAlreadyPassed(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundWaitingPeriod(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundNothingStored(u8, AuthIndex, NetworkCurve),
|
||||
DkgRoundMaxSignersNarrowed(u8, AuthIndex, AuthIndex, AuthIndex, NetworkCurve),
|
||||
}
|
||||
|
||||
impl<NetworkCurve> core::fmt::Display for ExodusOk<NetworkCurve>
|
||||
where
|
||||
NetworkCurve: core::fmt::Display
|
||||
{
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self {
|
||||
ExodusOk::ExodusEmptyRequests(auth_index, curve) => write!(fmt, "No EXODUS requests found by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusWaitingState(auth_index, curve) => write!(fmt, "Waiting for EXODUS requests to be processed by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusNonceCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Nonce committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusGroupCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Aggregated nonce and binding factor proof committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusSignedPartially(exodus, roast, auth_index, curve) => write!(fmt, "Partial signature created for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
|
||||
ExodusOk::ExodusVerifyingShareMissed(auth_index, dkg_index, curve) => write!(fmt, "EXODUS Round no verifying share for authority #{auth_index} based on DKG session #{dkg_index} for curve {curve}."),
|
||||
|
||||
ExodusOk::DkgNotPartOfRound(round, auth_index, curve) => write!(fmt, "DKG authority #{auth_index} for network curve {curve} is not part of Round{round}."),
|
||||
ExodusOk::DkgRoundRegistered(round, auth_index, curve) => write!(fmt, "DKG Round{round} registered by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgPreviousPurged(auth_index, curve) => write!(fmt, "Storage prepared for new DKG session by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundsCleared(auth_index, curve) => write!(fmt, "DKG Rounds cleared from storage by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundAlreadyPassed(round, auth_index, curve) => write!(fmt, "DKG Round{round} already done by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundWaitingPeriod(round, auth_index, curve) => write!(fmt, "DKG Round{round} waiting to be applied by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundNothingStored(round, auth_index, curve) => write!(fmt, "DKG nothing stored from Round{round} by authority #{auth_index} for network curve {curve}."),
|
||||
ExodusOk::DkgRoundMaxSignersNarrowed(round, auth_index, prev_max, curr_max, curve) => write!(fmt, "DKG Round{round} narrowed max_signers from {prev_max} to {curr_max} by {auth_index} for network curve {curve}."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,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))
|
||||
}
|
||||
}
|
||||
@ -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(¤t_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(¤t_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(¤t_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(
|
||||
¤t_account,
|
||||
&network_id,
|
||||
&preimage_hash
|
||||
);
|
||||
|
||||
let message_hash = SubstrateKeccakHasher::hash(&message_string_bytes);
|
||||
let mut message_bytes = [0u8; 32];
|
||||
message_bytes.copy_from_slice(message_hash.as_ref());
|
||||
|
||||
let msg_to_sign = libsecp256k1::Message::parse(&message_bytes);
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&msg_to_sign, &secret_key);
|
||||
|
||||
let mut signature_raw = [0u8; 65];
|
||||
signature_raw[0..32].copy_from_slice(&sig.r.b32());
|
||||
signature_raw[32..64].copy_from_slice(&sig.s.b32());
|
||||
signature_raw[64] = recovery_id.serialize();
|
||||
|
||||
let evm_signature = EvmSignature::from(signature_raw);
|
||||
|
||||
#[extrinsic_call]
|
||||
claim(
|
||||
RawOrigin::Signed(current_account.clone()),
|
||||
network_id,
|
||||
claim_package,
|
||||
dummy_evm_address,
|
||||
evm_signature,
|
||||
);
|
||||
|
||||
assert_eq!(dummy_shares, GlobalShares::<T>::get().claimed_shares);
|
||||
assert_eq!(
|
||||
dummy_shares,
|
||||
MemberShares::<T>::get(¤t_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,
|
||||
);
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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(¤t_user.substrate_account, MemberShare {
|
||||
locked_balance: minimum_donation,
|
||||
initiated_network: None,
|
||||
activated_shares: 0,
|
||||
});
|
||||
|
||||
let proof = generate_proof::<SubstrateKeccakHasher, u8>(&merkle_tree, max_index, slot_index as u8);
|
||||
|
||||
let claim_package = ClaimPackage {
|
||||
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(¤t_user.evm_address, network_id);
|
||||
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
|
||||
|
||||
let message_string_bytes = Governance::ethereum_signable_message(
|
||||
¤t_user.substrate_account,
|
||||
&network_id,
|
||||
&preimage_hash
|
||||
);
|
||||
|
||||
let message_hash = SubstrateKeccakHasher::hash(&message_string_bytes);
|
||||
let mut message_bytes = [0u8; 32];
|
||||
message_bytes.copy_from_slice(message_hash.as_ref());
|
||||
|
||||
let msg_to_sign = Message::parse(&message_bytes);
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&msg_to_sign, ¤t_user.evm_secret);
|
||||
|
||||
let mut signature_raw = [0u8; 65];
|
||||
signature_raw[0..32].copy_from_slice(&sig.r.b32());
|
||||
signature_raw[32..64].copy_from_slice(&sig.s.b32());
|
||||
signature_raw[64] = recovery_id.serialize();
|
||||
|
||||
let evm_signature = EvmSignature::from(signature_raw);
|
||||
|
||||
assert_ok!(Governance::claim(
|
||||
RuntimeOrigin::signed(current_user.substrate_account.clone()),
|
||||
network_id,
|
||||
claim_package,
|
||||
current_user.evm_address.clone(),
|
||||
evm_signature
|
||||
));
|
||||
|
||||
let updated_share = MemberShares::<Test>::get(¤t_user.substrate_account);
|
||||
assert_eq!(updated_share.activated_shares, current_user.allowed_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,
|
||||
)
|
||||
);
|
||||
})
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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
@ -1,316 +0,0 @@
|
||||
use ghost_traits::hashing::GhostHasher;
|
||||
|
||||
fn combine_values<H, V>(left: V, right: V) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
let hash_len = H::hash_len();
|
||||
let mut combined = sp_std::vec![0u8; hash_len * 2];
|
||||
|
||||
combined[..hash_len].copy_from_slice(left.as_ref());
|
||||
combined[hash_len..].copy_from_slice(right.as_ref());
|
||||
|
||||
H::hash(&combined)
|
||||
}
|
||||
|
||||
pub fn cumulative_hash<H, S>(raw_values: S) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
raw_values
|
||||
.into_iter()
|
||||
.fold(H::empty(), |acc_hash, raw_value| {
|
||||
let hashed_value = H::hash(raw_value.as_ref());
|
||||
combine_values::<H, _>(acc_hash, hashed_value)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sequential_hash<H, S>(raw_values: S) -> H::Hash
|
||||
where
|
||||
H: GhostHasher,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
let mut iter = raw_values.into_iter().peekable();
|
||||
|
||||
let Some(first_value) = iter.next() else {
|
||||
return H::empty();
|
||||
};
|
||||
if iter.peek().is_none() {
|
||||
return H::hash(first_value.as_ref());
|
||||
}
|
||||
|
||||
let current_hash = H::from_slice(first_value.as_ref());
|
||||
|
||||
iter.fold(current_hash, |acc_hash, raw_value| {
|
||||
let next_hash = H::from_slice(raw_value.as_ref());
|
||||
combine_values::<H, _>(acc_hash, next_hash)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_hash_ancestry<H, S>(
|
||||
tx_block_header_bytes: &[u8],
|
||||
ancestry_headers: S,
|
||||
trusted_top_block_hash: H::Hash,
|
||||
) -> bool
|
||||
where
|
||||
H: GhostHasher,
|
||||
H::Hash: PartialEq + Copy + AsRef<[u8]> + AsMut<[u8]>,
|
||||
S: IntoIterator,
|
||||
S::Item: AsRef<[u8]>,
|
||||
{
|
||||
ancestry_headers
|
||||
.into_iter()
|
||||
.try_fold(
|
||||
sequential_hash::<H, _>(core::iter::once(tx_block_header_bytes)),
|
||||
|acc_parent_hash, current_header| {
|
||||
let header_ref = current_header.as_ref();
|
||||
if header_ref.len() < 36 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut current_prev_hash = [0u8; 32];
|
||||
current_prev_hash.copy_from_slice(&header_ref[4..36]);
|
||||
|
||||
if current_prev_hash != acc_parent_hash.as_ref() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let current_header_iter = core::iter::once(header_ref);
|
||||
Some(sequential_hash::<H, _>(current_header_iter))
|
||||
},
|
||||
)
|
||||
.map(|mut final_le_hash| {
|
||||
final_le_hash.as_mut().reverse();
|
||||
final_le_hash == trusted_top_block_hash
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::H256;
|
||||
use sp_io::hashing::{blake2_256, sha2_256};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub struct TestBlake2Hasher;
|
||||
impl GhostHasher for TestBlake2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
H256::from(blake2_256(data))
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestSha2Hasher;
|
||||
impl GhostHasher for TestSha2Hasher {
|
||||
type Hash = H256;
|
||||
type HashBytes = [u8; 32];
|
||||
fn hash(data: &[u8]) -> Self::Hash {
|
||||
let first_pass: [u8; 32] = sha2_256(data);
|
||||
let second_pass: [u8; 32] = sha2_256(&first_pass);
|
||||
|
||||
H256::from(second_pass)
|
||||
}
|
||||
fn hash_len() -> usize {
|
||||
H256::len_bytes()
|
||||
}
|
||||
fn empty() -> Self::Hash {
|
||||
H256::zero()
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_sequential_hash(header: &[u8; 80]) -> H256 {
|
||||
sequential_hash::<TestSha2Hasher, _>(core::iter::once(header))
|
||||
}
|
||||
|
||||
fn create_header(prev_hash: [u8; 32]) -> [u8; 80] {
|
||||
let mut header = [0u8; 80];
|
||||
header[4..36].copy_from_slice(&prev_hash);
|
||||
header
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_empty_values() {
|
||||
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
||||
let result = cumulative_hash::<TestBlake2Hasher, _>(empty_list);
|
||||
assert_eq!(H256::zero(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_determinism_and_avalanche() {
|
||||
let dataset_1 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
let dataset_2 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
let dataset_3 = sp_std::vec![vec![9, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
|
||||
|
||||
let hash_1 = cumulative_hash::<TestBlake2Hasher, _>(dataset_1);
|
||||
let hash_2 = cumulative_hash::<TestBlake2Hasher, _>(dataset_2);
|
||||
let hash_3 = cumulative_hash::<TestBlake2Hasher, _>(dataset_3);
|
||||
|
||||
assert_eq!(hash_1, hash_2);
|
||||
assert_ne!(hash_1, hash_3);
|
||||
assert_ne!(hash_2, hash_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_order_sensitive() {
|
||||
let dataset_forward = sp_std::vec![sp_std::vec![1, 1, 1], sp_std::vec![2, 2, 2]];
|
||||
let dataset_reversed = sp_std::vec![sp_std::vec![2, 2, 2], sp_std::vec![1, 1, 1]];
|
||||
|
||||
let hash_forward = cumulative_hash::<TestBlake2Hasher, _>(dataset_forward);
|
||||
let hash_reversed = cumulative_hash::<TestBlake2Hasher, _>(dataset_reversed);
|
||||
|
||||
assert_ne!(hash_forward, hash_reversed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cumulative_hash_with_slices() {
|
||||
let slice_data: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]];
|
||||
let hash_from_slice = cumulative_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
||||
assert_ne!(hash_from_slice, H256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_empty() {
|
||||
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(empty_list);
|
||||
assert_eq!(H256::zero(), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_single_element() {
|
||||
let single_item = sp_std::vec![sp_std::vec![1, 2, 3]];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(single_item);
|
||||
let expected = TestBlake2Hasher::hash(&[1, 2, 3]);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_determinism_and_order() {
|
||||
let data_1 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
||||
let data_2 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
|
||||
let data_reversed = sp_std::vec![sp_std::vec![3], sp_std::vec![2], sp_std::vec![1]];
|
||||
|
||||
let hash_1 = sequential_hash::<TestBlake2Hasher, _>(data_1);
|
||||
let hash_2 = sequential_hash::<TestBlake2Hasher, _>(data_2);
|
||||
let hash_reversed = sequential_hash::<TestBlake2Hasher, _>(data_reversed);
|
||||
|
||||
assert_eq!(hash_1, hash_2);
|
||||
assert_ne!(hash_1, hash_reversed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_vs_cumulative_hash() {
|
||||
let dataset = sp_std::vec![sp_std::vec![1, 2, 3], sp_std::vec![4, 5, 6]];
|
||||
let seq_hash = sequential_hash::<TestBlake2Hasher, _>(dataset.clone());
|
||||
let cum_hash = cumulative_hash::<TestBlake2Hasher, _>(dataset);
|
||||
assert_ne!(seq_hash, cum_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sequential_hash_with_slices() {
|
||||
let slice_data: &[&[u8]] = &[&[10, 20], &[30, 40]];
|
||||
let result = sequential_hash::<TestBlake2Hasher, _>(slice_data.iter());
|
||||
assert_ne!(result, H256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_in_current_block_with_empty_ancestry() {
|
||||
let tx_header = [0x11u8; 80];
|
||||
let tx_block_le_hash = mock_sequential_hash(&tx_header);
|
||||
|
||||
let mut trusted_bytes = tx_block_le_hash.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&tx_header,
|
||||
&[],
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_single_ancestor_header() {
|
||||
let tx_header_100 = [0x22u8; 80];
|
||||
let hash_100_le = mock_sequential_hash(&tx_header_100);
|
||||
|
||||
let header_101 = create_header(hash_100_le.0);
|
||||
let hash_101_le = mock_sequential_hash(&header_101);
|
||||
|
||||
let mut trusted_bytes = hash_101_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [header_101];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&tx_header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_multiple_ancestor_headers_chain() {
|
||||
let header_100 = [0x55u8; 80];
|
||||
let hash_100_le = mock_sequential_hash(&header_100);
|
||||
|
||||
let header_101 = create_header(hash_100_le.0);
|
||||
let hash_101_le = mock_sequential_hash(&header_101);
|
||||
|
||||
let header_102 = create_header(hash_101_le.0);
|
||||
let hash_102_le = mock_sequential_hash(&header_102);
|
||||
|
||||
let header_103 = create_header(hash_102_le.0);
|
||||
let hash_103_le = mock_sequential_hash(&header_103);
|
||||
|
||||
let mut trusted_bytes = hash_103_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [header_101, header_102, header_103];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_fails_if_chain_is_broken() {
|
||||
let header_100 = [0xAAu8; 80];
|
||||
|
||||
let broken_header_101 = create_header([0xFF; 32]);
|
||||
let hash_101_le = mock_sequential_hash(&broken_header_101);
|
||||
|
||||
let mut trusted_bytes = hash_101_le.0;
|
||||
trusted_bytes.reverse();
|
||||
let trusted_top_block_hash = H256::from(trusted_bytes);
|
||||
|
||||
let ancestry = [broken_header_101];
|
||||
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
|
||||
&header_100,
|
||||
&ancestry,
|
||||
trusted_top_block_hash,
|
||||
);
|
||||
|
||||
assert!(!result);
|
||||
}
|
||||
}
|
||||
@ -1,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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
18
pallets/networks/Cargo.toml
Normal file → Executable 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
290
pallets/networks/src/benchmarking.rs
Normal file → Executable 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
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
47
pallets/networks/src/mock.rs
Normal file → Executable 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
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
423
pallets/networks/src/weights.rs
Normal file → Executable 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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
]
|
||||
@ -1,74 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-benchmarking"
|
||||
version = "26.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of FRAME nomination pools pallet benchmarking"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { workspace = true, default-features = false, features = ["derive"] }
|
||||
scale-info = { workspace = true, default-features = false, features = ["derive"] }
|
||||
|
||||
# FRAME
|
||||
frame-benchmarking = { workspace = true, default-features = false }
|
||||
frame-election-provider-support = { workspace = true, default-features = false }
|
||||
frame-support = { workspace = true, default-features = false }
|
||||
frame-system = { workspace = true, default-features = false }
|
||||
pallet-bags-list = { workspace = true, default-features = false }
|
||||
pallet-staking = { workspace = true, default-features = false }
|
||||
ghost-nomination-pools = { workspace = true, default-features = false }
|
||||
|
||||
# Substrate Primitives
|
||||
sp-runtime = { workspace = true, default-features = false }
|
||||
sp-runtime-interface = { workspace = true, default-features = false }
|
||||
sp-staking = { workspace = true, default-features = false }
|
||||
sp-std = { workspace = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { workspace = true, default-features = false }
|
||||
pallet-timestamp = { workspace = true }
|
||||
pallet-staking-reward-curve = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-election-provider-support/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"pallet-bags-list/std",
|
||||
"pallet-balances/std",
|
||||
"ghost-nomination-pools/std",
|
||||
"pallet-staking/std",
|
||||
"pallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime-interface/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-election-provider-support/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-bags-list/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"ghost-nomination-pools/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-staking/runtime-benchmarks",
|
||||
]
|
||||
@ -1,846 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
use frame_benchmarking::v1::{account, whitelist_account};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::{
|
||||
assert_ok, ensure,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate, Unbalanced},
|
||||
Get,
|
||||
},
|
||||
};
|
||||
use frame_system::RawOrigin as RuntimeOrigin;
|
||||
use ghost_nomination_pools::{
|
||||
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
|
||||
Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
|
||||
MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
|
||||
Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
||||
};
|
||||
use pallet_staking::MaxNominationsOf;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, StakingInterface};
|
||||
use sp_std::{vec, vec::Vec};
|
||||
// `frame_benchmarking::benchmarks!` macro needs this
|
||||
use ghost_nomination_pools::Call;
|
||||
|
||||
type CurrencyOf<T> = <T as ghost_nomination_pools::Config>::Currency;
|
||||
|
||||
const USER_SEED: u32 = 0;
|
||||
const MAX_SPANS: u32 = 100;
|
||||
|
||||
pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
pub trait Config:
|
||||
ghost_nomination_pools::Config
|
||||
+ pallet_staking::Config
|
||||
+ pallet_bags_list::Config<VoterBagsListInstance>
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn create_funded_user_with_balance<T: ghost_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
) -> T::AccountId {
|
||||
let user = account(string, n, USER_SEED);
|
||||
T::Currency::set_balance(&user, balance);
|
||||
user
|
||||
}
|
||||
|
||||
// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free
|
||||
// balance.
|
||||
fn create_pool_account<T: ghost_nomination_pools::Config>(
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
commission: Option<Perbill>,
|
||||
) -> (T::AccountId, T::AccountId) {
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let pool_creator: T::AccountId =
|
||||
create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
|
||||
let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
|
||||
|
||||
Pools::<T>::create(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
balance,
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(c) = commission {
|
||||
let pool_id = ghost_nomination_pools::LastPoolId::<T>::get();
|
||||
Pools::<T>::set_commission(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
pool_id,
|
||||
Some((c, pool_creator.clone())),
|
||||
)
|
||||
.expect("pool just created, commission can be set by root; qed");
|
||||
}
|
||||
|
||||
let pool_account = ghost_nomination_pools::BondedPools::<T>::iter()
|
||||
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
|
||||
.map(|(pool_id, _)| Pools::<T>::create_bonded_account(pool_id))
|
||||
.expect("pool_creator created a pool above");
|
||||
|
||||
(pool_creator, pool_account)
|
||||
}
|
||||
|
||||
fn vote_to_balance<T: ghost_nomination_pools::Config>(
|
||||
vote: u64,
|
||||
) -> Result<BalanceOf<T>, &'static str> {
|
||||
vote.try_into().map_err(|_| "could not convert u64 to Balance")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct ListScenario<T: ghost_nomination_pools::Config> {
|
||||
/// Stash/Controller that is expected to be moved.
|
||||
origin1: T::AccountId,
|
||||
creator1: T::AccountId,
|
||||
dest_weight: BalanceOf<T>,
|
||||
origin1_member: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> ListScenario<T> {
|
||||
/// An expensive scenario for bags-list implementation:
|
||||
///
|
||||
/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
|
||||
/// itself will need to be read and written to update its head. The node pointed to by r.next
|
||||
/// will need to be read and written as it will need to have its prev pointer updated. Note
|
||||
/// that there are two other worst case scenarios for bag removal: 1) the node is a tail and
|
||||
/// 2) the node is a middle node with prev and next; all scenarios end up with the same number
|
||||
/// of storage reads and writes.
|
||||
///
|
||||
/// - the destination bag has at least one node, which will need its next pointer updated.
|
||||
pub(crate) fn new(
|
||||
origin_weight: BalanceOf<T>,
|
||||
is_increase: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
|
||||
|
||||
ensure!(
|
||||
ghost_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
|
||||
"must allow at least three pools for benchmarks"
|
||||
);
|
||||
|
||||
// Burn the entire issuance.
|
||||
CurrencyOf::<T>::set_total_issuance(Zero::zero());
|
||||
|
||||
// Create accounts with the origin weight
|
||||
let (pool_creator1, pool_origin1) =
|
||||
create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin1,
|
||||
// NOTE: these don't really need to be validators.
|
||||
vec![account("random_validator", 0, USER_SEED)],
|
||||
)?;
|
||||
|
||||
let (_, pool_origin2) =
|
||||
create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin2,
|
||||
vec![account("random_validator", 0, USER_SEED)].clone(),
|
||||
)?;
|
||||
|
||||
// Find a destination weight that will trigger the worst case scenario
|
||||
let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
|
||||
&pool_origin1,
|
||||
is_increase,
|
||||
);
|
||||
|
||||
let dest_weight: BalanceOf<T> =
|
||||
dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
|
||||
|
||||
// Create an account with the worst case destination weight
|
||||
let (_, pool_dest1) =
|
||||
create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
|
||||
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
|
||||
|
||||
Ok(ListScenario {
|
||||
origin1: pool_origin1,
|
||||
creator1: pool_creator1,
|
||||
dest_weight,
|
||||
origin1_member: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
|
||||
let amount = MinJoinBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
// Max `amount` with minimum thresholds for account balance and joining a pool
|
||||
// to ensure 1) the user can be created and 2) can join the pool
|
||||
.max(amount);
|
||||
|
||||
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
|
||||
self.origin1_member = Some(joiner.clone());
|
||||
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
|
||||
|
||||
let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
|
||||
|
||||
// Unbond `amount` from the underlying pool account so when the member joins
|
||||
// we will maintain `current_bonded`.
|
||||
T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`.");
|
||||
|
||||
// Account pool points for the unbonded balance.
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
maybe_pool.as_mut().map(|pool| pool.points -= amount)
|
||||
});
|
||||
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
|
||||
|
||||
// check that the vote weight is still the same as the original bonded
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
|
||||
|
||||
// check the member was added correctly
|
||||
let member = PoolMembers::<T>::get(&joiner).unwrap();
|
||||
assert_eq!(member.points, amount);
|
||||
assert_eq!(member.pool_id, 1);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
let max_additional = scenario.dest_weight - origin_weight;
|
||||
let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
|
||||
|
||||
let joiner: T::AccountId
|
||||
= create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
|
||||
|
||||
whitelist_account!(joiner);
|
||||
}: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1)
|
||||
verify {
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = scenario.dest_weight - origin_weight;
|
||||
|
||||
// creator of the src pool will bond-extra, bumping itself to dest bag.
|
||||
|
||||
}: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra))
|
||||
verify {
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_other {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
// set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// transfer exactly `extra` to the depositor of the src pool (1),
|
||||
let reward_account1 = Pools::<T>::create_reward_account(1);
|
||||
assert!(extra >= CurrencyOf::<T>::minimum_balance());
|
||||
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
|
||||
|
||||
}: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards)
|
||||
verify {
|
||||
// commission of 50% deducted here.
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight / 2u32.into()
|
||||
);
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
|
||||
// Send funds to the reward account of the pool
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
|
||||
// behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// Sanity check
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unbond {
|
||||
// The weight the nominator will start at. The value used here is expected to be
|
||||
// significantly higher than the first position in a list (e.g. the first bag threshold).
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, false)?;
|
||||
let amount = origin_weight - scenario.dest_weight;
|
||||
|
||||
let scenario = scenario.add_joiner(amount);
|
||||
let member_id = scenario.origin1_member.unwrap().clone();
|
||||
let member_id_lookup = T::Lookup::unlookup(member_id.clone());
|
||||
let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
|
||||
whitelist_account!(member_id);
|
||||
}: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points)
|
||||
verify {
|
||||
let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap();
|
||||
// We at least went down to the destination bag
|
||||
assert!(bonded_after <= scenario.dest_weight);
|
||||
let member = PoolMembers::<T>::get(
|
||||
&member_id
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
|
||||
vec![0 + T::Staking::bonding_duration()]
|
||||
);
|
||||
assert_eq!(
|
||||
member.unbonding_eras.values().cloned().collect::<Vec<_>>(),
|
||||
vec![all_points]
|
||||
);
|
||||
}
|
||||
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
// Set the current era
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Add `s` count of slashing spans to storage.
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(pool_account);
|
||||
}: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s)
|
||||
verify {
|
||||
// The joiners funds didn't change
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(joiner);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s)
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into()
|
||||
);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// We set the pool to the destroying state so the depositor can leave
|
||||
BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = PoolState::Destroying;
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Unbond the creator
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
// Simulate some rewards so we can check if the rewards storage is cleaned up. We check this
|
||||
// here to ensure the complete flow for destroying a pool works - the reward pool account
|
||||
// should never exist by time the depositor withdraws so we test that it gets cleaned
|
||||
// up when unbonding.
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
Zero::zero()
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&pool_account),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Some last checks that storage items we expect to get cleaned up are present
|
||||
assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(BondedPools::<T>::contains_key(&1));
|
||||
assert!(SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(RewardPools::<T>::contains_key(&1));
|
||||
assert!(PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s)
|
||||
verify {
|
||||
// Pool removal worked
|
||||
assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(!BondedPools::<T>::contains_key(&1));
|
||||
assert!(!SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(!RewardPools::<T>::contains_key(&1));
|
||||
assert!(!PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&pool_account));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
// Funds where transferred back correctly
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
// gets bond back + rewards collecting when unbonding
|
||||
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
|
||||
);
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// Give the depositor some balance to bond
|
||||
CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
|
||||
|
||||
// Make sure no Pools exist at a pre-condition for our verify checks
|
||||
assert_eq!(RewardPools::<T>::count(), 0);
|
||||
assert_eq!(BondedPools::<T>::count(), 0);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: _(
|
||||
RuntimeOrigin::Signed(depositor.clone()),
|
||||
min_create_bond,
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup
|
||||
)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
nominate {
|
||||
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
// actual validators
|
||||
let validators: Vec<_> = (0..n)
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
|
||||
});
|
||||
|
||||
let caller = account("caller", 0, USER_SEED);
|
||||
whitelist_account!(caller);
|
||||
}:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying)
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 1 .. <T as ghost_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone())
|
||||
verify {
|
||||
assert_eq!(Metadata::<T>::get(&1), metadata);
|
||||
}
|
||||
|
||||
set_configs {
|
||||
}:_(
|
||||
RuntimeOrigin::Root,
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(Perbill::max_value())
|
||||
) verify {
|
||||
assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
|
||||
}
|
||||
|
||||
update_roles {
|
||||
let first_id = ghost_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
RuntimeOrigin::Signed(root.clone()),
|
||||
first_id,
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone())
|
||||
) verify {
|
||||
assert_eq!(
|
||||
ghost_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
|
||||
ghost_nomination_pools::PoolRoles {
|
||||
depositor: root,
|
||||
nominator: Some(random.clone()),
|
||||
bouncer: Some(random.clone()),
|
||||
root: Some(random),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
assert_ok!(T::Staking::nominate(&pool_account, validators));
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
|
||||
verify {
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
|
||||
}
|
||||
|
||||
set_commission {
|
||||
// Create a pool - do not set a commission yet.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
// set a max commission
|
||||
Pools::<T>::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap();
|
||||
// set a change rate
|
||||
Pools::<T>::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into(),
|
||||
}).unwrap();
|
||||
// set a claim permission to an account.
|
||||
Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(depositor.clone()))
|
||||
).unwrap();
|
||||
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(20), depositor.clone())),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into()
|
||||
}),
|
||||
throttle_from: Some(1u32.into()),
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_max {
|
||||
// Create a pool, setting a commission that will update when max commission is set.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50)));
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(50), depositor)),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: None,
|
||||
throttle_from: Some(0u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_change_rate {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
})
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
}),
|
||||
throttle_from: Some(1_u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_claim_permission {
|
||||
// Create a pool.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: None,
|
||||
throttle_from: None,
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_claim_permission {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Join pool
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
}:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned)
|
||||
verify {
|
||||
assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
|
||||
}
|
||||
|
||||
claim_commission {
|
||||
let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// member claims a payout to make some commission available.
|
||||
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
|
||||
// set a claim permission to an account.
|
||||
let _ = Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(claimer))
|
||||
);
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
adjust_pool_deposit {
|
||||
// Create a pool
|
||||
let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
|
||||
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_err());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1)
|
||||
verify {
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
@ -1,205 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::VoterBagsListInstance;
|
||||
use frame_election_provider_support::VoteWeight;
|
||||
use frame_support::{derive_impl, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
BuildStorage, FixedU128, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u128;
|
||||
type Nonce = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = Nonce;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: Balance = 10;
|
||||
}
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
}
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = ();
|
||||
type RewardRemainder = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = ConstU32<3>;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type MaxExposurePageSize = ConstU32<64>;
|
||||
type ElectionProvider =
|
||||
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||
type MaxControllersInDeprecationBatch = ConstU32<100>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type EventListeners = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
pub const MaxPointsToBalance: u8 = 10;
|
||||
}
|
||||
|
||||
impl ghost_nomination_pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = FixedU128;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxPointsToBalance = MaxPointsToBalance;
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
}
|
||||
|
||||
impl crate::Config for Runtime {}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Timestamp: pallet_timestamp,
|
||||
Balances: pallet_balances,
|
||||
Staking: pallet_staking,
|
||||
VoterList: pallet_bags_list::<Instance1>,
|
||||
Pools: ghost_nomination_pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = ghost_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(3),
|
||||
max_members: Some(3 * 3),
|
||||
global_max_commission: Some(Perbill::from_percent(50)),
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
sp_io::TestExternalities::from(storage)
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-fuzzer"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of Fuzzer for fixed point arithmetic primitives."
|
||||
documentation = "https://docs.rs/sp-arithmetic-fuzzer"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = "0.5.54"
|
||||
|
||||
ghost-nomination-pools = { workspace = true, features = ["fuzzing"] }
|
||||
|
||||
frame-system = { workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
|
||||
sp-runtime = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
sp-tracing = { workspace = true }
|
||||
|
||||
rand = { workspace = true, features = ["small_rng"] }
|
||||
log = { workspace = true, default-features = true }
|
||||
|
||||
[[bin]]
|
||||
name = "call"
|
||||
path = "src/call.rs"
|
||||
@ -1,354 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Running
|
||||
//! Running this fuzzer can be done with `cargo hfuzz run call`. `honggfuzz` CLI
|
||||
//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
|
||||
//!
|
||||
//! # Debugging a panic
|
||||
//! Once a panic is found, it can be debugged with
|
||||
//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/call/*.fuzz`.
|
||||
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, GetCallName, UnfilteredDispatchable},
|
||||
};
|
||||
use honggfuzz::fuzz;
|
||||
use pallet_nomination_pools::{
|
||||
log,
|
||||
mock::*,
|
||||
pallet as pools,
|
||||
pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers},
|
||||
BondExtra, BondedPool, GlobalMaxCommission, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool,
|
||||
MaxPools, MinCreateBond, MinJoinBond, PoolId,
|
||||
};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use sp_runtime::{assert_eq_error_rate, Perbill, Perquintill};
|
||||
|
||||
const ERA: BlockNumber = 1000;
|
||||
const MAX_ED_MULTIPLE: Balance = 10_000;
|
||||
const MIN_ED_MULTIPLE: Balance = 10;
|
||||
|
||||
// not quite elegant, just to make it available in random_signed_origin.
|
||||
const REWARD_AGENT_ACCOUNT: AccountId = 42;
|
||||
|
||||
/// Grab random accounts, either known ones, or new ones.
|
||||
fn random_signed_origin<R: Rng>(rng: &mut R) -> (RuntimeOrigin, AccountId) {
|
||||
let count = PoolMembers::<T>::count();
|
||||
if rng.gen::<bool>() && count > 0 {
|
||||
// take an existing account.
|
||||
let skip = rng.gen_range(0..count as usize);
|
||||
|
||||
// this is tricky: the account might be our reward agent, which we never want to be
|
||||
// randomly chosen here. Try another one, or, if it is only our agent, return a random
|
||||
// one nonetheless.
|
||||
let candidate = PoolMembers::<T>::iter_keys().skip(skip).take(1).next().unwrap();
|
||||
let acc =
|
||||
if candidate == REWARD_AGENT_ACCOUNT { rng.gen::<AccountId>() } else { candidate };
|
||||
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
} else {
|
||||
// create a new account
|
||||
let acc = rng.gen::<AccountId>();
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_ed_multiple<R: Rng>(rng: &mut R) -> Balance {
|
||||
let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE);
|
||||
ExistentialDeposit::get() * multiple
|
||||
}
|
||||
|
||||
fn fund_account<R: Rng>(rng: &mut R, account: &AccountId) {
|
||||
let target_amount = random_ed_multiple(rng);
|
||||
if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) {
|
||||
let _ = Balances::deposit_creating(account, top_up);
|
||||
}
|
||||
assert!(Balances::free_balance(account) >= target_amount);
|
||||
}
|
||||
|
||||
fn random_existing_pool<R: Rng>(mut rng: &mut R) -> Option<PoolId> {
|
||||
BondedPools::<T>::iter_keys().collect::<Vec<_>>().choose(&mut rng).map(|x| *x)
|
||||
}
|
||||
|
||||
fn random_call<R: Rng>(mut rng: &mut R) -> (pools::Call<T>, RuntimeOrigin) {
|
||||
let op = rng.gen::<usize>();
|
||||
let mut op_count = <pools::Call<T> as GetCallName>::get_call_names().len();
|
||||
// Exclude set_state, set_metadata, set_configs, update_roles and chill.
|
||||
op_count -= 5;
|
||||
|
||||
match op % op_count {
|
||||
0 => {
|
||||
// join
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::join { amount, pool_id }, origin)
|
||||
},
|
||||
1 => {
|
||||
// bond_extra
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let extra = if rng.gen::<bool>() {
|
||||
BondExtra::Rewards
|
||||
} else {
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
BondExtra::FreeBalance(amount)
|
||||
};
|
||||
(PoolsCall::<T>::bond_extra { extra }, origin)
|
||||
},
|
||||
2 => {
|
||||
// claim_payout
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::claim_payout {}, origin)
|
||||
},
|
||||
3 => {
|
||||
// unbond
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::unbond { member_account: who, unbonding_points: amount }, origin)
|
||||
},
|
||||
4 => {
|
||||
// pool_withdraw_unbonded
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin)
|
||||
},
|
||||
5 => {
|
||||
// withdraw_unbonded
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
(
|
||||
PoolsCall::<T>::withdraw_unbonded { member_account: who, num_slashing_spans: 0 },
|
||||
origin,
|
||||
)
|
||||
},
|
||||
6 => {
|
||||
// create
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let root = who;
|
||||
let bouncer = who;
|
||||
let nominator = who;
|
||||
(PoolsCall::<T>::create { amount, root, bouncer, nominator }, origin)
|
||||
},
|
||||
7 => {
|
||||
// nominate
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let validators = Default::default();
|
||||
(PoolsCall::<T>::nominate { pool_id, validators }, origin)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RewardAgent {
|
||||
who: AccountId,
|
||||
pool_id: Option<PoolId>,
|
||||
expected_reward: Balance,
|
||||
}
|
||||
|
||||
// TODO: inject some slashes into the game.
|
||||
impl RewardAgent {
|
||||
fn new(who: AccountId) -> Self {
|
||||
Self { who, ..Default::default() }
|
||||
}
|
||||
|
||||
fn join(&mut self) {
|
||||
if self.pool_id.is_some() {
|
||||
return
|
||||
}
|
||||
let pool_id = LastPoolId::<T>::get();
|
||||
let amount = 10 * ExistentialDeposit::get();
|
||||
let origin = RuntimeOrigin::signed(self.who);
|
||||
let _ = Balances::deposit_creating(&self.who, 10 * amount);
|
||||
self.pool_id = Some(pool_id);
|
||||
log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount);
|
||||
assert_ok!(PoolsCall::join::<T> { amount, pool_id }.dispatch_bypass_filter(origin));
|
||||
}
|
||||
|
||||
fn claim_payout(&mut self) {
|
||||
// 10 era later, we claim our payout. We expect our income to be roughly what we
|
||||
// calculated.
|
||||
if !PoolMembers::<T>::contains_key(&self.who) {
|
||||
log!(warn, "reward agent is not in the pool yet, cannot claim");
|
||||
return
|
||||
}
|
||||
let pre = Balances::free_balance(&42);
|
||||
let origin = RuntimeOrigin::signed(42);
|
||||
assert_ok!(PoolsCall::<T>::claim_payout {}.dispatch_bypass_filter(origin));
|
||||
let post = Balances::free_balance(&42);
|
||||
|
||||
let income = post - pre;
|
||||
log::info!(
|
||||
target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}",
|
||||
income,
|
||||
self.expected_reward,
|
||||
);
|
||||
assert_eq_error_rate!(income, self.expected_reward, 10);
|
||||
self.expected_reward = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reward_agent = RewardAgent::new(REWARD_AGENT_ACCOUNT);
|
||||
sp_tracing::try_init_simple();
|
||||
let mut ext = sp_io::TestExternalities::new_empty();
|
||||
let mut events_histogram = Vec::<(PoolsEvents<T>, u32)>::default();
|
||||
let mut iteration = 0 as BlockNumber;
|
||||
let mut ok = 0;
|
||||
let mut err = 0;
|
||||
|
||||
let dot: Balance = (10 as Balance).pow(10);
|
||||
ExistentialDeposit::set(dot);
|
||||
BondingDuration::set(8);
|
||||
|
||||
ext.execute_with(|| {
|
||||
MaxPoolMembers::<T>::set(Some(10_000));
|
||||
MaxPoolMembersPerPool::<T>::set(Some(1000));
|
||||
MaxPools::<T>::set(Some(1_000));
|
||||
GlobalMaxCommission::<T>::set(Some(Perbill::from_percent(25)));
|
||||
|
||||
MinCreateBond::<T>::set(10 * ExistentialDeposit::get());
|
||||
MinJoinBond::<T>::set(5 * ExistentialDeposit::get());
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
loop {
|
||||
fuzz!(|seed: [u8; 32]| {
|
||||
use ::rand::{rngs::SmallRng, SeedableRng};
|
||||
let mut rng = SmallRng::from_seed(seed);
|
||||
|
||||
ext.execute_with(|| {
|
||||
let (call, origin) = random_call(&mut rng);
|
||||
let outcome = call.clone().dispatch_bypass_filter(origin.clone());
|
||||
iteration += 1;
|
||||
match outcome {
|
||||
Ok(_) => ok += 1,
|
||||
Err(_) => err += 1,
|
||||
};
|
||||
|
||||
log!(
|
||||
trace,
|
||||
"iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err",
|
||||
iteration,
|
||||
call,
|
||||
origin,
|
||||
outcome,
|
||||
ok,
|
||||
err,
|
||||
);
|
||||
|
||||
// possibly join the reward_agent
|
||||
if iteration > ERA / 2 && BondedPools::<T>::count() > 0 {
|
||||
reward_agent.join();
|
||||
}
|
||||
// and possibly roughly every 4 era, trigger payout for the agent. Doing this more
|
||||
// frequent is also harmless.
|
||||
if rng.gen_range(0..(4 * ERA)) == 0 {
|
||||
reward_agent.claim_payout();
|
||||
}
|
||||
|
||||
// execute sanity checks at a fixed interval, possibly on every block.
|
||||
if iteration %
|
||||
(std::env::var("SANITY_CHECK_INTERVAL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok()))
|
||||
.unwrap_or(1) == 0
|
||||
{
|
||||
log!(info, "running sanity checks at {}", iteration);
|
||||
Pools::do_try_state(u8::MAX).unwrap();
|
||||
}
|
||||
|
||||
// collect and reset events.
|
||||
System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| {
|
||||
if let pallet_nomination_pools::mock::RuntimeEvent::Pools(inner) = e {
|
||||
Some(inner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(|e| {
|
||||
if let Some((_, c)) = events_histogram
|
||||
.iter_mut()
|
||||
.find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e))
|
||||
{
|
||||
*c += 1;
|
||||
} else {
|
||||
events_histogram.push((e, 1))
|
||||
}
|
||||
});
|
||||
System::reset_events();
|
||||
|
||||
// trigger an era change, and check the status of the reward agent.
|
||||
if iteration % ERA == 0 {
|
||||
CurrentEra::mutate(|c| *c += 1);
|
||||
BondedPools::<T>::iter().for_each(|(id, _)| {
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
let _ =
|
||||
Balances::deposit_creating(&Pools::create_reward_account(id), amount);
|
||||
// if we just paid out the reward agent, let's calculate how much we expect
|
||||
// our reward agent to have earned.
|
||||
if reward_agent.pool_id.map_or(false, |mid| mid == id) {
|
||||
let all_points = BondedPool::<T>::get(id).map(|p| p.points).unwrap();
|
||||
let member_points =
|
||||
PoolMembers::<T>::get(reward_agent.who).map(|m| m.points).unwrap();
|
||||
let agent_share = Perquintill::from_rational(member_points, all_points);
|
||||
log::info!(
|
||||
target: "reward-agent",
|
||||
"🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}",
|
||||
amount,
|
||||
agent_share,
|
||||
agent_share * amount,
|
||||
);
|
||||
reward_agent.expected_reward += agent_share * amount;
|
||||
}
|
||||
});
|
||||
|
||||
log!(
|
||||
info,
|
||||
"iteration {}, {} pools, {} members, {} ok {} err, events = {:?}",
|
||||
iteration,
|
||||
BondedPools::<T>::count(),
|
||||
PoolMembers::<T>::count(),
|
||||
ok,
|
||||
err,
|
||||
events_histogram
|
||||
.iter()
|
||||
.map(|(x, c)| (
|
||||
format!("{:?}", x)
|
||||
.split(" ")
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap(),
|
||||
c,
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-nomination-pools-runtime-api"
|
||||
version = "23.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "Fork of Runtime API for nomination-pools FRAME pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true, default-features = false, features = ["derive"] }
|
||||
sp-api = { workspace = true, default-features = false }
|
||||
sp-std = { workspace = true, default-features = false }
|
||||
ghost-nomination-pools = { workspace = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "ghost-nomination-pools/std", "sp-api/std", "sp-std/std"]
|
||||
@ -1,3 +0,0 @@
|
||||
Runtime API definition for nomination-pools pallet.
|
||||
|
||||
License: Apache-2.0
|
||||
@ -1,42 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Runtime API definition for nomination-pools pallet.
|
||||
//! Currently supports only one rpc endpoint.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Codec;
|
||||
use ghost_nomination_pools::PoolId;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// Runtime api for accessing information about nomination pools.
|
||||
pub trait NominationPoolsApi<AccountId, Balance>
|
||||
where
|
||||
AccountId: Codec,
|
||||
Balance: Codec,
|
||||
{
|
||||
/// Returns the pending rewards for the member that the AccountId was given for.
|
||||
fn pending_rewards(who: AccountId) -> Balance;
|
||||
|
||||
/// Returns the equivalent balance of `points` for a given pool.
|
||||
fn points_to_balance(pool_id: PoolId, points: Balance) -> Balance;
|
||||
|
||||
/// Returns the equivalent points of `new_funds` for a given pool.
|
||||
fn balance_to_points(pool_id: PoolId, new_funds: Balance) -> Balance;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,574 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate::{self as pools};
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::fungible::Mutate,
|
||||
PalletId,
|
||||
};
|
||||
use frame_system::{EnsureSignedBy, RawOrigin};
|
||||
use sp_runtime::{BuildStorage, FixedU128};
|
||||
use sp_staking::{OnStakingUpdate, Stake};
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
pub type AccountId = u128;
|
||||
pub type Balance = u128;
|
||||
pub type RewardCounter = FixedU128;
|
||||
// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests
|
||||
// as well, e.g. `StorageItem::<T>::get()`.
|
||||
pub type T = Runtime;
|
||||
pub type Currency = <T as Config>::Currency;
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_bonded_account() -> AccountId {
|
||||
Pools::create_bonded_account(1)
|
||||
}
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_reward_account() -> AccountId {
|
||||
Pools::create_reward_account(1)
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MinJoinBondConfig: Balance = 2;
|
||||
pub static CurrentEra: EraIndex = 0;
|
||||
pub static BondingDuration: EraIndex = 3;
|
||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
// map from a user to a vec of eras and amounts being unlocked in each era.
|
||||
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub static MaxUnbonding: u32 = 8;
|
||||
pub static StakingMinBond: Balance = 10;
|
||||
pub storage Nominations: Option<Vec<AccountId>> = None;
|
||||
}
|
||||
pub struct StakingMock;
|
||||
|
||||
impl StakingMock {
|
||||
pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.insert(who, bonded);
|
||||
BondedBalanceMap::set(&x)
|
||||
}
|
||||
/// Mimics a slash towards a pool specified by `pool_id`.
|
||||
/// This reduces the bonded balance of a pool by `amount` and calls [`Pools::on_slash`] to
|
||||
/// enact changes in the nomination-pool pallet.
|
||||
///
|
||||
/// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for
|
||||
/// `slashed_unlocking`.
|
||||
pub fn slash_by(pool_id: PoolId, amount: Balance) {
|
||||
let acc = Pools::create_bonded_account(pool_id);
|
||||
let bonded = BondedBalanceMap::get();
|
||||
let pre_total = bonded.get(&acc).unwrap();
|
||||
Self::set_bonded_balance(acc, pre_total - amount);
|
||||
Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_staking::StakingInterface for StakingMock {
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
type CurrencyToVote = ();
|
||||
|
||||
fn minimum_nominator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
fn minimum_validator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
|
||||
fn desired_validator_count() -> u32 {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn current_era() -> EraIndex {
|
||||
CurrentEra::get()
|
||||
}
|
||||
|
||||
fn bonding_duration() -> EraIndex {
|
||||
BondingDuration::get()
|
||||
}
|
||||
|
||||
fn status(
|
||||
_: &Self::AccountId,
|
||||
) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
|
||||
Nominations::get()
|
||||
.map(|noms| sp_staking::StakerStatus::Nominator(noms))
|
||||
.ok_or(DispatchError::Other("NotStash"))
|
||||
}
|
||||
|
||||
fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.get_mut(who).map(|v| *v += extra);
|
||||
BondedBalanceMap::set(&x);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
|
||||
BondedBalanceMap::set(&x);
|
||||
|
||||
let era = Self::current_era();
|
||||
let unlocking_at = era + Self::bonding_duration();
|
||||
let mut y = UnbondingBalanceMap::get();
|
||||
y.entry(*who).or_insert(Default::default()).push((unlocking_at, amount));
|
||||
UnbondingBalanceMap::set(&y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_payee(_stash: &Self::AccountId, _reward_acc: &Self::AccountId) -> DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn chill(_: &Self::AccountId) -> sp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
||||
let mut unbonding_map = UnbondingBalanceMap::get();
|
||||
|
||||
// closure to calculate the current unlocking funds across all eras/accounts.
|
||||
let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance {
|
||||
pair.iter()
|
||||
.try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount))
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?;
|
||||
let unlocking_before = unlocking(&staker_map);
|
||||
|
||||
let current_era = Self::current_era();
|
||||
|
||||
staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
|
||||
|
||||
// if there was a withdrawal, notify the pallet.
|
||||
Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map)));
|
||||
|
||||
UnbondingBalanceMap::set(&unbonding_map);
|
||||
Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty())
|
||||
}
|
||||
|
||||
fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult {
|
||||
StakingMock::set_bonded_balance(*stash, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nominate(_: &Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
|
||||
Nominations::set(&Some(nominations));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(_: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
|
||||
Nominations::get()
|
||||
}
|
||||
|
||||
fn stash_by_ctrl(_controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
|
||||
match (UnbondingBalanceMap::get().get(who), BondedBalanceMap::get().get(who).copied()) {
|
||||
(None, None) => Err(DispatchError::Other("balance not found")),
|
||||
(Some(v), None) => Ok(Stake {
|
||||
total: v.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)),
|
||||
active: 0,
|
||||
}),
|
||||
(None, Some(v)) => Ok(Stake { total: v, active: v }),
|
||||
(Some(a), Some(b)) => Ok(Stake {
|
||||
total: a.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)) + b,
|
||||
active: b,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn election_ongoing() -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn force_unstake(_who: Self::AccountId) -> sp_runtime::DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add_era_stakers(
|
||||
_current_era: &EraIndex,
|
||||
_stash: &Self::AccountId,
|
||||
_exposures: Vec<(Self::AccountId, Self::Balance)>,
|
||||
) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn set_current_era(_era: EraIndex) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn max_exposure_page_size() -> sp_staking::Page {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn slash_reward_fraction() -> Perbill {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type SS58Prefix = ();
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = u64;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type DbWeight = ();
|
||||
type BlockLength = ();
|
||||
type BlockWeights = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = frame_support::traits::ConstU32<1024>;
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<U256, Balance> for U256ToBalance {
|
||||
fn convert(n: U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 2;
|
||||
pub static MaxMetadataLen: u32 = 2;
|
||||
pub static CheckLevel: u8 = 255;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const Admin: u128 = 42;
|
||||
}
|
||||
|
||||
impl pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = RewardCounter;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = StakingMock;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxMetadataLen = MaxMetadataLen;
|
||||
type MaxUnbonding = MaxUnbonding;
|
||||
type MaxPointsToBalance = frame_support::traits::ConstU8<10>;
|
||||
type AdminOrigin = EnsureSignedBy<Admin, AccountId>;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Pools: pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub struct ExtBuilder {
|
||||
members: Vec<(AccountId, Balance)>,
|
||||
max_members: Option<u32>,
|
||||
max_members_per_pool: Option<u32>,
|
||||
global_max_commission: Option<Perbill>,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
members: Default::default(),
|
||||
max_members: Some(4),
|
||||
max_members_per_pool: Some(3),
|
||||
global_max_commission: Some(Perbill::from_percent(90)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "fuzzing", allow(dead_code))]
|
||||
impl ExtBuilder {
|
||||
// Add members to pool 0.
|
||||
pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self {
|
||||
self.members = members;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ed(self, ed: Balance) -> Self {
|
||||
ExistentialDeposit::set(ed);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_bond(self, min: Balance) -> Self {
|
||||
StakingMinBond::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_join_bond(self, min: Balance) -> Self {
|
||||
MinJoinBondConfig::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_check(self, level: u8) -> Self {
|
||||
CheckLevel::set(level);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members_per_pool(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members_per_pool = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn global_max_commission(mut self, commission: Option<Perbill>) -> Self {
|
||||
self.global_max_commission = commission;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage =
|
||||
frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
|
||||
let _ = crate::GenesisConfig::<Runtime> {
|
||||
min_join_bond: MinJoinBondConfig::get(),
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(2),
|
||||
max_members_per_pool: self.max_members_per_pool,
|
||||
max_members: self.max_members,
|
||||
global_max_commission: self.global_max_commission,
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// make a pool
|
||||
let amount_to_bond = Pools::depositor_min_bond();
|
||||
Currency::set_balance(&10, amount_to_bond * 5);
|
||||
assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
|
||||
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
|
||||
let last_pool = LastPoolId::<Runtime>::get();
|
||||
for (account_id, bonded) in self.members {
|
||||
<Runtime as Config>::Currency::set_balance(&account_id, bonded * 2);
|
||||
assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool));
|
||||
}
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn build_and_execute(self, test: impl FnOnce()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
Pools::do_try_state(CheckLevel::get()).unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) {
|
||||
BondedPools::<Runtime>::try_mutate(pool_id, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = state;
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
storage PoolsEvents: u32 = 0;
|
||||
storage BalancesEvents: u32 = 0;
|
||||
}
|
||||
|
||||
/// Helper to run a specified amount of blocks.
|
||||
pub fn run_blocks(n: u64) {
|
||||
let current_block = System::block_number();
|
||||
run_to_block(n + current_block);
|
||||
}
|
||||
|
||||
/// Helper to run to a specific block.
|
||||
pub fn run_to_block(n: u64) {
|
||||
let current_block = System::block_number();
|
||||
assert!(n > current_block);
|
||||
while System::block_number() < n {
|
||||
Pools::on_finalize(System::block_number());
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
Pools::on_initialize(System::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
/// All events of this pallet.
|
||||
pub fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = PoolsEvents::get();
|
||||
PoolsEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// All events of the `Balances` pallet.
|
||||
pub fn balances_events_since_last_call() -> Vec<pallet_balances::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = BalancesEvents::get();
|
||||
BalancesEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// Same as `fully_unbond`, in permissioned setting.
|
||||
pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult {
|
||||
let points = PoolMembers::<Runtime>::get(member)
|
||||
.map(|d| d.active_points())
|
||||
.unwrap_or_default();
|
||||
Pools::unbond(RuntimeOrigin::signed(member), member, points)
|
||||
}
|
||||
|
||||
pub fn pending_rewards_for_delegator(delegator: AccountId) -> Balance {
|
||||
let member = PoolMembers::<T>::get(delegator).unwrap();
|
||||
let bonded_pool = BondedPools::<T>::get(member.pool_id).unwrap();
|
||||
let reward_pool = RewardPools::<T>::get(member.pool_id).unwrap();
|
||||
|
||||
assert!(!bonded_pool.points.is_zero());
|
||||
|
||||
let commission = bonded_pool.commission.current();
|
||||
let current_rc = reward_pool
|
||||
.current_reward_counter(member.pool_id, bonded_pool.points, commission)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
member.pending_rewards(current_rc).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum RewardImbalance {
|
||||
// There is no reward deficit.
|
||||
Surplus(Balance),
|
||||
// There is a reward deficit.
|
||||
Deficit(Balance),
|
||||
}
|
||||
|
||||
pub fn pool_pending_rewards(pool: PoolId) -> Result<BalanceOf<T>, sp_runtime::DispatchError> {
|
||||
let bonded_pool = BondedPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
let reward_pool = RewardPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
|
||||
let current_rc = if !bonded_pool.points.is_zero() {
|
||||
let commission = bonded_pool.commission.current();
|
||||
reward_pool.current_reward_counter(pool, bonded_pool.points, commission)?.0
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
Ok(PoolMembers::<T>::iter()
|
||||
.filter(|(_, d)| d.pool_id == pool)
|
||||
.map(|(_, d)| d.pending_rewards(current_rc).unwrap_or_default())
|
||||
.fold(0u32.into(), |acc: BalanceOf<T>, x| acc.saturating_add(x)))
|
||||
}
|
||||
|
||||
pub fn reward_imbalance(pool: PoolId) -> RewardImbalance {
|
||||
let pending_rewards = pool_pending_rewards(pool).expect("pool should exist");
|
||||
let current_balance = RewardPool::<Runtime>::current_balance(pool);
|
||||
|
||||
if pending_rewards > current_balance {
|
||||
RewardImbalance::Deficit(pending_rewards - current_balance)
|
||||
} else {
|
||||
RewardImbalance::Surplus(current_balance - pending_rewards)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn u256_to_balance_convert_works() {
|
||||
assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero());
|
||||
assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn u256_to_balance_convert_panics_correctly() {
|
||||
U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_to_u256_convert_works() {
|
||||
assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero());
|
||||
assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
[package]
|
||||
name = "pallet-nomination-pools-test-staking"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://substrate.io"
|
||||
repository.workspace = true
|
||||
description = "FRAME nomination pools pallet tests with the staking pallet"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dev-dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] }
|
||||
scale-info = { version = "2.11.1", features = ["derive"] }
|
||||
|
||||
sp-runtime = { path = "../../../primitives/runtime" }
|
||||
sp-io = { path = "../../../primitives/io" }
|
||||
sp-std = { path = "../../../primitives/std" }
|
||||
sp-staking = { path = "../../../primitives/staking" }
|
||||
sp-core = { path = "../../../primitives/core" }
|
||||
|
||||
frame-system = { path = "../../system" }
|
||||
frame-support = { path = "../../support" }
|
||||
frame-election-provider-support = { path = "../../election-provider-support" }
|
||||
|
||||
pallet-timestamp = { path = "../../timestamp" }
|
||||
pallet-balances = { path = "../../balances" }
|
||||
pallet-staking = { path = "../../staking" }
|
||||
pallet-bags-list = { path = "../../bags-list" }
|
||||
pallet-staking-reward-curve = { path = "../../staking/reward-curve" }
|
||||
pallet-nomination-pools = { path = ".." }
|
||||
|
||||
sp-tracing = { path = "../../../primitives/tracing" }
|
||||
log = { workspace = true, default-features = true }
|
||||
@ -1,823 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod mock;
|
||||
|
||||
use frame_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use mock::*;
|
||||
use pallet_nomination_pools::{
|
||||
BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember,
|
||||
PoolMembers, PoolState,
|
||||
};
|
||||
use pallet_staking::{
|
||||
CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination,
|
||||
};
|
||||
use sp_runtime::{bounded_btree_map, traits::Zero};
|
||||
|
||||
#[test]
|
||||
fn pool_lifecycle_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
// have the pool nominate.
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// pool goes into destroying
|
||||
assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
|
||||
|
||||
// depositor cannot unbond yet.
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// now the members want to unbond.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 },
|
||||
]
|
||||
);
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
for e in 1..BondingDuration::get() {
|
||||
CurrentEra::<Runtime>::set(Some(e));
|
||||
assert_noop!(
|
||||
Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0),
|
||||
PoolsError::<Runtime>::CannotWithdrawAny
|
||||
);
|
||||
}
|
||||
|
||||
// members are now unlocked.
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
|
||||
|
||||
// depositor cannot still unbond
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// but members can now withdraw.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
assert!(PoolMembers::<Runtime>::get(20).is_none());
|
||||
assert!(PoolMembers::<Runtime>::get(21).is_none());
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 },
|
||||
]
|
||||
);
|
||||
|
||||
// as soon as all members have left, the depositor can try to unbond, but since the
|
||||
// min-nominator intention is set, they must chill first.
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
|
||||
pallet_staking::Error::<Runtime>::InsufficientBond
|
||||
);
|
||||
|
||||
assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Chilled { stash: POOL1_BONDED },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }]
|
||||
);
|
||||
|
||||
// waiting another bonding duration:
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1));
|
||||
|
||||
// pools is fully destroyed now.
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_chill_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::minimum_balance(), 5);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
// have the pool nominate.
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// in case depositor does not have more than `MinNominatorBond` staked, we can end up in
|
||||
// situation where a member unbonding would cause pool balance to drop below
|
||||
// `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is
|
||||
// increased after the pool is created.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
RuntimeOrigin::root(),
|
||||
pallet_staking::ConfigOp::Set(55), // minimum nominator bond
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
));
|
||||
|
||||
// members can unbond as long as total stake of the pool is above min nominator bond
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
|
||||
|
||||
// this member cannot unbond since it will cause `pool stake < MinNominatorBond`
|
||||
assert_noop!(
|
||||
Pools::unbond(RuntimeOrigin::signed(21), 21, 10),
|
||||
StakingError::<Runtime>::InsufficientBond,
|
||||
);
|
||||
|
||||
// members can call `chill` permissionlessly now
|
||||
assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1));
|
||||
|
||||
// now another member can unbond.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
|
||||
assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
|
||||
|
||||
// nominator can not resume nomination until depositor have enough stake
|
||||
assert_noop!(
|
||||
Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// other members joining pool does not affect the depositor's ability to resume nomination
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1));
|
||||
|
||||
assert_noop!(
|
||||
Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
|
||||
PoolsError::<Runtime>::MinimumBondNotMet,
|
||||
);
|
||||
|
||||
// depositor can bond extra stake
|
||||
assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
|
||||
|
||||
// `chill` can not be called permissionlessly anymore
|
||||
assert_noop!(
|
||||
Pools::chill(RuntimeOrigin::signed(20), 1),
|
||||
PoolsError::<Runtime>::NotNominator,
|
||||
);
|
||||
|
||||
// now nominator can resume nomination
|
||||
assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
|
||||
|
||||
// skip to make the unbonding period end.
|
||||
CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
|
||||
|
||||
// members can now withdraw.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Chilled { stash: POOL1_BONDED },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra
|
||||
StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_e2e() {
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<Runtime>::get(), 1);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Payee::<Runtime>::get(POOL1_BONDED),
|
||||
Some(RewardDestination::Account(POOL1_REWARD))
|
||||
);
|
||||
|
||||
// have two members join
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// now let's progress a bit.
|
||||
CurrentEra::<Runtime>::set(Some(1));
|
||||
|
||||
// 20 / 80 of the total funds are unlocked, and safe from any further slash.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(2));
|
||||
|
||||
// note: depositor cannot fully unbond at this point.
|
||||
// these funds will still get slashed.
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 },
|
||||
]
|
||||
);
|
||||
|
||||
// At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and
|
||||
// another 30 are active and vulnerable to slash. Let's slash half of them.
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
30,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
2, // slash era 2, affects chunks at era 5 onwards.
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 },
|
||||
// 30 has been slashed to 15 (15 slash)
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(3));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
|
||||
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(21).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
last_recorded_reward_counter: Zero::zero(),
|
||||
// the 10 points unlocked just now correspond to 5 points in the unbond pool.
|
||||
unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }]
|
||||
);
|
||||
|
||||
// now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free.
|
||||
CurrentEra::<Runtime>::set(Some(6));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
|
||||
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// 20 had unbonded 10 safely, and 10 got slashed by half.
|
||||
PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
|
||||
// 21 unbonded all of it after the slash
|
||||
PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
// a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }]
|
||||
);
|
||||
|
||||
// now, finally, we can unbond the depositor further than their current limit.
|
||||
assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
|
||||
PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 }
|
||||
]
|
||||
);
|
||||
|
||||
CurrentEra::<Runtime>::set(Some(9));
|
||||
assert_eq!(
|
||||
PoolMembers::<Runtime>::get(10).unwrap(),
|
||||
PoolMember {
|
||||
pool_id: 1,
|
||||
points: 0,
|
||||
last_recorded_reward_counter: Zero::zero(),
|
||||
unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
|
||||
}
|
||||
);
|
||||
// withdraw the depositor, they should lose 12 balance in total due to slash.
|
||||
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 },
|
||||
PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
|
||||
PoolsEvent::Destroyed { pool_id: 1 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_proportional() {
|
||||
// a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that
|
||||
// happened in era 100 should only affect the latter two.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(LastPoolId::<T>::get(), 1);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1));
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true },
|
||||
PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true },
|
||||
PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// now let's progress a lot.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
|
||||
// and unbond
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 21,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 128
|
||||
}]
|
||||
);
|
||||
|
||||
CurrentEra::<T>::set(Some(101));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 22,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 129
|
||||
}]
|
||||
);
|
||||
|
||||
// Apply a slash that happened in era 100. This is typically applied with a delay.
|
||||
// Of the total 100, 50 is slashed.
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
50,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// This era got slashed 12.5, which rounded up to 13.
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 },
|
||||
// This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more
|
||||
// slashed, and 12 is all the remaining slash
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 },
|
||||
// Bonded pool got slashed for 25, remaining 15 in it.
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_non_proportional_only_bonded_pool() {
|
||||
// A typical example where a pool member unbonds in era 99, and they can get away with a slash
|
||||
// that happened in era 100, as long as the pool has enough active bond to cover the slash. If
|
||||
// everything else in the slashing/staking system works, this should always be the case.
|
||||
// Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk
|
||||
// if it runs out of chunks that it thinks should be affected by the slash.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
|
||||
);
|
||||
|
||||
// progress and unbond.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
// slash for 30. This will be deducted only from the bonded pool.
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
30,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_slash_non_proportional_bonded_pool_and_chunks() {
|
||||
// An uncommon example where even though some funds are unlocked such that they should not be
|
||||
// affected by a slash, we still slash out of them. This should not happen at all. If a
|
||||
// nomination has unbonded, from the next era onwards, their exposure will drop, so if an era
|
||||
// happens in that era, then their share of that slash should naturally be less, such that only
|
||||
// their active ledger stake is enough to compensate it.
|
||||
new_test_ext().execute_with(|| {
|
||||
ExistentialDeposit::set(1);
|
||||
BondingDuration::set(28);
|
||||
assert_eq!(Balances::minimum_balance(), 1);
|
||||
assert_eq!(Staking::current_era(), None);
|
||||
|
||||
// create the pool, we know this has id 1.
|
||||
assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
PoolsEvent::Created { depositor: 10, pool_id: 1 },
|
||||
PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
|
||||
]
|
||||
);
|
||||
|
||||
// have two members join
|
||||
let bond = 20;
|
||||
assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
|
||||
);
|
||||
|
||||
// progress and unbond.
|
||||
CurrentEra::<T>::set(Some(99));
|
||||
assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![PoolsEvent::Unbonded {
|
||||
member: 20,
|
||||
pool_id: 1,
|
||||
balance: bond,
|
||||
points: bond,
|
||||
era: 127
|
||||
}]
|
||||
);
|
||||
|
||||
// slash 50. This will be deducted only from the bonded pool and one of the unbonding pools.
|
||||
CurrentEra::<T>::set(Some(100));
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
|
||||
pallet_staking::slashing::do_slash::<Runtime>(
|
||||
&POOL1_BONDED,
|
||||
50,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
staking_events_since_last_call(),
|
||||
vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
|
||||
);
|
||||
assert_eq!(
|
||||
pool_events_since_last_call(),
|
||||
vec![
|
||||
// out of 20, 10 was taken.
|
||||
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 },
|
||||
// out of 40, all was taken.
|
||||
PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 }
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -1,273 +0,0 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use frame_election_provider_support::VoteWeight;
|
||||
use frame_support::{
|
||||
assert_ok, derive_impl,
|
||||
pallet_prelude::*,
|
||||
parameter_types,
|
||||
traits::{ConstU64, ConstU8},
|
||||
PalletId,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
BuildStorage, FixedU128, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u128;
|
||||
type Nonce = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
pub(crate) type T = Runtime;
|
||||
|
||||
pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128;
|
||||
pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128;
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = Nonce;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<1>;
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub static BondingDuration: u32 = 3;
|
||||
}
|
||||
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = ();
|
||||
type RewardRemainder = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type MaxExposurePageSize = ConstU32<64>;
|
||||
type ElectionProvider =
|
||||
frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||
type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type MaxControllersInDeprecationBatch = ConstU32<100>;
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type EventListeners = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
impl pallet_nomination_pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = FixedU128;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type Staking = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type MaxPointsToBalance = ConstU8<10>;
|
||||
type PalletId = PoolsPalletId;
|
||||
type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: frame_system,
|
||||
Timestamp: pallet_timestamp,
|
||||
Balances: pallet_balances,
|
||||
Staking: pallet_staking,
|
||||
VoterList: pallet_bags_list::<Instance1>,
|
||||
Pools: pallet_nomination_pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = pallet_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(5),
|
||||
max_members: Some(3 * 5),
|
||||
global_max_commission: Some(Perbill::from_percent(90)),
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// set some limit for nominations.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
RuntimeOrigin::root(),
|
||||
pallet_staking::ConfigOp::Set(10), // minimum nominator bond
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
pallet_staking::ConfigOp::Noop,
|
||||
));
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static ObservedEventsPools: usize = 0;
|
||||
static ObservedEventsStaking: usize = 0;
|
||||
static ObservedEventsBalances: usize = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsPools::get();
|
||||
ObservedEventsPools::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsStaking::get();
|
||||
ObservedEventsStaking::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
12
pallets/slow-clap/Cargo.toml
Normal file → Executable file
12
pallets/slow-clap/Cargo.toml
Normal file → Executable 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",
|
||||
]
|
||||
|
||||
@ -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!(
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user