Compare commits
No commits in common. "main" and "pallet-slow-clap" have entirely different histories.
main
...
pallet-slo
1223
Cargo.lock
generated
1223
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@ -17,7 +17,7 @@ homepage.workspace = true
|
||||
[workspace.package]
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["571nky", "57r37ch", "f4750"]
|
||||
version = "0.9.3"
|
||||
version = "0.8.2"
|
||||
edition = "2021"
|
||||
homepage = "https://ghostchain.io"
|
||||
repository = "https://git.ghostchain.io/ghostchain/ghost-node"
|
||||
@ -67,21 +67,18 @@ serial_test = { version = "2.0.0" }
|
||||
color-eyre = { version = "0.6.1" }
|
||||
bs58 = { version = "0.5.0" }
|
||||
prometheus-parse = { version = "0.2.2" }
|
||||
k256 = { version = "0.13.0", default-features = false }
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
num-traits = { version = "0.2.17", default-features = false }
|
||||
libsecp256k1 = { version = "0.7", default-features = false }
|
||||
bip39 = { package = "parity-bip39", version = "2.0.1" }
|
||||
sha3 = { version = "0.10", default-features = false }
|
||||
sha2 = { version = "0.10.7", default-features = false }
|
||||
serde = { version = "1.0.197", default-features = false }
|
||||
serde_derive = { version = "1.0.117", default-features = false }
|
||||
serde_json = { version = "1.0.114", default-features = false }
|
||||
blake2-rfc = { version = "0.2.18", default-features = false }
|
||||
impl-serde = { version = "0.4.0", default-features = false }
|
||||
hex = { version = "0.4.3", default-features = false }
|
||||
strum = { version = "0.28.0", default-features = false }
|
||||
metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false }
|
||||
|
||||
scale-value = { version = "0.16.0" }
|
||||
@ -89,13 +86,6 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
|
||||
scale-info = { version = "2.5.0", default-features = false }
|
||||
subxt = { version = "0.37.0", default-features = false }
|
||||
|
||||
frost-secp256k1-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 +141,6 @@ pallet-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk
|
||||
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
@ -166,6 +155,8 @@ pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
|
||||
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
@ -193,6 +184,8 @@ generate-bags = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
|
||||
pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
|
||||
|
||||
# Substrate Client dependencies
|
||||
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" }
|
||||
@ -234,22 +227,15 @@ ghost-metrics = { path = "metrics", default-features = false }
|
||||
ghost-rpc = { path = "rpc", default-features = false }
|
||||
service = { package = "ghost-service", path = "service", default-features = false }
|
||||
ghost-traits = { path = "pallets/traits", default-features = false }
|
||||
ghost-helpers = { path = "pallets/helpers", default-features = false }
|
||||
ghost-networks = { path = "pallets/networks", default-features = false }
|
||||
ghost-governance = { path = "pallets/governance", default-features = false }
|
||||
ghost-weaver = { path = "pallets/weaver", default-features = false }
|
||||
ghost-exodus = { path = "pallets/exodus", default-features = false }
|
||||
ghost-claims = { path = "pallets/claims", default-features = false }
|
||||
ghost-slow-clap = { path = "pallets/slow-clap", default-features = false }
|
||||
ghost-sudo = { path = "pallets/sudo", default-features = false }
|
||||
ghost-nomination-pools = { path = "pallets/pools", default-features = false }
|
||||
ghost-nomination-pools-runtime-api = { path = "pallets/pools/runtime-api", default-features = false }
|
||||
ghost-nomination-pools-benchmarking = { path = "pallets/pools/benchmarking", default-features = false }
|
||||
ghost-nomination-pools-fuzzer = { path = "pallets/pools/benchmarking", default-features = false }
|
||||
|
||||
ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false }
|
||||
casper-runtime = { path = "runtime/casper", default-features = false }
|
||||
casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false }
|
||||
primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false }
|
||||
runtime-common = { package = "runtime-common", path = "runtime/common", default-features = false }
|
||||
runtime-common = { package = "ghost-runtime-common", path = "runtime/common", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
color-eyre = { workspace = true }
|
||||
@ -285,13 +271,9 @@ members = [
|
||||
"runtime/casper",
|
||||
"runtime/casper/constants",
|
||||
"pallets/networks",
|
||||
"pallets/governance",
|
||||
"pallets/pools",
|
||||
"pallets/claims",
|
||||
"pallets/slow-clap",
|
||||
"pallets/sudo",
|
||||
"pallets/exodus",
|
||||
"pallets/exodus/runtime-api",
|
||||
"pallets/helpers",
|
||||
"pallets/weaver",
|
||||
"utils/bags-list",
|
||||
"utils/chain-spec-builder",
|
||||
"utils/generate-bags",
|
||||
@ -344,12 +326,9 @@ crypto-mac = { opt-level = 3 }
|
||||
curve25519-dalek = { opt-level = 3 }
|
||||
ed25519-dalek = { opt-level = 3 }
|
||||
flate2 = { opt-level = 3 }
|
||||
frost-core = { opt-level = 3 }
|
||||
frost-secp256k1-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 +344,6 @@ parking_lot = { opt-level = 3 }
|
||||
parking_lot_core = { opt-level = 3 }
|
||||
percent-encoding = { opt-level = 3 }
|
||||
primitive-types = { opt-level = 3 }
|
||||
rand_chacha = { opt-level = 3 }
|
||||
ring = { opt-level = 3 }
|
||||
rustls = { opt-level = 3 }
|
||||
sha2 = { opt-level = 3 }
|
||||
|
||||
126
README.md
126
README.md
@ -150,8 +150,28 @@ 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 you are trying to install WebAssembly (wasm) on an ARM-based processor like the ones in Raspberry Pi try this instead:
|
||||
```bash
|
||||
rustup target add wasm32-unknown-unknown --toolchain stable-aarch64-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 +180,10 @@ the following command:
|
||||
rustup show
|
||||
```
|
||||
|
||||
```bash
|
||||
rustup +nightly show
|
||||
```
|
||||
|
||||
You should see something like this:
|
||||
|
||||

|
||||
@ -170,6 +194,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 +211,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 +226,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 +234,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 +320,7 @@ Only `port 30333` should be opened.
|
||||
|
||||
Press _CTRL+C_ to stop the node.
|
||||
|
||||
# Launching GHOST TestNet
|
||||
# Launching GHOST TestNet 3.1
|
||||
|
||||
Switch to main GIT branch:
|
||||
```bash
|
||||
@ -316,7 +348,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,7 +437,7 @@ sha256sum /etc/ghost/casper.json
|
||||
|
||||
You should see:
|
||||
```
|
||||
4645c7d39a64424d823f659d408070f8c90426117e7af147b0360697b2db7f4f /etc/ghost/casper.json
|
||||
ad653233c978bfd00c5e4525b17628632bc4319c5f5f4b4dcc2f050ef3c3d145
|
||||
```
|
||||
|
||||
Create running `ghost-node` service that starts on system boot using `--unit-file` flag.
|
||||
@ -372,7 +463,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
|
||||
```
|
||||
@ -417,6 +511,17 @@ Type `y` and press _Enter_ for create dedicated user for running `ghost-node`:
|
||||
|
||||

|
||||
|
||||
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 +535,7 @@ Start `ghost-node`:
|
||||
sudo systemctl start ghost-node
|
||||
```
|
||||
|
||||
Wait ~60 seconds!
|
||||
Wait 60 seconds!
|
||||
|
||||
Check node is started:
|
||||
```bash
|
||||
@ -440,6 +545,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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
use crate::cli::{Cli, Subcommand};
|
||||
use futures::future::TryFutureExt;
|
||||
use sc_cli::SubstrateCli;
|
||||
use service::{self, IdentifyVariant};
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
|
||||
use futures::future::TryFutureExt;
|
||||
use keyring::Sr25519Keyring;
|
||||
use sc_cli::SubstrateCli;
|
||||
use service::{
|
||||
self,
|
||||
benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder},
|
||||
IdentifyVariant,
|
||||
};
|
||||
|
||||
pub use crate::{error::Error, service::BlockId};
|
||||
#[cfg(feature = "pyroscope")]
|
||||
@ -64,11 +67,11 @@ impl SubstrateCli for Cli {
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper" => Box::new(service::chain_spec::casper_config()?),
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper-dev" | "casper-development" => {
|
||||
"casper-dev" | "dev" | "development" => {
|
||||
Box::new(service::chain_spec::casper_development_config()?)
|
||||
}
|
||||
#[cfg(feature = "casper-native")]
|
||||
"casper-local" => {
|
||||
"casper-local" | "local" => {
|
||||
Box::new(service::chain_spec::casper_local_testnet_config()?)
|
||||
}
|
||||
#[cfg(feature = "casper-native")]
|
||||
@ -123,10 +126,7 @@ where
|
||||
|
||||
set_ss58_version(chain_spec);
|
||||
|
||||
runner.run_node_until_exit(move |mut config| async move {
|
||||
config.offchain_worker.enabled = true;
|
||||
config.offchain_worker.indexing_enabled = true;
|
||||
|
||||
runner.run_node_until_exit(move |config| async move {
|
||||
let hwbench = (!cli.run.no_hardware_benchmarks)
|
||||
.then_some(config.database.path().map(|database_path| {
|
||||
let _ = std::fs::create_dir_all(&database_path);
|
||||
@ -276,13 +276,22 @@ pub fn run() -> Result<()> {
|
||||
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 db = backend.expose_db();
|
||||
@ -297,8 +306,6 @@ pub fn run() -> Result<()> {
|
||||
}),
|
||||
// These commands are very similar and can be handled in nearly the same way
|
||||
BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
|
||||
use service::benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder};
|
||||
|
||||
runner.sync_run(|mut config| {
|
||||
let (client, _, _, _) = service::new_chain_ops(&mut config)?;
|
||||
|
||||
@ -312,7 +319,7 @@ pub fn run() -> Result<()> {
|
||||
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(),
|
||||
);
|
||||
|
||||
@ -340,11 +347,21 @@ pub fn run() -> Result<()> {
|
||||
BenchmarkCmd::Pallet(cmd) => {
|
||||
set_ss58_version(chain_spec);
|
||||
|
||||
runner.sync_run(|config| {
|
||||
cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>(
|
||||
Some(config.chain_spec),
|
||||
).map_err(|e| Error::SubstrateCli(e))
|
||||
})
|
||||
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(
|
||||
|
||||
@ -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.4"
|
||||
description = "Ghost balance and rank claims based on EVM actions"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -20,6 +20,8 @@ frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
|
||||
pallet-ranked-collective = { workspace = true }
|
||||
pallet-vesting = { workspace = true }
|
||||
pallet-balances = { workspace = true }
|
||||
|
||||
sp-core = { features = ["serde"], workspace = true }
|
||||
@ -27,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",
|
||||
]
|
||||
59
pallets/claims/src/benchmarking.rs
Normal file
59
pallets/claims/src/benchmarking.rs
Normal file
@ -0,0 +1,59 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
use frame_support::dispatch::RawOrigin;
|
||||
use pallet_ranked_collective::Pallet as Club;
|
||||
|
||||
#[benchmark]
|
||||
fn claim() {
|
||||
let i = 1337u32;
|
||||
let ethereum_secret_key =
|
||||
libsecp256k1::SecretKey::parse(&keccak_256(&i.to_le_bytes())).unwrap();
|
||||
let eth_address = crate::secp_utils::eth(ðereum_secret_key);
|
||||
|
||||
let balance = CurrencyOf::<T, I>::minimum_balance();
|
||||
let pseudo_account: T::AccountId = Pallet::<T, I>::into_account_id(eth_address).unwrap();
|
||||
let _ = CurrencyOf::<T, I>::deposit_creating(&pseudo_account, balance);
|
||||
Total::<T, I>::put(balance);
|
||||
|
||||
let pseudo_rank = 5u16;
|
||||
let _ = Club::<T, I>::do_add_member_to_rank(pseudo_account.clone(), pseudo_rank, false);
|
||||
|
||||
let user_account: T::AccountId = account("user", i, 0);
|
||||
let signature =
|
||||
crate::secp_utils::sig::<T, I>(ðereum_secret_key, &user_account.encode());
|
||||
|
||||
let prev_balance = CurrencyOf::<T, I>::free_balance(&user_account);
|
||||
let prev_rank = Club::<T, I>::rank_of(&user_account);
|
||||
|
||||
#[extrinsic_call]
|
||||
claim(
|
||||
RawOrigin::Signed(user_account.clone()),
|
||||
eth_address,
|
||||
signature,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CurrencyOf::<T, I>::free_balance(&user_account),
|
||||
prev_balance + balance
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T, I>::free_balance(&pseudo_account),
|
||||
balance - balance
|
||||
);
|
||||
|
||||
let rank = match prev_rank {
|
||||
Some(current_rank) if pseudo_rank <= current_rank => Some(current_rank),
|
||||
_ => Some(pseudo_rank),
|
||||
};
|
||||
assert_eq!(Club::<T, I>::rank_of(&user_account), rank);
|
||||
assert_eq!(Club::<T, I>::rank_of(&pseudo_account), None);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,);
|
||||
}
|
||||
336
pallets/claims/src/lib.rs
Normal file
336
pallets/claims/src/lib.rs
Normal file
@ -0,0 +1,336 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame_support::{
|
||||
ensure,
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
Currency, ExistenceRequirement, Get, RankedMembers, RankedMembersSwapHandler,
|
||||
VestingSchedule,
|
||||
},
|
||||
DefaultNoBound,
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
pub use pallet::*;
|
||||
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
|
||||
use sp_runtime::traits::{BlockNumberProvider, CheckedDiv, CheckedSub};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
extern crate alloc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, string::String};
|
||||
|
||||
mod weights;
|
||||
pub use crate::weights::WeightInfo;
|
||||
|
||||
mod benchmarking;
|
||||
mod mock;
|
||||
mod secp_utils;
|
||||
mod tests;
|
||||
|
||||
/// An ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
|
||||
///
|
||||
/// This gets serialized to the 0x-prefixed hex representation.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)]
|
||||
pub struct EthereumAddress(pub [u8; 20]);
|
||||
|
||||
impl Serialize for EthereumAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
|
||||
serializer.serialize_str(&format!("0x{}", hex))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for EthereumAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base_string = String::deserialize(deserializer)?;
|
||||
let offset = if base_string.starts_with("0x") { 2 } else { 0 };
|
||||
let s = &base_string[offset..];
|
||||
if s.len() != 40 {
|
||||
Err(serde::de::Error::custom(
|
||||
"Bad length of Ethereum address (should be 42 including `0x`)",
|
||||
))?;
|
||||
}
|
||||
let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
|
||||
.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
|
||||
let mut r = Self::default();
|
||||
r.0.copy_from_slice(&raw);
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, TypeInfo)]
|
||||
pub struct EcdsaSignature(pub [u8; 65]);
|
||||
|
||||
impl PartialEq for EcdsaSignature {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
&self.0[..] == &other.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_std::fmt::Debug for EcdsaSignature {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
|
||||
write!(f, "EcdsaSignature({:?})", &self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
type CurrencyOf<T, I> = <<T as Config<I>>::VestingSchedule as VestingSchedule<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::Currency;
|
||||
|
||||
type BalanceOf<T, I> =
|
||||
<CurrencyOf<T, I> as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
type RankOf<T, I> = <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::Rank;
|
||||
type AccountIdOf<T, I> = <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::AccountId;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>:
|
||||
frame_system::Config + pallet_ranked_collective::Config<I>
|
||||
{
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>;
|
||||
type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
|
||||
type MemberSwappedHandler: RankedMembersSwapHandler<AccountIdOf<Self, I>, RankOf<Self, I>>;
|
||||
|
||||
#[pallet::constant]
|
||||
type Prefix: Get<&'static [u8]>;
|
||||
|
||||
#[pallet::constant]
|
||||
type MaximumWithdrawAmount: Get<BalanceOf<Self, I>>;
|
||||
|
||||
#[pallet::constant]
|
||||
type VestingBlocks: Get<u32>;
|
||||
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
Claimed {
|
||||
receiver: T::AccountId,
|
||||
donor: T::AccountId,
|
||||
amount: BalanceOf<T, I>,
|
||||
rank: Option<RankOf<T, I>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
InvalidEthereumSignature,
|
||||
InvalidEthereumAddress,
|
||||
NoBalanceToClaim,
|
||||
AddressDecodingFailed,
|
||||
ArithmeticError,
|
||||
PotUnderflow,
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
pub type Total<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
pub total: BalanceOf<T, I>,
|
||||
pub members_and_ranks: Vec<(T::AccountId, u16)>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
let cult_accounts: Vec<_> = self
|
||||
.members_and_ranks
|
||||
.iter()
|
||||
.map(|(account_id, rank)| {
|
||||
assert!(
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(
|
||||
account_id.clone(),
|
||||
*rank,
|
||||
false
|
||||
)
|
||||
.is_ok(),
|
||||
"error during adding and promotion"
|
||||
);
|
||||
account_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
self.members_and_ranks.len() == cult_accounts.len(),
|
||||
"duplicates in `members_and_ranks`"
|
||||
);
|
||||
|
||||
Total::<T, I>::put(self.total);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::claim())]
|
||||
pub fn claim(
|
||||
origin: OriginFor<T>,
|
||||
ethereum_address: EthereumAddress,
|
||||
ethereum_signature: EcdsaSignature,
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let data = who.using_encoded(to_ascii_hex);
|
||||
let recovered_address = Self::recover_ethereum_address(ðereum_signature, &data)
|
||||
.ok_or(Error::<T, I>::InvalidEthereumSignature)?;
|
||||
|
||||
ensure!(
|
||||
recovered_address == ethereum_address,
|
||||
Error::<T, I>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
Self::do_claim(who, ethereum_address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(data.len() * 2);
|
||||
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
|
||||
for &b in data.iter() {
|
||||
push_nibble(b / 16);
|
||||
push_nibble(b % 16);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn ethereum_signable_message(what: &[u8]) -> Vec<u8> {
|
||||
let prefix = T::Prefix::get();
|
||||
let mut l = prefix.len() + what.len();
|
||||
let mut rev = Vec::new();
|
||||
while l > 0 {
|
||||
rev.push(b'0' + (l % 10) as u8);
|
||||
l /= 10;
|
||||
}
|
||||
let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
|
||||
v.extend(rev.into_iter().rev());
|
||||
v.extend_from_slice(prefix);
|
||||
v.extend_from_slice(what);
|
||||
v
|
||||
}
|
||||
|
||||
fn recover_ethereum_address(s: &EcdsaSignature, what: &[u8]) -> Option<EthereumAddress> {
|
||||
let msg = keccak_256(&Self::ethereum_signable_message(what));
|
||||
let mut res = EthereumAddress::default();
|
||||
res.0
|
||||
.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn into_account_id(address: EthereumAddress) -> Result<T::AccountId, codec::Error> {
|
||||
let mut data = [0u8; 32];
|
||||
data[0..4].copy_from_slice(b"evm:");
|
||||
data[4..24].copy_from_slice(&address.0[..]);
|
||||
|
||||
let hash = sp_core::keccak_256(&data);
|
||||
T::AccountId::decode(&mut &hash[..])
|
||||
}
|
||||
|
||||
fn do_claim(receiver: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult {
|
||||
let donor = Self::into_account_id(ethereum_address)
|
||||
.ok()
|
||||
.ok_or(Error::<T, I>::AddressDecodingFailed)?;
|
||||
|
||||
let balance_due = CurrencyOf::<T, I>::free_balance(&donor);
|
||||
ensure!(
|
||||
balance_due >= CurrencyOf::<T, I>::minimum_balance(),
|
||||
Error::<T, I>::NoBalanceToClaim
|
||||
);
|
||||
|
||||
let new_total = Total::<T, I>::get()
|
||||
.checked_sub(&balance_due)
|
||||
.ok_or(Error::<T, I>::PotUnderflow)?;
|
||||
|
||||
CurrencyOf::<T, I>::transfer(
|
||||
&donor,
|
||||
&receiver,
|
||||
balance_due,
|
||||
ExistenceRequirement::AllowDeath,
|
||||
)?;
|
||||
|
||||
let max_amount = T::MaximumWithdrawAmount::get();
|
||||
if balance_due > max_amount {
|
||||
let vesting_balance = balance_due
|
||||
.checked_sub(&max_amount)
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
let vesting_blocks = T::VestingBlocks::get();
|
||||
let per_block_balance = vesting_balance
|
||||
.checked_div(&vesting_blocks.into())
|
||||
.ok_or(Error::<T, I>::ArithmeticError)?;
|
||||
|
||||
T::VestingSchedule::add_vesting_schedule(
|
||||
&receiver,
|
||||
vesting_balance,
|
||||
per_block_balance,
|
||||
T::BlockNumberProvider::current_block_number(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let rank = if let Some(rank) =
|
||||
<pallet_ranked_collective::Pallet<T, I> as RankedMembers>::rank_of(&donor)
|
||||
{
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_remove_member_from_rank(&donor, rank)?;
|
||||
let new_rank =
|
||||
match <pallet_ranked_collective::Pallet<T, I> as RankedMembers>::rank_of(&receiver)
|
||||
{
|
||||
Some(current_rank) if current_rank >= rank => current_rank,
|
||||
Some(current_rank) if current_rank < rank => {
|
||||
for _ in 0..rank - current_rank {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_promote_member(
|
||||
receiver.clone(),
|
||||
None,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
rank
|
||||
}
|
||||
_ => {
|
||||
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(
|
||||
receiver.clone(),
|
||||
rank,
|
||||
false,
|
||||
)?;
|
||||
rank
|
||||
}
|
||||
};
|
||||
<T as pallet::Config<I>>::MemberSwappedHandler::swapped(&donor, &receiver, new_rank);
|
||||
Some(new_rank)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Total::<T, I>::put(new_total);
|
||||
Self::deposit_event(Event::<T, I>::Claimed {
|
||||
receiver,
|
||||
donor,
|
||||
amount: balance_due,
|
||||
rank,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
281
pallets/claims/src/mock.rs
Normal file
281
pallets/claims/src/mock.rs
Normal file
@ -0,0 +1,281 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use crate as ghost_claims;
|
||||
use frame_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU16, ConstU64, PollStatus, Polling, WithdrawReasons},
|
||||
};
|
||||
use frame_system::EnsureRootWithSuccess;
|
||||
pub use pallet_ranked_collective::{Rank, TallyOf};
|
||||
use sp_runtime::{traits::Convert, BuildStorage};
|
||||
|
||||
pub mod eth_keys {
|
||||
use crate::{mock::Test, EcdsaSignature, EthereumAddress};
|
||||
use codec::Encode;
|
||||
use hex_literal::hex;
|
||||
|
||||
pub fn total_claims() -> u64 {
|
||||
10 + 100 + 1000
|
||||
}
|
||||
pub fn alice_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
69
|
||||
}
|
||||
pub fn bob_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
1337
|
||||
}
|
||||
|
||||
pub fn first_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("1A69d2D5568D1878023EeB121a73d33B9116A760"))
|
||||
}
|
||||
pub fn second_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("2f86cfBED3fbc1eCf2989B9aE5fc019a837A9C12"))
|
||||
}
|
||||
pub fn third_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("e83f67361Ac74D42A48E2DAfb6706eb047D8218D"))
|
||||
}
|
||||
pub fn fourth_eth_public_known() -> EthereumAddress {
|
||||
EthereumAddress(hex!("827ee4ad9b259b6fa1390ed60921508c78befd63"))
|
||||
}
|
||||
|
||||
fn first_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"01c928771aea942a1e7ac06adf2b73dfbc9a25d9eaa516e3673116af7f345198"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn second_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"b19a435901872f817185f7234a1484eae837613f9d10cf21927a23c2d8cb9139"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn third_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"d3baf57b74d65719b2dc33f5a464176022d0cc5edbca002234229f3e733875fc"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn fourth_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"c4683d566436af6b58b4a59c8f501319226e85b21869bf93d5eeb4596d4791d4"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
fn wrong_eth_private_key() -> libsecp256k1::SecretKey {
|
||||
libsecp256k1::SecretKey::parse(&hex!(
|
||||
"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn first_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&first_eth_private_key())
|
||||
}
|
||||
pub fn second_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&second_eth_private_key())
|
||||
}
|
||||
pub fn third_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&third_eth_private_key())
|
||||
}
|
||||
pub fn fourth_eth_public_key() -> EthereumAddress {
|
||||
crate::secp_utils::eth(&fourth_eth_private_key())
|
||||
}
|
||||
|
||||
pub fn first_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(first_eth_public_key())
|
||||
}
|
||||
pub fn second_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(second_eth_public_key())
|
||||
}
|
||||
pub fn third_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(third_eth_public_key())
|
||||
}
|
||||
pub fn fourth_account_id() -> <Test as frame_system::Config>::AccountId {
|
||||
crate::secp_utils::into_account_id::<Test, ()>(fourth_eth_public_key())
|
||||
}
|
||||
|
||||
pub fn first_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&first_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn second_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&second_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn third_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&third_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
pub fn fourth_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&fourth_eth_private_key(), &bob_account_id().encode())
|
||||
}
|
||||
pub fn wrong_signature() -> EcdsaSignature {
|
||||
crate::secp_utils::sig::<Test, ()>(&wrong_eth_private_key(), &alice_account_id().encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MinVestedTransfer: u64 = 1;
|
||||
pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
|
||||
WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
|
||||
}
|
||||
|
||||
impl pallet_vesting::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type BlockNumberToBalance = sp_runtime::traits::ConvertInto;
|
||||
type MinVestedTransfer = MinVestedTransfer;
|
||||
type WeightInfo = ();
|
||||
type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons;
|
||||
|
||||
type BlockNumberProvider = System;
|
||||
const MAX_VESTING_SCHEDULES: u32 = 28;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub MinRankOfClassDelta: Rank = 1;
|
||||
}
|
||||
|
||||
pub struct MinRankOfClass<Delta>(sp_std::marker::PhantomData<Delta>);
|
||||
impl<Delta: Get<Rank>> Convert<Class, Rank> for MinRankOfClass<Delta> {
|
||||
fn convert(a: Class) -> Rank {
|
||||
a.saturating_sub(Delta::get())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPolls;
|
||||
impl Polling<TallyOf<Test>> for TestPolls {
|
||||
type Index = u8;
|
||||
type Votes = u32;
|
||||
type Moment = u64;
|
||||
type Class = Class;
|
||||
|
||||
fn classes() -> Vec<Self::Class> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_ongoing(_index: u8) -> Option<(TallyOf<Test>, Self::Class)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn access_poll<R>(
|
||||
_index: Self::Index,
|
||||
_f: impl FnOnce(PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>) -> R,
|
||||
) -> R {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn try_access_poll<R>(
|
||||
_index: Self::Index,
|
||||
_f: impl FnOnce(
|
||||
PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>,
|
||||
) -> Result<R, DispatchError>,
|
||||
) -> Result<R, DispatchError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn create_ongoing(_class: Self::Class) -> Result<Self::Index, ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn end_ongoing(_index: Self::Index, _approved: bool) -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_ranked_collective::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type AddOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type RemoveOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type PromoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type DemoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
type ExchangeOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
|
||||
|
||||
type Polls = TestPolls;
|
||||
type MemberSwappedHandler = ();
|
||||
type MinRankOfClass = MinRankOfClass<MinRankOfClassDelta>;
|
||||
type VoteWeight = pallet_ranked_collective::Geometric;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkSetup = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub Prefix: &'static [u8] = b"AccountId:";
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type VestingSchedule = Vesting;
|
||||
type BlockNumberProvider = System;
|
||||
type MemberSwappedHandler = ();
|
||||
|
||||
type Prefix = Prefix;
|
||||
type MaximumWithdrawAmount = ConstU64<200>;
|
||||
type VestingBlocks = ConstU32<80>;
|
||||
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
type Class = Rank;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Vesting: pallet_vesting,
|
||||
Club: pallet_ranked_collective,
|
||||
Claims: ghost_claims,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default()
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(crate::mock::eth_keys::first_account_id(), 10),
|
||||
(crate::mock::eth_keys::second_account_id(), 100),
|
||||
(crate::mock::eth_keys::third_account_id(), 1000),
|
||||
],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
ghost_claims::GenesisConfig::<Test> {
|
||||
total: crate::mock::eth_keys::total_claims(),
|
||||
members_and_ranks: vec![
|
||||
(crate::mock::eth_keys::second_account_id(), 1),
|
||||
(crate::mock::eth_keys::third_account_id(), 3),
|
||||
],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
ext
|
||||
}
|
||||
35
pallets/claims/src/secp_utils.rs
Normal file
35
pallets/claims/src/secp_utils.rs
Normal file
@ -0,0 +1,35 @@
|
||||
#![cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
|
||||
use crate::{keccak_256, Config, EcdsaSignature, EthereumAddress};
|
||||
|
||||
pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
|
||||
libsecp256k1::PublicKey::from_secret_key(secret)
|
||||
}
|
||||
|
||||
pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
|
||||
let mut res = EthereumAddress::default();
|
||||
res.0
|
||||
.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn into_account_id<T: Config<I>, I: 'static>(address: EthereumAddress) -> T::AccountId {
|
||||
super::Pallet::<T, I>::into_account_id(address).unwrap()
|
||||
}
|
||||
|
||||
pub fn sig<T: Config<I>, I: 'static>(
|
||||
secret: &libsecp256k1::SecretKey,
|
||||
what: &[u8],
|
||||
) -> EcdsaSignature {
|
||||
let msg = keccak_256(&super::Pallet::<T, I>::ethereum_signable_message(
|
||||
&crate::to_ascii_hex(what)[..],
|
||||
));
|
||||
|
||||
let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
|
||||
|
||||
let mut r = [0u8; 65];
|
||||
r[0..64].copy_from_slice(&sig.serialize()[..]);
|
||||
r[64] = recovery_id.serialize();
|
||||
EcdsaSignature(r)
|
||||
}
|
||||
285
pallets/claims/src/tests.rs
Normal file
285
pallets/claims/src/tests.rs
Normal file
@ -0,0 +1,285 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use hex_literal::hex;
|
||||
use mock::{
|
||||
eth_keys::{
|
||||
alice_account_id, bob_account_id, first_account_id, first_eth_public_key,
|
||||
first_eth_public_known, first_signature, fourth_account_id, fourth_eth_public_key,
|
||||
fourth_eth_public_known, fourth_signature, second_account_id, second_eth_public_key,
|
||||
second_eth_public_known, second_signature, third_account_id, third_eth_public_key,
|
||||
third_eth_public_known, third_signature, total_claims, wrong_signature,
|
||||
},
|
||||
ghost_claims, new_test_ext, Balances, Claims, Club, RuntimeEvent, RuntimeOrigin, System, Test,
|
||||
Vesting,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serde_works() {
|
||||
let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
|
||||
let y = serde_json::to_string(&x).unwrap();
|
||||
assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
|
||||
let z: EthereumAddress = serde_json::from_str(&y).unwrap();
|
||||
assert_eq!(x, z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_eth_public_accounts_are_correct() {
|
||||
assert_eq!(first_eth_public_key(), first_eth_public_known());
|
||||
assert_eq!(second_eth_public_key(), second_eth_public_known());
|
||||
assert_eq!(third_eth_public_key(), third_eth_public_known());
|
||||
assert_eq!(fourth_eth_public_key(), fourth_eth_public_known());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature()
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 10);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&second_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 100);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn big_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 200);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(
|
||||
ghost_claims::Total::<Test, ()>::get(),
|
||||
total_claims() - 1000
|
||||
);
|
||||
assert_ok!(Balances::transfer_allow_death(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
bob_account_id(),
|
||||
200
|
||||
));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_accounts_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_accounts_reverese_claiming_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
second_eth_public_key(),
|
||||
second_signature(),
|
||||
));
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature(),
|
||||
));
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), None);
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_with_bad_signature() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
first_eth_public_key(),
|
||||
wrong_signature()
|
||||
),
|
||||
crate::Error::<Test>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_with_wrong_address() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(bob_account_id()),
|
||||
first_eth_public_key(),
|
||||
first_signature()
|
||||
),
|
||||
crate::Error::<Test>::InvalidEthereumAddress
|
||||
);
|
||||
|
||||
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
|
||||
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
|
||||
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
|
||||
|
||||
assert_eq!(Club::rank_of(&alice_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&first_account_id()), None);
|
||||
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
|
||||
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
|
||||
|
||||
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_claim_nothing() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_err!(
|
||||
Claims::claim(
|
||||
RuntimeOrigin::signed(bob_account_id()),
|
||||
fourth_eth_public_key(),
|
||||
fourth_signature()
|
||||
),
|
||||
crate::Error::<Test>::NoBalanceToClaim
|
||||
);
|
||||
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
|
||||
assert_eq!(Balances::usable_balance(&fourth_account_id()), 0);
|
||||
assert_eq!(Vesting::vesting_balance(&bob_account_id()), None);
|
||||
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_emitted_during_claim() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let account = third_account_id();
|
||||
let amount = Balances::usable_balance(&account);
|
||||
let rank = Club::rank_of(&account);
|
||||
|
||||
System::reset_events();
|
||||
assert_eq!(System::event_count(), 0);
|
||||
assert_ok!(Claims::claim(
|
||||
RuntimeOrigin::signed(alice_account_id()),
|
||||
third_eth_public_key(),
|
||||
third_signature(),
|
||||
));
|
||||
System::assert_has_event(RuntimeEvent::Claims(crate::Event::Claimed {
|
||||
receiver: alice_account_id(),
|
||||
donor: account,
|
||||
amount,
|
||||
rank,
|
||||
}));
|
||||
})
|
||||
}
|
||||
77
pallets/claims/src/weights.rs
Normal file
77
pallets/claims/src/weights.rs
Normal file
@ -0,0 +1,77 @@
|
||||
// This file is part of Ghost Network.
|
||||
|
||||
// Ghost Network is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Ghost Network is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Ghost Network. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Autogenerated weights for `ghost_claims`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2024-08-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// ./target/release/ghost
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain=casper-dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=ghost_claims
|
||||
// --extrinsic=*
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
// --header=./file_header.txt
|
||||
// --output=./runtime/casper/src/weights/ghost_claims.rs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use frame_support::{
|
||||
traits::Get,
|
||||
weights::{Weight, constants::RocksDbWeight},
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `ghost_claims`.
|
||||
pub trait WeightInfo {
|
||||
fn claim() -> Weight;
|
||||
}
|
||||
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `System::Account` (r:2 w:2)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `GhostClaims::Total` (r:1 w:1)
|
||||
/// Proof: `GhostClaims::Total` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `CultCollective::Members` (r:2 w:2)
|
||||
/// Proof: `CultCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `CultCollective::MemberCount` (r:6 w:6)
|
||||
/// Proof: `CultCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`)
|
||||
/// Storage: `CultCollective::IdToIndex` (r:6 w:12)
|
||||
/// Proof: `CultCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
|
||||
/// Storage: `CultCollective::IndexToId` (r:0 w:6)
|
||||
/// Proof: `CultCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
|
||||
fn claim() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `896`
|
||||
// Estimated: `16164`
|
||||
// Minimum execution time: 754_086_000 picoseconds.
|
||||
Weight::from_parts(756_147_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 16164))
|
||||
.saturating_add(RocksDbWeight::get().reads(17))
|
||||
.saturating_add(RocksDbWeight::get().writes(29))
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-networks"
|
||||
version = "0.2.10"
|
||||
description = "Registry and lifecycle management for external network metadata and cryptographic profiles."
|
||||
version = "0.1.16"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -11,9 +10,7 @@ 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 }
|
||||
@ -22,7 +19,6 @@ pallet-staking = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
ghost-traits = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
primitives = { workspace = true }
|
||||
@ -35,7 +31,6 @@ default = ["std"]
|
||||
std = [
|
||||
"scale-info/std",
|
||||
"codec/std",
|
||||
"log/std",
|
||||
"num-traits/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
@ -44,7 +39,6 @@ std = [
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"ghost-traits/std",
|
||||
"ghost-helpers/std",
|
||||
"pallet-staking/std",
|
||||
"pallet-balances/std",
|
||||
]
|
||||
|
||||
@ -6,10 +6,9 @@ use crate::Pallet as GhostNetworks;
|
||||
use frame_benchmarking::v1::{benchmarks, BenchmarkError};
|
||||
use sp_runtime::Saturating;
|
||||
|
||||
use ghost_helpers::networks::{
|
||||
MAX_ENDPOINT_LEN, MAX_ENDPOINTS_LEN, MAX_GATEKEEPER_LEN,
|
||||
MAX_SELECTOR_LEN,
|
||||
};
|
||||
const MAX_NAME_LEN: u32 = 20;
|
||||
const MAX_ENDPOINT_LEN: u32 = 150;
|
||||
const MAX_ENDPOINT_NUMBER: u32 = 20;
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
@ -19,13 +18,18 @@ fn prepare_network<T: Config>(
|
||||
n: u32,
|
||||
m: u32,
|
||||
k: u32,
|
||||
) -> (T::NetworkId, NetworkData) {
|
||||
) -> (<T as module::Config>::NetworkId, NetworkData) {
|
||||
let chain_id: <T as module::Config>::NetworkId = Default::default();
|
||||
let chain_id = chain_id.saturating_add((n + m).into());
|
||||
|
||||
let mut gatekeeper = sp_std::vec![];
|
||||
for i in 0..n {
|
||||
gatekeeper.push((i % 256) as u8);
|
||||
let mut gatekeeper = b"0x".to_vec();
|
||||
for i in 0..40 {
|
||||
gatekeeper.push(i);
|
||||
}
|
||||
|
||||
let mut topic_name = b"0x".to_vec();
|
||||
for i in 0..64 {
|
||||
topic_name.push(i);
|
||||
}
|
||||
|
||||
let mut default_endpoints = sp_std::vec![];
|
||||
@ -33,24 +37,24 @@ fn prepare_network<T: Config>(
|
||||
default_endpoints.push(sp_std::vec![0x69; m as usize]);
|
||||
}
|
||||
|
||||
let network = ghost_helpers::networks::NetworkDataBuilder::default()
|
||||
.with_gatekeeper(gatekeeper)
|
||||
.with_default_endpoints(default_endpoints)
|
||||
.with_selector(vec![1u8; 4])
|
||||
.with_network_type(NetworkType::Evm)
|
||||
.with_network_curve(NetworkCurve::Secp256k1)
|
||||
.with_rate_limit_delay(6)
|
||||
.with_finality_delay(69)
|
||||
.with_block_deviation(420)
|
||||
.with_incoming_share(0)
|
||||
.with_outgoing_share(0)
|
||||
.build();
|
||||
let network = NetworkData {
|
||||
chain_name: sp_std::vec![0x69; n as usize],
|
||||
default_endpoints,
|
||||
gatekeeper,
|
||||
topic_name,
|
||||
network_type: NetworkType::Evm,
|
||||
finality_delay: 69,
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
incoming_fee: 0,
|
||||
outgoing_fee: 0,
|
||||
};
|
||||
|
||||
(chain_id, network)
|
||||
}
|
||||
|
||||
fn create_network<T: Config>(
|
||||
chain_id: T::NetworkId,
|
||||
chain_id: <T as module::Config>::NetworkId,
|
||||
network: NetworkData,
|
||||
) -> Result<Option<NetworkData>, BenchmarkError> {
|
||||
let prev_network = match GhostNetworks::<T>::get(&chain_id) {
|
||||
@ -59,8 +63,8 @@ fn create_network<T: Config>(
|
||||
let authority = T::RegisterOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
GhostNetworks::<T>::register_network(
|
||||
authority,
|
||||
chain_id,
|
||||
authority.clone(),
|
||||
chain_id.clone(),
|
||||
network.clone(),
|
||||
)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
@ -73,52 +77,53 @@ fn create_network<T: Config>(
|
||||
|
||||
benchmarks! {
|
||||
register_network {
|
||||
let i in 1 .. MAX_GATEKEEPER_LEN;
|
||||
let i in 1 .. MAX_NAME_LEN;
|
||||
let j in 1 .. MAX_ENDPOINT_LEN;
|
||||
let k in 1 .. MAX_ENDPOINTS_LEN;
|
||||
let k in 1 .. MAX_ENDPOINT_NUMBER;
|
||||
|
||||
let (chain_id, network) = prepare_network::<T>(i, j, k);
|
||||
let authority = T::RegisterOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), None);
|
||||
assert!(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)?;
|
||||
|
||||
update_network_name {
|
||||
let n in 1 .. MAX_NAME_LEN;
|
||||
let name = sp_std::vec![0x42; n as usize];
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, selector)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), name.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkSelectorUpdated { chain_id }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkNameUpdated {
|
||||
chain_id: chain_id.clone(), chain_name: name,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_endpoint {
|
||||
let n in 1 .. MAX_ENDPOINT_LEN;
|
||||
let index = 0u32;
|
||||
let raw_endpoint = sp_std::vec![0x42; n as usize];
|
||||
let endpoint = BoundedVec::<u8, ConstU32<MAX_ENDPOINT_LEN>>::try_from(raw_endpoint)
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
let index_to_update = 0u32;
|
||||
let endpoint = sp_std::vec![0x42; n as usize];
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, Some(index), Some(endpoint))
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), Some(index_to_update), Some(endpoint.clone()))
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkEndpointUpdated {chain_id, index }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkEndpointUpdated {
|
||||
chain_id: chain_id.clone(),
|
||||
index: index_to_update,
|
||||
endpoint,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_finality_delay {
|
||||
@ -126,13 +131,13 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, delay)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), delay)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkFinalityDelayUpdated {
|
||||
chain_id, finality_delay: delay,
|
||||
chain_id: chain_id.clone(), finality_delay: delay,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_rate_limit_delay {
|
||||
@ -140,25 +145,27 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, rate_limit)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), rate_limit)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkRateLimitDelayUpdated {
|
||||
chain_id, rate_limit_delay: rate_limit,
|
||||
chain_id: chain_id.clone(), rate_limit_delay: rate_limit,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_block_deviation {
|
||||
let block_deviation = 1337;
|
||||
update_network_block_distance {
|
||||
let block_distance = 1337;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, block_deviation)
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), block_distance)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkBlockDeviationUpdated { chain_id, block_deviation }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
assert_last_event::<T>(Event::NetworkBlockDistanceUpdated {
|
||||
chain_id: chain_id.clone(), block_distance,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_network_type {
|
||||
@ -166,92 +173,83 @@ benchmarks! {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, network_type.clone())
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network_type.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkTypeUpdated { chain_id, network_type }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
|
||||
}
|
||||
|
||||
update_network_curve {
|
||||
let network_curve = NetworkCurve::Ed25519;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id, network)?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id, network_curve)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkCurveUpdated { chain_id, network_curve }.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id), prev_network);
|
||||
}
|
||||
|
||||
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;
|
||||
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, 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, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), topic_name.clone())
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkTopicNameUpdated {
|
||||
chain_id: chain_id.clone(), topic_name,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_incoming_network_fee {
|
||||
let incoming_fee = 1337;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), incoming_fee)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkIncomingFeeUpdated {
|
||||
chain_id: chain_id.clone(), incoming_fee,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
update_outgoing_network_fee {
|
||||
let outgoing_fee = 1337;
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::UpdateOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?;
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), outgoing_fee)
|
||||
verify {
|
||||
assert_last_event::<T>(Event::NetworkOutgoingFeeUpdated {
|
||||
chain_id: chain_id.clone(), outgoing_fee,
|
||||
}.into());
|
||||
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network);
|
||||
}
|
||||
|
||||
remove_network {
|
||||
let (chain_id, network) = prepare_network::<T>(1, 1, 1);
|
||||
let authority = T::RemoveOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let maybe_network = create_network::<T>(chain_id, network)?;
|
||||
assert_eq!(GhostNetworks::<T>::networks(chain_id), maybe_network);
|
||||
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
|
||||
}: _<T::RuntimeOrigin>(authority, chain_id)
|
||||
let 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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
135
pallets/networks/src/math.rs
Normal file
135
pallets/networks/src/math.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use crate::AtLeast32BitUnsigned;
|
||||
|
||||
pub struct MulDiv<Balance>(core::marker::PhantomData<Balance>);
|
||||
impl<Balance> MulDiv<Balance>
|
||||
where
|
||||
Balance: Copy
|
||||
+ AtLeast32BitUnsigned
|
||||
+ num_traits::ops::wrapping::WrappingAdd
|
||||
+ num_traits::ops::overflowing::OverflowingAdd
|
||||
+ sp_std::ops::AddAssign
|
||||
+ sp_std::ops::Not<Output = Balance>
|
||||
+ sp_std::ops::Shl<Output = Balance>
|
||||
+ sp_std::ops::Shr<Output = Balance>
|
||||
+ sp_std::ops::BitAnd<Balance, Output = Balance>,
|
||||
{
|
||||
|
||||
fn zero(&self) -> Balance {
|
||||
0u32.into()
|
||||
}
|
||||
|
||||
fn one(&self) -> Balance {
|
||||
1u32.into()
|
||||
}
|
||||
|
||||
fn bit_shift(&self) -> Balance {
|
||||
let u32_shift: u32 = core::mem::size_of::<Balance>()
|
||||
.saturating_mul(4)
|
||||
.try_into()
|
||||
.unwrap_or_default();
|
||||
u32_shift.into()
|
||||
}
|
||||
|
||||
fn least_significant_bits(&self, a: Balance) -> Balance {
|
||||
a & ((self.one() << self.bit_shift()) - self.one())
|
||||
}
|
||||
|
||||
fn most_significant_bits(&self, a: Balance) -> Balance {
|
||||
a >> self.bit_shift()
|
||||
}
|
||||
|
||||
fn two_complement(&self, a: Balance) -> Balance {
|
||||
(!a).wrapping_add(&self.one())
|
||||
}
|
||||
|
||||
fn adjusted_ratio(&self, a: Balance) -> Balance {
|
||||
(self.two_complement(a) / a).wrapping_add(&self.one())
|
||||
}
|
||||
|
||||
fn modulo(&self, a: Balance) -> Balance {
|
||||
self.two_complement(a) % a
|
||||
}
|
||||
|
||||
fn overflow_resistant_addition(
|
||||
&self,
|
||||
a0: Balance,
|
||||
a1: Balance,
|
||||
b0: Balance,
|
||||
b1: Balance,
|
||||
) -> (Balance, Balance) {
|
||||
let (r0, overflow) = a0.overflowing_add(&b0);
|
||||
let overflow: Balance = overflow.then(|| 1u32).unwrap_or_default().into();
|
||||
let r1 = a1.wrapping_add(&b1).wrapping_add(&overflow);
|
||||
(r0, r1)
|
||||
}
|
||||
|
||||
fn overflow_resistant_multiplication(&self, a: Balance, b: Balance) -> (Balance, Balance) {
|
||||
let (a0, a1) = (
|
||||
self.least_significant_bits(a),
|
||||
self.most_significant_bits(a),
|
||||
);
|
||||
let (b0, b1) = (
|
||||
self.least_significant_bits(b),
|
||||
self.most_significant_bits(b),
|
||||
);
|
||||
let (x, y) = (a1 * b0, b1 * a0);
|
||||
|
||||
let (r0, r1) = (a0 * b0, a1 * b1);
|
||||
let (r0, r1) = self.overflow_resistant_addition(
|
||||
r0,
|
||||
r1,
|
||||
self.least_significant_bits(x) << self.bit_shift(),
|
||||
self.most_significant_bits(x),
|
||||
);
|
||||
let (r0, r1) = self.overflow_resistant_addition(
|
||||
r0,
|
||||
r1,
|
||||
self.least_significant_bits(y) << self.bit_shift(),
|
||||
self.most_significant_bits(y),
|
||||
);
|
||||
|
||||
(r0, r1)
|
||||
}
|
||||
|
||||
fn overflow_resistant_division(
|
||||
&self,
|
||||
mut a0: Balance,
|
||||
mut a1: Balance,
|
||||
b: Balance,
|
||||
) -> (Balance, Balance) {
|
||||
if b == self.one() {
|
||||
return (a0, a1);
|
||||
}
|
||||
|
||||
let zero: Balance = 0u32.into();
|
||||
let (q, r) = (self.adjusted_ratio(b), self.modulo(b));
|
||||
let (mut x0, mut x1) = (zero, zero);
|
||||
|
||||
while a1 != zero {
|
||||
let (t0, t1) = self.overflow_resistant_multiplication(a1, q);
|
||||
let (new_x0, new_x1) = self.overflow_resistant_addition(x0, x1, t0, t1);
|
||||
x0 = new_x0;
|
||||
x1 = new_x1;
|
||||
|
||||
let (t0, t1) = self.overflow_resistant_multiplication(a1, r);
|
||||
let (new_a0, new_a1) = self.overflow_resistant_addition(t0, t1, a0, zero);
|
||||
a0 = new_a0;
|
||||
a1 = new_a1;
|
||||
}
|
||||
|
||||
self.overflow_resistant_addition(x0, x1, a0 / b, zero)
|
||||
}
|
||||
|
||||
fn mul_div(&self, a: Balance, b: Balance, c: Balance) -> Balance {
|
||||
let (t0, t1) = self.overflow_resistant_multiplication(a, b);
|
||||
self.overflow_resistant_division(t0, t1, c).0
|
||||
}
|
||||
|
||||
pub fn calculate(a: Balance, b: Balance, c: Balance) -> Balance {
|
||||
let inner = MulDiv(core::marker::PhantomData);
|
||||
if c == inner.zero() {
|
||||
return c;
|
||||
}
|
||||
inner.mul_div(a, b, c)
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,6 @@ pallet_staking_reward_curve::build! {
|
||||
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub const MaxNetworks: u32 = 3;
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
@ -99,8 +98,7 @@ impl ghost_networks::Config for Test {
|
||||
type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>;
|
||||
type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>;
|
||||
type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>;
|
||||
type MaxNetworks = MaxNetworks;
|
||||
type WeightInfo = ();
|
||||
type WeightInfo = crate::weights::SubstrateWeight<Test>;
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@
|
||||
//! 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: `[]`
|
||||
//! DATE: 2025-06-19, 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
|
||||
@ -40,7 +40,7 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{
|
||||
traits::Get,
|
||||
traits::Get,
|
||||
weights::{
|
||||
Weight, constants::RocksDbWeight
|
||||
}
|
||||
@ -50,72 +50,64 @@ use sp_std::marker::PhantomData;
|
||||
/// Weight functions needed for ghost_networks
|
||||
pub trait WeightInfo {
|
||||
fn register_network(i: u32, j: u32, k: u32, ) -> Weight;
|
||||
fn update_network_selector() -> Weight;
|
||||
fn update_network_name(n: u32, ) -> Weight;
|
||||
fn update_network_endpoint(n: u32, ) -> Weight;
|
||||
fn update_network_finality_delay() -> Weight;
|
||||
fn update_network_rate_limit_delay() -> Weight;
|
||||
fn update_network_block_deviation() -> Weight;
|
||||
fn update_network_block_distance() -> Weight;
|
||||
fn update_network_type() -> Weight;
|
||||
fn update_network_curve() -> Weight;
|
||||
fn update_network_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 {
|
||||
// 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))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_selector() -> Weight {
|
||||
/// The range of component `i` is `[1, 20]`.
|
||||
/// The range of component `j` is `[1, 150]`.
|
||||
/// The range of component `k` is `[1, 20]`.
|
||||
fn register_network(_i: u32, _j: u32, k: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `167`
|
||||
// Estimated: `3632`
|
||||
// Minimum execution time: 42_680_000 picoseconds.
|
||||
Weight::from_parts(43_454_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3632))
|
||||
// Measured: `109`
|
||||
// Estimated: `3574`
|
||||
// Minimum execution time: 46_023_000 picoseconds.
|
||||
Weight::from_parts(97_871_287, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3574))
|
||||
// Standard Error: 94_524
|
||||
.saturating_add(Weight::from_parts(940_486, 0).saturating_mul(k.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `n` is `[1, 128]`.
|
||||
/// The range of component `n` is `[1, 20]`.
|
||||
fn update_network_name(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 49_906_000 picoseconds.
|
||||
Weight::from_parts(55_537_587, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
// Standard Error: 87_704
|
||||
.saturating_add(Weight::from_parts(92_366, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `n` is `[1, 150]`.
|
||||
fn update_network_endpoint(n: u32, ) -> Weight {
|
||||
// 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()))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 50_556_000 picoseconds.
|
||||
Weight::from_parts(57_726_674, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
// Standard Error: 12_261
|
||||
.saturating_add(Weight::from_parts(274, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
@ -123,11 +115,11 @@ impl WeightInfo for () {
|
||||
/// Proof: `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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 49_406_000 picoseconds.
|
||||
Weight::from_parts(51_256_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
@ -135,23 +127,23 @@ impl WeightInfo for () {
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_rate_limit_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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 49_572_000 picoseconds.
|
||||
Weight::from_parts(52_584_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_block_deviation() -> Weight {
|
||||
fn update_network_block_distance() -> 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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 48_880_000 picoseconds.
|
||||
Weight::from_parts(50_596_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
@ -159,94 +151,72 @@ impl WeightInfo for () {
|
||||
/// Proof: `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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 48_282_000 picoseconds.
|
||||
Weight::from_parts(49_137_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::NetworkCurves` (r:2 w:2)
|
||||
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::NetworkCurvesVec` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::NetworkCurvesVec` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn update_network_curve() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `180`
|
||||
// Estimated: `6120`
|
||||
// Minimum execution time: 82_975_000 picoseconds.
|
||||
Weight::from_parts(84_583_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 6120))
|
||||
.saturating_add(RocksDbWeight::get().reads(4))
|
||||
.saturating_add(RocksDbWeight::get().writes(4))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_decimals() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `167`
|
||||
// Estimated: `3632`
|
||||
// Minimum execution time: 41_602_000 picoseconds
|
||||
Weight::from_parts(42_603_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3632))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_network_gatekeeper() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `167`
|
||||
// Estimated: `3632`
|
||||
// Minimum execution time: 42_643_000 picoseconds.
|
||||
Weight::from_parts(43_190_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3632))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 50_853_000 picoseconds.
|
||||
Weight::from_parts(51_982_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_incoming_network_share() -> Weight {
|
||||
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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 50_343_000 picoseconds.
|
||||
Weight::from_parts(52_380_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_outgoing_network_share() -> Weight {
|
||||
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))
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 49_393_000 picoseconds.
|
||||
Weight::from_parts(80_966_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn update_outgoing_network_fee() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 49_579_000 picoseconds.
|
||||
Weight::from_parts(51_126_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().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`)
|
||||
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: `302`
|
||||
// Estimated: `3767`
|
||||
// Minimum execution time: 44_634_000 picoseconds.
|
||||
Weight::from_parts(45_815_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3767))
|
||||
.saturating_add(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()
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ghost-slow-clap"
|
||||
version = "0.4.29"
|
||||
description = "DEPRECATED: Applause protocol for the EVM bridge."
|
||||
version = "0.3.56"
|
||||
description = "Applause protocol for the EVM bridge"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -28,7 +28,6 @@ sp-io = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
ghost-networks = { workspace = true }
|
||||
ghost-traits = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { workspace = true }
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
use super::*;
|
||||
use frame_benchmarking::v1::*;
|
||||
|
||||
use frame_support::traits::fungible::Inspect;
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
pub fn create_account<T: Config>() -> T::AccountId {
|
||||
@ -11,36 +12,16 @@ 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 {
|
||||
chain_name: "Ethereum".into(),
|
||||
default_endpoints: vec![b"https://other.endpoint.network.com".to_vec()],
|
||||
finality_delay: 69,
|
||||
avg_block_speed: 69,
|
||||
rate_limit_delay: 69,
|
||||
block_distance: 69,
|
||||
network_type: ghost_networks::NetworkType::Evm,
|
||||
network_curve: ghost_networks::NetworkCurve::Secp256k1,
|
||||
gatekeeper: b"0x4d224452801ACEd8B2F0aebE155379bb5D594381".to_vec(),
|
||||
topic_name: b"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".to_vec(),
|
||||
incoming_fee: 0,
|
||||
outgoing_fee: 0,
|
||||
};
|
||||
|
||||
let _ = T::NetworkDataHandler::register(network_id, network_data.clone());
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
slow_clap {
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
prepare_evm_network::<T>(network_id);
|
||||
|
||||
let minimum_balance = <<T as pallet::Config>::Currency>::minimum_balance();
|
||||
let receiver = create_account::<T>();
|
||||
let amount = minimum_balance + minimum_balance;
|
||||
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
let session_index = T::ValidatorSet::session_index();
|
||||
let transaction_hash = H256::repeat_byte(1u8);
|
||||
let args_hash = Pallet::<T>::generate_unique_hash(&receiver, &amount, &network_id);
|
||||
|
||||
let authorities = vec![T::AuthorityId::generate_pair(None)];
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
|
||||
@ -58,7 +39,6 @@ benchmarks! {
|
||||
receiver: receiver.clone(),
|
||||
amount,
|
||||
};
|
||||
let args_hash = Pallet::<T>::generate_unique_hash(&clap);
|
||||
|
||||
let authority_id = authorities
|
||||
.get(authority_index as usize)
|
||||
@ -66,20 +46,16 @@ benchmarks! {
|
||||
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)
|
||||
verify {
|
||||
assert_eq!(ApplauseDetails::<T>::get(&session_index, &args_hash).is_some(), true);
|
||||
let clap_key = (session_index, transaction_hash, args_hash);
|
||||
assert_eq!(ReceivedClaps::<T>::get(&clap_key).get(&authority_index).is_some(), true);
|
||||
assert_eq!(<<T as pallet::Config>::Currency>::total_balance(&receiver), amount);
|
||||
}
|
||||
|
||||
commit_block {
|
||||
let session_index = T::ValidatorSet::session_index();
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
prepare_evm_network::<T>(network_id);
|
||||
|
||||
let authorities = vec![T::AuthorityId::generate_pair(None)];
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
|
||||
@ -91,7 +67,11 @@ benchmarks! {
|
||||
session_index,
|
||||
authority_index,
|
||||
network_id,
|
||||
last_stored_block: 69,
|
||||
commitment: CommitmentDetails {
|
||||
last_registered_block: 69,
|
||||
last_seen_block: 420,
|
||||
last_updated: 1337,
|
||||
}
|
||||
};
|
||||
|
||||
let authority_id = authorities
|
||||
@ -99,47 +79,16 @@ benchmarks! {
|
||||
.expect("first authority should exist");
|
||||
let signature = authority_id.sign(&block_commitment.encode())
|
||||
.ok_or("couldn't make signature")?;
|
||||
|
||||
}: _(RawOrigin::None, block_commitment, signature)
|
||||
verify {
|
||||
let current_block_number = <Pallet<T> as BlockNumberProvider>::current_block_number();
|
||||
let stored_commitment = BlockCommitments::<T>::get(&network_id)
|
||||
.get(&authority_index)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
assert_eq!(stored_commitment.last_stored_block, 69);
|
||||
assert_eq!(stored_commitment.commits, 1);
|
||||
assert_eq!(stored_commitment.last_updated, current_block_number);
|
||||
}
|
||||
|
||||
try_offend_validators {
|
||||
let n in 1 .. T::MaxAuthorities::get();
|
||||
let d in 0 .. T::MaxAuthorities::get();
|
||||
|
||||
let session_index = T::ValidatorSet::session_index();
|
||||
let mut validators_vec = Vec::new();
|
||||
|
||||
for i in 0..T::MaxAuthorities::get() {
|
||||
let v = account("validator", i, 0);
|
||||
validators_vec.push(v);
|
||||
}
|
||||
|
||||
let validators = WeakBoundedVec::<ValidatorId<T>, T::MaxAuthorities>::try_from(validators_vec)
|
||||
.expect("weak bounded vec should be constructed; qed");
|
||||
|
||||
let offence_type = OffenceType::CommitmentOffence;
|
||||
let disabled_bitmap = BitMap::new(T::MaxAuthorities::get());
|
||||
let mut offence_bitmap = BitMap::new(T::MaxAuthorities::get());
|
||||
for i in 0..d {
|
||||
offence_bitmap.set(i);
|
||||
}
|
||||
}: {
|
||||
let _ = Pallet::<T>::try_offend_validators(
|
||||
&session_index,
|
||||
&validators,
|
||||
offence_bitmap,
|
||||
disabled_bitmap,
|
||||
offence_type
|
||||
);
|
||||
assert_eq!(stored_commitment.last_registered_block, 69);
|
||||
assert_eq!(stored_commitment.last_seen_block, 420);
|
||||
assert_eq!(stored_commitment.last_updated, 1337);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ where
|
||||
Ok(Some(s.as_bytes().to_vec()))
|
||||
}
|
||||
|
||||
pub fn de_string_to_block_number<'de, D>(de: D) -> Result<Option<u64>, D::Error>
|
||||
pub fn de_string_to_u64<'de, D>(de: D) -> Result<Option<u64>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
@ -17,7 +17,7 @@ where
|
||||
Ok(u64::from_str_radix(s, 16).ok())
|
||||
}
|
||||
|
||||
pub fn de_string_to_block_number_pure<'de, D>(de: D) -> Result<u64, D::Error>
|
||||
pub fn de_string_to_u64_pure<'de, D>(de: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
use sp_runtime::{traits::UniqueSaturatedInto, SaturatedConversion, Saturating};
|
||||
use sp_runtime::SaturatedConversion;
|
||||
use sp_staking::SessionIndex;
|
||||
|
||||
use crate::{
|
||||
deserialisations::{
|
||||
de_string_to_block_number, de_string_to_block_number_pure, de_string_to_bytes,
|
||||
de_string_to_h256, de_string_to_vec_of_bytes,
|
||||
de_string_to_bytes, de_string_to_h256, de_string_to_u64, de_string_to_u64_pure,
|
||||
de_string_to_vec_of_bytes,
|
||||
},
|
||||
AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, BlockNumberFor, Call, Clap, Config,
|
||||
Decode, Deserialize, Encode, ExternalBlockNumber, NetworkIdOf, RuntimeAppPublic, RuntimeDebug,
|
||||
SubmitTransaction, Vec, BLOCK_COMMITMENT_DELAY, H256, LOG_TARGET,
|
||||
AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, Call, Clap, CommitmentDetails, Config,
|
||||
Decode, Deserialize, Encode, NetworkIdOf, RuntimeAppPublic, RuntimeDebug, SubmitTransaction,
|
||||
Vec, COMMITMENT_DELAY_MILLIS, H256, LOG_TARGET,
|
||||
};
|
||||
|
||||
const NUMBER_OF_TOPICS: usize = 3;
|
||||
@ -28,8 +28,8 @@ pub struct EvmResponse {
|
||||
#[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)]
|
||||
#[serde(untagged)]
|
||||
pub enum EvmResponseType {
|
||||
#[serde(deserialize_with = "de_string_to_block_number_pure")]
|
||||
BlockNumber(ExternalBlockNumber),
|
||||
#[serde(deserialize_with = "de_string_to_u64_pure")]
|
||||
BlockNumber(u64),
|
||||
TransactionLogs(Vec<Log>),
|
||||
}
|
||||
|
||||
@ -38,14 +38,39 @@ pub enum EvmResponseType {
|
||||
pub struct Log {
|
||||
#[serde(default, deserialize_with = "de_string_to_h256")]
|
||||
pub transaction_hash: Option<H256>,
|
||||
#[serde(default, deserialize_with = "de_string_to_block_number")]
|
||||
pub block_number: Option<ExternalBlockNumber>,
|
||||
#[serde(default, deserialize_with = "de_string_to_u64")]
|
||||
pub block_number: Option<u64>,
|
||||
#[serde(default, deserialize_with = "de_string_to_vec_of_bytes")]
|
||||
pub topics: Vec<Vec<u8>>,
|
||||
pub removed: bool,
|
||||
}
|
||||
|
||||
impl EvmResponseType {
|
||||
fn prepare_block_commitment<T: Config>(
|
||||
&self,
|
||||
from_block: u64,
|
||||
authority_index: AuthIndex,
|
||||
session_index: SessionIndex,
|
||||
network_id: NetworkIdOf<T>,
|
||||
) -> BlockCommitment<NetworkIdOf<T>> {
|
||||
let last_updated = sp_io::offchain::timestamp().unix_millis();
|
||||
let current_block = match self {
|
||||
EvmResponseType::BlockNumber(block_number) => *block_number,
|
||||
EvmResponseType::TransactionLogs(_) => Default::default(),
|
||||
};
|
||||
|
||||
BlockCommitment {
|
||||
session_index,
|
||||
authority_index,
|
||||
network_id,
|
||||
commitment: CommitmentDetails {
|
||||
last_registered_block: from_block,
|
||||
last_seen_block: current_block,
|
||||
last_updated,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_clap<T: Config>(
|
||||
&self,
|
||||
authority_index: AuthIndex,
|
||||
@ -102,7 +127,7 @@ impl EvmResponseType {
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Found {:?} claps for network {:?}",
|
||||
"🧐 Found {:?} claps for network {:?}",
|
||||
claps_len,
|
||||
network_id,
|
||||
);
|
||||
@ -113,7 +138,7 @@ impl EvmResponseType {
|
||||
None => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Clap #{} signing failed from authority #{:?} for network {:?}",
|
||||
"🧐 Clap #{} signing failed from authority #{:?} for network {:?}",
|
||||
clap_index,
|
||||
authority_index,
|
||||
network_id,
|
||||
@ -132,7 +157,7 @@ impl EvmResponseType {
|
||||
{
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Failed to submit clap #{} from authority #{:?} for network {:?}: {:?}",
|
||||
"🧐 Failed to submit clap #{} from authority #{:?} for network {:?}: {:?}",
|
||||
clap_index,
|
||||
authority_index,
|
||||
network_id,
|
||||
@ -144,36 +169,36 @@ impl EvmResponseType {
|
||||
|
||||
fn sign_and_submit_block_commitment<T: Config>(
|
||||
&self,
|
||||
block_now: BlockNumberFor<T>,
|
||||
from_block: ExternalBlockNumber,
|
||||
from_block: u64,
|
||||
authority_index: AuthIndex,
|
||||
authority_key: T::AuthorityId,
|
||||
session_index: SessionIndex,
|
||||
network_id: NetworkIdOf<T>,
|
||||
) {
|
||||
let block_commitment = BlockCommitment {
|
||||
session_index,
|
||||
let block_commitment = self.prepare_block_commitment::<T>(
|
||||
from_block,
|
||||
authority_index,
|
||||
session_index,
|
||||
network_id,
|
||||
last_stored_block: from_block,
|
||||
};
|
||||
);
|
||||
|
||||
let stored_last_updated = BlockCommitments::<T>::get(&network_id)
|
||||
.get(&authority_index)
|
||||
.map(|details| {
|
||||
details
|
||||
.last_updated
|
||||
.saturating_add(BLOCK_COMMITMENT_DELAY.unique_saturated_into())
|
||||
})
|
||||
.map(|details| details.last_updated)
|
||||
.unwrap_or_default();
|
||||
|
||||
if block_now < stored_last_updated {
|
||||
let current_last_updated = block_commitment
|
||||
.commitment
|
||||
.last_updated
|
||||
.saturating_sub(COMMITMENT_DELAY_MILLIS);
|
||||
|
||||
if current_last_updated < stored_last_updated {
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 New block commitment from authority #{:?} for network {:?}",
|
||||
"🧐 New block commitment from authority #{:?} for network {:?}",
|
||||
authority_index,
|
||||
network_id,
|
||||
);
|
||||
@ -183,7 +208,7 @@ impl EvmResponseType {
|
||||
None => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Block commitment signing failed from authority #{:?} for network {:?}",
|
||||
"🧐 Block commitment signing failed from authority #{:?} for network {:?}",
|
||||
authority_index,
|
||||
network_id,
|
||||
);
|
||||
@ -199,7 +224,7 @@ impl EvmResponseType {
|
||||
if let Err(e) = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Failed to submit block commitment from authority #{:?} for network {:?}: {:?}",
|
||||
"🧐 Failed to submit block commitment from authority #{:?} for network {:?}: {:?}",
|
||||
authority_index,
|
||||
network_id,
|
||||
e,
|
||||
@ -209,8 +234,7 @@ impl EvmResponseType {
|
||||
|
||||
pub fn sign_and_submit<T: Config>(
|
||||
&self,
|
||||
block_now: BlockNumberFor<T>,
|
||||
from_block: ExternalBlockNumber,
|
||||
from_block: u64,
|
||||
authority_index: AuthIndex,
|
||||
authority_key: T::AuthorityId,
|
||||
session_index: SessionIndex,
|
||||
@ -224,7 +248,6 @@ impl EvmResponseType {
|
||||
network_id,
|
||||
),
|
||||
EvmResponseType::BlockNumber(_) => self.sign_and_submit_block_commitment::<T>(
|
||||
block_now,
|
||||
from_block,
|
||||
authority_index,
|
||||
authority_key,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
pub mod v3;
|
||||
|
||||
pub type MigrateV2ToV3<T> = frame_support::migrations::VersionedMigration<
|
||||
2,
|
||||
3,
|
||||
v3::CommitmentDetailsTypesChanged<T>,
|
||||
crate::Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
@ -1,71 +0,0 @@
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
storage,
|
||||
traits::{Get, UncheckedOnRuntimeUpgrade},
|
||||
weights::Weight,
|
||||
};
|
||||
use ghost_networks::NetworkDataInspectHandler;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::traits::{BlockNumberProvider, UniqueSaturatedInto};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
use crate::{AuthIndex, BTreeMap, BlockCommitments, CommitmentDetails, Config, Pallet, LOG_TARGET};
|
||||
|
||||
#[derive(
|
||||
RuntimeDebug,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct OldCommitmentDetails {
|
||||
pub last_stored_block: u64,
|
||||
pub last_updated: u64,
|
||||
pub commits: u64,
|
||||
}
|
||||
|
||||
pub struct CommitmentDetailsTypesChanged<T>(PhantomData<T>);
|
||||
impl<T: Config> UncheckedOnRuntimeUpgrade for CommitmentDetailsTypesChanged<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(3);
|
||||
let current_block_number = <Pallet<T> as BlockNumberProvider>::current_block_number();
|
||||
|
||||
for network_id in T::NetworkDataHandler::iter_indexes() {
|
||||
let mut new_btree_map = BTreeMap::new();
|
||||
|
||||
let key_hash = BlockCommitments::<T>::hashed_key_for(&network_id);
|
||||
let old_commitments = storage::unhashed::take_or_default::<
|
||||
BTreeMap<AuthIndex, OldCommitmentDetails>,
|
||||
>(&key_hash);
|
||||
|
||||
for (authority_index, details) in old_commitments.iter() {
|
||||
new_btree_map.insert(
|
||||
authority_index,
|
||||
CommitmentDetails {
|
||||
last_stored_block: details.last_stored_block,
|
||||
last_updated: current_block_number,
|
||||
commits: details.commits.unique_saturated_into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
BlockCommitments::<T>::insert(&network_id, new_btree_map);
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"👻 Block commitments migrated from unix timestamp to internal block for network {:?}",
|
||||
network_id,
|
||||
);
|
||||
}
|
||||
|
||||
weight
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,6 @@ use frame_support::{
|
||||
traits::{ConstU32, ConstU64},
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use ghost_traits::evictor::StakingEvictor;
|
||||
use pallet_session::historical as pallet_session_historical;
|
||||
use sp_runtime::{
|
||||
curve::PiecewiseLinear,
|
||||
@ -20,8 +19,8 @@ use sp_staking::{
|
||||
|
||||
use sp_runtime::BuildStorage;
|
||||
|
||||
use crate::{self as slow_clap};
|
||||
use crate::{Config, ExposureListener};
|
||||
use crate as slow_clap;
|
||||
use crate::{ApplauseListener, Config, EraIndex};
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
@ -37,14 +36,17 @@ frame_support::construct_runtime!(
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub static EvicatedValidators: Vec<u64> = vec![];
|
||||
pub static FixedValidators: Vec<u64> = vec![0, 1, 2, 3];
|
||||
pub static Validators: Option<Vec<u64>> = Some(vec![
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
}
|
||||
|
||||
pub struct TestSessionManager;
|
||||
impl pallet_session::SessionManager<u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
|
||||
Some(FixedValidators::get())
|
||||
Validators::mutate(|l| l.take())
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
@ -52,14 +54,17 @@ impl pallet_session::SessionManager<u64> for TestSessionManager {
|
||||
|
||||
impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
|
||||
Some(FixedValidators::get().iter().map(|l| (*l, *l)).collect())
|
||||
Validators::mutate(|l| {
|
||||
l.take()
|
||||
.map(|validators| validators.iter().map(|v| (*v, *v)).collect())
|
||||
})
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
type IdentificationTuple = (u64, u64);
|
||||
type Offence = crate::SlowClapOffence<IdentificationTuple>;
|
||||
type Offence = crate::ThrottlingOffence<IdentificationTuple>;
|
||||
|
||||
parameter_types! {
|
||||
pub static Offences: Vec<(Vec<u64>, Offence)> = vec![];
|
||||
@ -85,7 +90,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut result = sp_io::TestExternalities::new(t);
|
||||
|
||||
result.execute_with(|| {
|
||||
for i in 0..=3 {
|
||||
for i in 1..=3 {
|
||||
System::inc_providers(&i);
|
||||
assert_eq!(
|
||||
Session::set_keys(RuntimeOrigin::signed(i), i.into(), vec![],),
|
||||
@ -132,7 +137,6 @@ parameter_types! {
|
||||
|
||||
parameter_types! {
|
||||
pub static MockAverageSessionLength: Option<u64> = None;
|
||||
pub const MaxNetworks: u32 = 5;
|
||||
}
|
||||
|
||||
impl ghost_networks::Config for Runtime {
|
||||
@ -142,7 +146,6 @@ impl ghost_networks::Config for Runtime {
|
||||
type RegisterOrigin = EnsureRoot<Self::AccountId>;
|
||||
type UpdateOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RemoveOrigin = EnsureRoot<Self::AccountId>;
|
||||
type MaxNetworks = MaxNetworks;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@ -161,7 +164,6 @@ 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)]
|
||||
@ -174,53 +176,24 @@ impl pallet_balances::Config for Runtime {
|
||||
|
||||
type Balance = u64;
|
||||
|
||||
pub struct TestStakingEvictor;
|
||||
impl StakingEvictor<u64> for TestStakingEvictor {
|
||||
fn try_evict_validator(who: &u64) -> bool {
|
||||
if !FixedValidators::get().contains(who) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut evicted_validators = EvicatedValidators::get();
|
||||
match evicted_validators.iter().position(|x| x == who) {
|
||||
Some(_) => false,
|
||||
None => {
|
||||
evicted_validators.push(*who);
|
||||
EvicatedValidators::set(evicted_validators);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestExposureListener;
|
||||
impl ExposureListener<Balance, u64> for TestExposureListener
|
||||
pub struct TestSomeCoolTrait;
|
||||
impl ApplauseListener<Balance> for TestSomeCoolTrait
|
||||
where
|
||||
Balance: AtLeast32BitUnsigned + From<u64>,
|
||||
{
|
||||
fn get_accounts_by_indexes(
|
||||
indexes_iterator: impl Iterator<Item = usize>,
|
||||
) -> sp_std::prelude::Vec<u64> {
|
||||
let all_validators = FixedValidators::get();
|
||||
indexes_iterator
|
||||
.filter_map(|i| all_validators.get(i).copied())
|
||||
.collect()
|
||||
fn get_current_era() -> EraIndex {
|
||||
1
|
||||
}
|
||||
|
||||
fn get_account_by_index(index: usize) -> Option<u64> {
|
||||
FixedValidators::get().get(index).copied()
|
||||
fn get_threshold_amount(_era: EraIndex) -> Balance {
|
||||
666_666_667u64.into()
|
||||
}
|
||||
|
||||
fn get_total_exposure() -> Balance {
|
||||
1_000_000_000u64.into()
|
||||
}
|
||||
|
||||
fn get_validator_exposure(validator: &u64) -> Balance {
|
||||
match validator {
|
||||
0 => 250_000_000u64,
|
||||
1 => 200_000_000u64,
|
||||
2 => 250_000_000u64,
|
||||
3 => 300_000_000u64,
|
||||
fn get_validator_total_exposure(_era: EraIndex, index: usize) -> Balance {
|
||||
match index {
|
||||
0 => 500_000_000u64,
|
||||
1 => 300_000_000u64,
|
||||
2 => 200_000_000u64,
|
||||
_ => 0,
|
||||
}
|
||||
.into()
|
||||
@ -237,15 +210,14 @@ impl Config for Runtime {
|
||||
type BlockNumberProvider = System;
|
||||
type ReportUnresponsiveness = OffenceHandler;
|
||||
type DisabledValidators = Session;
|
||||
type ExposureListener = TestExposureListener;
|
||||
type StakingEvictor = TestStakingEvictor;
|
||||
type ApplauseListener = TestSomeCoolTrait;
|
||||
|
||||
type MaxAuthorities = ConstU32<5>;
|
||||
type ApplauseThreshold = ConstU32<500_000_000>;
|
||||
type ApplauseThreshold = ConstU32<50>;
|
||||
type OffenceThreshold = ConstU32<0>;
|
||||
type UnsignedPriority = ConstU64<{ 1 << 20 }>;
|
||||
type HistoryDepth = HistoryDepth;
|
||||
type MinAuthoritiesNumber = ConstU32<1>;
|
||||
type EpochDuration = EpochDuration;
|
||||
type MinAuthoritiesNumber = ConstU32<2>;
|
||||
|
||||
type WeightInfo = ();
|
||||
}
|
||||
@ -264,7 +236,32 @@ pub fn advance_session() {
|
||||
let now = System::block_number().max(1);
|
||||
System::set_block_number(now + 1);
|
||||
Session::rotate_session();
|
||||
|
||||
let session_index = Session::current_index();
|
||||
|
||||
let authorities = Session::validators()
|
||||
.into_iter()
|
||||
.map(UintAuthorityId)
|
||||
.collect();
|
||||
|
||||
SlowClap::set_test_authorities(session_index, authorities);
|
||||
assert_eq!(session_index, (now / Period::get()) as u32);
|
||||
}
|
||||
|
||||
pub fn advance_session_with_authority(authority: u64) {
|
||||
let now = System::block_number().max(1);
|
||||
System::set_block_number(now + 1);
|
||||
Session::rotate_session();
|
||||
|
||||
let session_index = Session::current_index();
|
||||
|
||||
SlowClap::set_test_authorities(
|
||||
session_index,
|
||||
vec![
|
||||
UintAuthorityId::from(authority),
|
||||
UintAuthorityId::from(69),
|
||||
UintAuthorityId::from(420),
|
||||
UintAuthorityId::from(1337),
|
||||
],
|
||||
);
|
||||
assert_eq!(session_index, (now / Period::get()) as u32);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@
|
||||
//! Autogenerated weights for `ghost_slow_clap`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2026-02-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! DATE: 2025-11-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
|
||||
@ -49,28 +49,21 @@ use core::marker::PhantomData;
|
||||
pub trait WeightInfo {
|
||||
fn slow_clap() -> Weight;
|
||||
fn commit_block()-> Weight;
|
||||
fn try_offend_validators(validators_len: u32, offenders_len: u32) -> Weight;
|
||||
}
|
||||
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::LatestExecutedBlock` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::LatestExecutedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::ApplauseDetails` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::ApplauseDetails` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::Authorities` (r:1 w:0)
|
||||
/// Proof: `GhostSlowClaps::Authorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::DisabledAuthorityIndexes` (r:1 w:0)
|
||||
/// Proof: `GhostSlowClaps::DisabledAuthorityIndexes` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::TotalExposure` (r:1 w:0)
|
||||
/// Proof: `GhostSlowClaps::TotalExposure` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Session::Validators` (r:1 w:0)
|
||||
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Staking::CurrentEra` (r:1 w:0)
|
||||
/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Staking::ErasStakersOverview` (r:1 w:0)
|
||||
/// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
|
||||
/// Storage: `GhostSlowClaps::ReceivedClaps` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::ReceivedClaps` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::ClapsInSession` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::ClapsInSession` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::ApplausesForTransaction` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::ApplausesForTransaction` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::NullifyNeeded` (r:1 w:0)
|
||||
/// Proof: `GhostNetworks::NullifyNeeded` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::GatekeeperAmount` (r:1 w:1)
|
||||
/// Proof: `GhostNetworks::GatekeeperAmount` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::BridgedImbalance` (r:1 w:1)
|
||||
@ -81,70 +74,16 @@ impl WeightInfo for () {
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn slow_clap() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1417`
|
||||
// Estimated: `4882`
|
||||
// Minimum execution time: 305_397_000 picoseconds.
|
||||
Weight::from_parts(310_207_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 4882))
|
||||
.saturating_add(RocksDbWeight::get().reads(13))
|
||||
.saturating_add(RocksDbWeight::get().writes(6))
|
||||
// Measured: `355`
|
||||
// Estimated: `3820`
|
||||
// Minimum execution time: 213_817_000 picoseconds.
|
||||
Weight::from_parts(216_977_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3820))
|
||||
.saturating_add(RocksDbWeight::get().reads(10))
|
||||
.saturating_add(RocksDbWeight::get().writes(7))
|
||||
}
|
||||
/// Storage: `Session::CurrentIndex` (r:1 w:0)
|
||||
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::LatestExecutedBlock` (r:1 w:0)
|
||||
/// Proof: `GhostSlowClaps::LatestExecutedBlock` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
|
||||
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `GhostSlowClaps::BlockCommitments` (r:1 w:1)
|
||||
/// Proof: `GhostSlowClaps::BlockCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn commit_block() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `701`
|
||||
// Estimated: `4166`
|
||||
// Minimum execution time: 88_007_000 picoseconds.
|
||||
Weight::from_parts(89_662_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 4166))
|
||||
.saturating_add(RocksDbWeight::get().reads(4))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Staking::ActiveEra` (r:1 w:0)
|
||||
/// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Staking::ErasStakersOverview` (r:100000 w:0)
|
||||
/// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Staking::ErasStakers` (r:100000 w:0)
|
||||
/// Proof: `Staking::ErasStakers` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Session::Validators` (r:1 w:0)
|
||||
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Offences::ConcurrentReportsIndex` (r:1 w:1)
|
||||
/// Proof: `Offences::ConcurrentReportsIndex` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Offences::Reports` (r:97959 w:97959)
|
||||
/// Proof: `Offences::Reports` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Staking::SlashRewardFraction` (r:1 w:0)
|
||||
/// Proof: `Staking::SlashRewardFraction` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0)
|
||||
/// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Staking::Invulnerables` (r:1 w:0)
|
||||
/// Proof: `Staking::Invulnerables` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Staking::SlashingSpans` (r:97959 w:97959)
|
||||
/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Staking::DisabledValidators` (r:1 w:1)
|
||||
/// Proof: `Staking::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `n` is `[1, 100000]`.
|
||||
/// The range of component `d` is `[0, 100000]`.
|
||||
fn try_offend_validators(n: u32, d: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `674`
|
||||
// Estimated: `1886 + d * (2567 ±0)`
|
||||
// Minimum execution time: 13_294_008_000 picoseconds.
|
||||
Weight::from_parts(13_307_595_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1886))
|
||||
// Standard Error: 1_146_439
|
||||
.saturating_add(Weight::from_parts(29_632_292, 0).saturating_mul(n.into()))
|
||||
// Standard Error: 1_146_441
|
||||
.saturating_add(Weight::from_parts(14_654_685, 0).saturating_mul(d.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into())))
|
||||
.saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(d.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2567).saturating_mul(d.into()))
|
||||
|
||||
fn commit_block()-> Weight {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-sudo"
|
||||
version = "0.0.3"
|
||||
version = "0.0.2"
|
||||
description = "Port of the sudo pallet because of dependencies issue"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
//! Autogenerated weights for `ghost_sudo`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2026-09-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! DATE: 2025-07-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
|
||||
@ -28,7 +28,7 @@
|
||||
// --chain=casper-dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=ghost_sudo
|
||||
// --pallet=ghost-sudo
|
||||
// --extrinsic=*
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
@ -41,7 +41,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use frame_support::{
|
||||
traits::Get,
|
||||
traits::Get,
|
||||
weights::{Weight, constants::RocksDbWeight},
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
@ -57,14 +57,14 @@ pub trait WeightInfo {
|
||||
/// Weight functions for `ghost_sudo`.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Storage: `GhostSudo::Key` (r:1 w:1)
|
||||
/// Storage: `GhostSudo::Key` (r:1 w:1)
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn set_key() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 38_841_000 picoseconds.
|
||||
Weight::from_parts(39_982_000, 0)
|
||||
// Minimum execution time: 40_014_000 picoseconds.
|
||||
Weight::from_parts(40_856_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
@ -73,10 +73,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn sudo() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 42_412_000 picoseconds.
|
||||
Weight::from_parts(43_446_000, 0)
|
||||
// Minimum execution time: 44_086_000 picoseconds.
|
||||
Weight::from_parts(45_920_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
}
|
||||
@ -84,10 +84,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn sudo_as() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 43_006_000 picoseconds.
|
||||
Weight::from_parts(43_886_000, 0)
|
||||
// Minimum execution time: 44_106_000 picoseconds.
|
||||
Weight::from_parts(44_650_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
}
|
||||
@ -95,10 +95,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn remove_key() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 35_731_000 picoseconds.
|
||||
Weight::from_parts(36_443_000, 0)
|
||||
// Minimum execution time: 36_416_000 picoseconds.
|
||||
Weight::from_parts(37_373_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
@ -107,14 +107,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `GhostSudo::Key` (r:1 w:1)
|
||||
/// Storage: `GhostSudo::Key` (r:1 w:1)
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn set_key() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 38_841_000 picoseconds.
|
||||
Weight::from_parts(39_982_000, 0)
|
||||
// Minimum execution time: 40_014_000 picoseconds.
|
||||
Weight::from_parts(40_856_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
@ -123,10 +123,10 @@ impl WeightInfo for () {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn sudo() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 42_412_000 picoseconds.
|
||||
Weight::from_parts(43_446_000, 0)
|
||||
// Minimum execution time: 44_086_000 picoseconds.
|
||||
Weight::from_parts(45_920_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
}
|
||||
@ -134,10 +134,10 @@ impl WeightInfo for () {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn sudo_as() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 43_006_000 picoseconds.
|
||||
Weight::from_parts(43_886_000, 0)
|
||||
// Minimum execution time: 44_106_000 picoseconds.
|
||||
Weight::from_parts(44_650_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
}
|
||||
@ -145,10 +145,10 @@ impl WeightInfo for () {
|
||||
/// Proof: `GhostSudo::Key` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
fn remove_key() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `98`
|
||||
// Measured: `165`
|
||||
// Estimated: `1517`
|
||||
// Minimum execution time: 35_731_000 picoseconds.
|
||||
Weight::from_parts(36_443_000, 0)
|
||||
// Minimum execution time: 36_416_000 picoseconds.
|
||||
Weight::from_parts(37_373_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 1517))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-traits"
|
||||
version = "0.4.6"
|
||||
description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more."
|
||||
version = "0.3.23"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
@ -9,24 +8,12 @@ homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
num-traits = { workspace = true }
|
||||
frost-core = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
frame-support = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"frame-support/std",
|
||||
"num-traits/std",
|
||||
"sp-arithmetic/std",
|
||||
"rand_chacha/std",
|
||||
"frost-core/std",
|
||||
"sp-runtime/std",
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
use sp_arithmetic::traits::AtLeast8BitUnsigned;
|
||||
use sp_runtime::traits::UniqueSaturatedInto;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub trait BoundedBitmapMetadata {
|
||||
fn total_bits_capacity() -> u32;
|
||||
fn chunk_size() -> u32;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapWriter {
|
||||
fn insert<N: Copy + TryInto<usize>>(&mut self, i: N) -> Option<N>;
|
||||
fn remove<N: Copy + TryInto<usize>>(&mut self, i: N) -> Option<N>;
|
||||
fn truncate(&mut self);
|
||||
fn nullify(&mut self);
|
||||
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapReader {
|
||||
fn active_bits_count(&self) -> u32;
|
||||
fn highest_bit<N>(&self) -> Option<N>
|
||||
where
|
||||
usize: UniqueSaturatedInto<N>;
|
||||
fn count_ones<N>(&self) -> N
|
||||
where
|
||||
u32: UniqueSaturatedInto<N>;
|
||||
fn count_zeros<N>(&self) -> N
|
||||
where
|
||||
u32: UniqueSaturatedInto<N>;
|
||||
fn is_empty(&self) -> bool;
|
||||
fn contains<N>(&self, index: N) -> bool
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapGenerator {
|
||||
fn empty<N>(n: N) -> Self
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
fn all_ones<N>(n: N) -> Self
|
||||
where
|
||||
N: UniqueSaturatedInto<usize>;
|
||||
fn empty_from(bitmap: &Self) -> Self;
|
||||
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
|
||||
where
|
||||
Outer: AtLeast8BitUnsigned + num_traits::PrimInt;
|
||||
fn from_vec<Outer>(values: &Vec<Outer>) -> Self
|
||||
where
|
||||
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy;
|
||||
}
|
||||
|
||||
pub trait BoundedBitmapIterable {
|
||||
type IntoIter<'a, ItemType>: Iterator<Item = ItemType> + 'a
|
||||
where
|
||||
Self: 'a,
|
||||
ItemType: 'static,
|
||||
u32: UniqueSaturatedInto<ItemType>;
|
||||
|
||||
fn iter<ItemType>(&self) -> Self::IntoIter<'_, ItemType>
|
||||
where
|
||||
ItemType: 'static,
|
||||
u32: UniqueSaturatedInto<ItemType>;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
pub trait StakingEvictor<AccountId> {
|
||||
fn try_evict_validator(who: &AccountId) -> bool;
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
use frost_core::{Ciphersuite, Group, Identifier};
|
||||
use rand_chacha::rand_core::{CryptoRng, RngCore};
|
||||
use sp_std::{
|
||||
collections::btree_map::BTreeMap,
|
||||
marker::PhantomData,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
pub trait IdentifierConverter<A, E> {
|
||||
fn non_zero_index(index: A) -> Result<A, E>;
|
||||
fn convert_identifier_to_index(bytes: &[u8]) -> Result<A, E>;
|
||||
}
|
||||
|
||||
pub trait FlexibleRoundOptimizedSchnorrThresholdSignature<A, E>: MerkleTreeBuilder<A, E> {
|
||||
type Packages<'a>;
|
||||
type NoncePackages<'a>;
|
||||
|
||||
fn header_bytes_len(&self) -> usize;
|
||||
fn element_bytes_len(&self) -> usize;
|
||||
fn signature_bytes_len(&self) -> usize;
|
||||
fn scalar_bytes_len(&self) -> usize;
|
||||
|
||||
fn verify_signature_share(
|
||||
&self,
|
||||
i: A,
|
||||
is: impl Iterator<Item = A>,
|
||||
ss: &[u8],
|
||||
bf: &[u8],
|
||||
nh: &[u8],
|
||||
bh: &[u8],
|
||||
gc: &[u8],
|
||||
vs: &[u8],
|
||||
vk: &[u8],
|
||||
m: &[u8],
|
||||
) -> Result<(), E>;
|
||||
fn is_signature_valid(&self, vk: &[u8], r: &[u8], z: &[u8], m: &[u8]) -> Result<(), E>;
|
||||
fn accumulate_signature_scalar(&self, ss: &[u8], sh: &[u8]) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn generate_nonce_commitment<R>(&self, sk: &[u8], r: R) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), E>
|
||||
where
|
||||
R: CryptoRng + RngCore;
|
||||
|
||||
fn generate_group_commitment(
|
||||
&self,
|
||||
a: A,
|
||||
sc: &Self::NoncePackages<'_>,
|
||||
vk: &[u8],
|
||||
m: &[u8],
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Vec<Self::Hash>, Self::Hash), E>;
|
||||
fn init_signing_package(&self, n: &Self::NoncePackages<'_>, m: &[u8]) -> Result<Vec<u8>, E>;
|
||||
fn partial_sign_message(&self, sp: &[u8], sn: &[u8], ss: &[u8]) -> Result<Vec<u8>, E>;
|
||||
}
|
||||
|
||||
pub trait EllipticCurveDiffieHellman<A, C, D, K, E> {
|
||||
fn ecdh_encrypt_package<R>(&self, c: &C, aad: &[u8], m: &[u8], r: R) -> Result<D, E>
|
||||
where
|
||||
R: RngCore + CryptoRng;
|
||||
|
||||
fn ecdh_decrypt_package(&self, c: &C, d: &D, aad: &[u8]) -> Result<Vec<u8>, E>;
|
||||
fn ecdh_prepare_additional_info(&self, si: A, ri: A, di: K, _m: PhantomData<D>) -> Vec<u8>;
|
||||
fn ecdh_get_cipher_from_keys(&self, rp: &[u8], sp: &[u8], i: &[u8], _m: PhantomData<D>) -> Result<C, E>;
|
||||
}
|
||||
|
||||
pub trait DistributedKeyGeneration<A, E>: MerkleTreeBuilder<A, E> {
|
||||
type RoundPackages;
|
||||
type Part1Result;
|
||||
type Part2Result;
|
||||
type Part3Result;
|
||||
|
||||
fn dkg_narrow_round_max_signers(&self, s: &[u8], m: A, r: u8) -> Result<Vec<u8>, E>;
|
||||
fn dkg_verify_proof_of_knowledge(&self, i: A, r1: &[u8]) -> Result<A, E>;
|
||||
fn dkg_part1<R>(&self, i: A, max: A, min: A, rng: R) -> Self::Part1Result
|
||||
where
|
||||
R: RngCore + CryptoRng;
|
||||
|
||||
fn dkg_part2(&self, r1_s: &[u8], r1_pm: &Self::RoundPackages) -> Self::Part2Result;
|
||||
fn dkg_verify_private_package(&self, i: A, r1: &[u8], r2: &[u8]) -> Result<(), E>;
|
||||
|
||||
fn dkg_part3(
|
||||
&self,
|
||||
i: A,
|
||||
r2_s: &[u8],
|
||||
r1_pm: &Self::RoundPackages,
|
||||
r2_pm: &Self::RoundPackages,
|
||||
) -> Self::Part3Result;
|
||||
|
||||
fn dkg_derive_verifying_share(&self, sk: &[u8]) -> Result<Vec<u8>, E>;
|
||||
}
|
||||
|
||||
pub trait MerkleTreeBuilder<A, E>: IdentifierConverter<A, E> {
|
||||
type Hash;
|
||||
|
||||
fn build_merkle_tree<C, V, F>(
|
||||
verifying_shares: &BTreeMap<Identifier<C>, V>,
|
||||
serialize_fn: F,
|
||||
) -> Result<Vec<Self::Hash>, E>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn generate_merkle_proof_from_tree<C, V, F>(
|
||||
verifying_shares: &BTreeMap<Identifier<C>, V>,
|
||||
merkle_tree: &[Self::Hash],
|
||||
authority_index: A,
|
||||
serialize_fn: F,
|
||||
) -> Result<(Vec<u8>, Vec<Self::Hash>), E>
|
||||
where
|
||||
C: frost_core::Ciphersuite,
|
||||
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
|
||||
F: Fn(&V) -> Result<Vec<u8>, E>;
|
||||
|
||||
fn verify_merkle_proof(
|
||||
&self,
|
||||
verifying_share: &[u8],
|
||||
merkle_proof: &[Self::Hash],
|
||||
merkle_root: Self::Hash,
|
||||
authority_index: A,
|
||||
) -> Result<(), E>;
|
||||
}
|
||||
|
||||
pub trait EvmGovernanceRegistrar<NetworkId, EvmAddress, GovernanceAction> {
|
||||
fn register_evm_governance_action(
|
||||
network_id: NetworkId,
|
||||
target_address: EvmAddress,
|
||||
governance_action: GovernanceAction
|
||||
) -> frame_support::dispatch::DispatchResult;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub trait GhostHasher {
|
||||
type Hash: PartialEq + Copy + AsRef<[u8]> + AsMut<[u8]>;
|
||||
type HashBytes: AsRef<[u8]> + AsMut<[u8]> + TryFrom<Vec<u8>> + Default;
|
||||
|
||||
fn from_slice(bytes: &[u8]) -> Self::Hash {
|
||||
let mut target_hash = Self::empty();
|
||||
|
||||
let hash_buffer = target_hash.as_mut();
|
||||
let copy_len = bytes.len().min(hash_buffer.len());
|
||||
|
||||
hash_buffer[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
|
||||
target_hash
|
||||
}
|
||||
|
||||
fn hash_to_bytes(hash: Self::Hash) -> Self::HashBytes {
|
||||
let mut bytes = Self::HashBytes::default();
|
||||
bytes.as_mut().copy_from_slice(hash.as_ref());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn hash(data: &[u8]) -> Self::Hash;
|
||||
fn empty() -> Self::Hash;
|
||||
fn hash_len() -> usize;
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod bounded_bitmap;
|
||||
pub mod exodus;
|
||||
pub mod hashing;
|
||||
pub mod networks;
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
use crate::converter::IdentifierConverter;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_core::H256;
|
||||
|
||||
pub trait MerkleTree<A, E>: IdentifierConverter<A, E> {
|
||||
type VerifyingShares;
|
||||
type Hash: AsRef<[u8]>;
|
||||
|
||||
fn merkle_build_tree(vs: &Self::VerifyingShares) -> Result<Vec<Self::Hash>, E>;
|
||||
fn merkle_generate_proof_from_tree(vsm: &Self::VerifyingShares, t: &[Self::Hash], i: A) -> Result<(Vec<u8>, Vec<H256>), E>;
|
||||
fn merkle_verify_proof(&self, vs: &[u8], p: &[Self::Hash], r: Self::Hash, i: A) -> Result<(), E>;
|
||||
}
|
||||
@ -1,26 +1,10 @@
|
||||
use frame_support::{pallet_prelude::*, storage::PrefixIterator};
|
||||
use sp_std::vec::Vec;
|
||||
use sp_runtime::{
|
||||
offchain::http::PendingRequest,
|
||||
traits::{AtLeast32BitUnsigned, Member},
|
||||
DispatchResult,
|
||||
};
|
||||
|
||||
pub trait NetworkDataBasicHandler {
|
||||
type NetworkCurve: Parameter
|
||||
+ Member
|
||||
+ Default
|
||||
+ Copy
|
||||
+ TypeInfo
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
type NetworkType: Parameter
|
||||
+ Member
|
||||
+ Default
|
||||
+ Copy
|
||||
+ TypeInfo
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
type NetworkId: Parameter
|
||||
+ Member
|
||||
+ AtLeast32BitUnsigned
|
||||
@ -32,45 +16,28 @@ pub trait NetworkDataBasicHandler {
|
||||
}
|
||||
|
||||
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
|
||||
fn count() -> u32;
|
||||
fn contains_key(n: &Self::NetworkId) -> bool;
|
||||
fn curve_exists(c: &Self::NetworkCurve) -> bool;
|
||||
fn network_for_block(b: impl Into<usize>) -> Option<(Self::NetworkId, Network)>;
|
||||
fn get(n: &Self::NetworkId) -> Option<Network>;
|
||||
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
|
||||
fn iter_indexes() -> impl Iterator<Item = Self::NetworkId>;
|
||||
fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve>;
|
||||
fn iter_types_by_curve(n: &Self::NetworkCurve) -> impl Iterator<Item = Self::NetworkType>;
|
||||
fn is_nullification_period() -> bool;
|
||||
}
|
||||
|
||||
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {
|
||||
fn register(chain_id: Self::NetworkId, network: Network) -> DispatchResult;
|
||||
fn remove(chain_id: Self::NetworkId) -> DispatchResult;
|
||||
|
||||
fn register_incoming(chain_id: &Self::NetworkId, amount: Balance) -> Result<Balance, ()>;
|
||||
fn register_outgoing(chain_id: &Self::NetworkId, amount: Balance) -> Result<Balance, ()>;
|
||||
}
|
||||
fn increase_gatekeeper_amount(
|
||||
chain_id: &Self::NetworkId,
|
||||
amount: &Balance,
|
||||
) -> Result<Balance, ()>;
|
||||
fn decrease_gatekeeper_amount(
|
||||
chain_id: &Self::NetworkId,
|
||||
amount: &Balance,
|
||||
) -> Result<Balance, ()>;
|
||||
|
||||
pub trait NetworkRpcResolver<W, B, H, I, S> {
|
||||
fn get_block_request_body(&self, id: I) -> Vec<u8>;
|
||||
fn get_hash_request_body(
|
||||
&self,
|
||||
id: I,
|
||||
block: B,
|
||||
session: S,
|
||||
contract_ref: &[u8],
|
||||
selector_ref: &[u8],
|
||||
) -> Vec<u8>;
|
||||
fn parse_block_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: I,
|
||||
) -> Vec<B>;
|
||||
fn parse_hash_requests(
|
||||
&self,
|
||||
pending_requests: Vec<PendingRequest>,
|
||||
deadline: sp_runtime::offchain::Timestamp,
|
||||
request_id: I,
|
||||
) -> Vec<(H, S)>;
|
||||
fn accumulate_outgoing_imbalance(amount: &Balance) -> Result<Balance, ()>;
|
||||
fn accumulate_incoming_imbalance(amount: &Balance) -> Result<Balance, ()>;
|
||||
|
||||
fn accumulate_commission(commission: &Balance) -> Result<Balance, ()>;
|
||||
fn nullify_commission();
|
||||
fn trigger_nullification();
|
||||
}
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
[package]
|
||||
name = "ghost-weaver"
|
||||
version = "0.0.8"
|
||||
description = "Weaving a secure cryptographic tapestry across different external chains."
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["alloc"] }
|
||||
|
||||
codec = { workspace = true, features = ["max-encoded-len"] }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
|
||||
frame-benchmarking = { workspace = true, optional = true }
|
||||
frame-support = { workspace = true }
|
||||
frame-system = { workspace = true }
|
||||
pallet-balances = { workspace = true, optional = true }
|
||||
|
||||
sp-keystore = { workspace = true, optional = true }
|
||||
sp-application-crypto = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-arithmetic = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
sp-io = { workspace = true }
|
||||
|
||||
ghost-networks = { workspace = true }
|
||||
ghost-helpers = { workspace = true }
|
||||
ghost-traits = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-keystore = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"log/std",
|
||||
"hex/std",
|
||||
"codec/std",
|
||||
"num-traits/std",
|
||||
"scale-info/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"frame-benchmarking?/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-runtime/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-keystore?/std",
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
"sp-io/std",
|
||||
"ghost-networks/std",
|
||||
"ghost-helpers/std",
|
||||
"ghost-traits/std",
|
||||
"pallet-balances/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"ghost-networks/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"ghost-helpers/runtime-benchmarks",
|
||||
"sp-keystore"
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"ghost-networks/try-runtime",
|
||||
"pallet-balances/try-runtime",
|
||||
]
|
||||
@ -1,389 +0,0 @@
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::vec;
|
||||
|
||||
use ghost_helpers::networks::NetworkDataBuilder;
|
||||
|
||||
fn prepare_pallet<T: Config>(
|
||||
authorities_len: u32
|
||||
) -> (NetworkIdOf<T>, T::AuthorityId, AuthIndex) {
|
||||
let authority = T::AuthorityId::generate_pair(None);
|
||||
let authorities = vec![authority.clone(); authorities_len as usize];
|
||||
|
||||
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
|
||||
.expect("more than the maximum number of keys provided");
|
||||
|
||||
Authorities::<T>::put(bounded_authorities);
|
||||
|
||||
let network_id = NetworkIdOf::<T>::default();
|
||||
let network_data = NetworkDataBuilder::default()
|
||||
.with_network_type(NetworkType::Utxo)
|
||||
.with_gatekeeper(
|
||||
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
|
||||
hex::decode("4635b1794a22bfbe6c5c5eba17b693f4aaf0e348")
|
||||
.expect("Bytes32 from UTXO should be valid"),
|
||||
)
|
||||
.build();
|
||||
|
||||
T::NetworkDataHandler::register(network_id, network_data)
|
||||
.expect("network should be valid for insertion");
|
||||
|
||||
(network_id, authority, 0)
|
||||
}
|
||||
|
||||
#[benchmarks(where T: Config)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn extend_warp(
|
||||
a: Linear<1, { T::MaxAuthorities::get() as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let (network_id, authority, authority_index) = prepare_pallet::<T>(a);
|
||||
|
||||
TapestryDrafts::<T>::remove(&network_id);
|
||||
|
||||
let block_attestation = AttestationContext::default()
|
||||
.with_network_id(network_id)
|
||||
.with_authority_index(authority_index)
|
||||
.build_block_attestation(420);
|
||||
|
||||
let signature = authority
|
||||
.sign(&block_attestation.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, block_attestation, signature);
|
||||
|
||||
let current_attestations = NetworkAttestations::<T>::get(&network_id);
|
||||
assert_eq!(current_attestations.len(), a as usize);
|
||||
assert_eq!(current_attestations[authority_index as usize], Some(420));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn weave_weft(
|
||||
a: Linear<1, { T::MaxAuthorities::get() as u32 }>
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let weaving_session = 420;
|
||||
let next_session = weaving_session + 1;
|
||||
let root_hash = H256::repeat_byte(0x69);
|
||||
|
||||
let (network_id, authority, authority_index) = prepare_pallet::<T>(a);
|
||||
|
||||
let tapestry_key = (network_id, weaving_session, next_session, root_hash);
|
||||
TapestryStrands::<T>::mutate(&tapestry_key, |tapestry| {
|
||||
(0..a).for_each(|i| {
|
||||
let index = i as AuthIndex;
|
||||
if index == authority_index { return; }
|
||||
tapestry.insert(index);
|
||||
});
|
||||
});
|
||||
|
||||
let current_block = T::BlockNumberProvider::current_block_number();
|
||||
let dummy_draft = TapestryDraftBuilder::default()
|
||||
.with_current_block(current_block)
|
||||
.with_block_delay(T::WeavingDelay::get())
|
||||
.with_median_external(1337 as ExternalBlockNumber)
|
||||
.build();
|
||||
|
||||
TapestryDrafts::<T>::insert(&network_id, dummy_draft);
|
||||
|
||||
let hash_attestation = AttestationContext::default()
|
||||
.with_network_id(network_id)
|
||||
.with_weaving_session(weaving_session)
|
||||
.with_authority_index(authority_index)
|
||||
.build_hash_attestation(root_hash, next_session);
|
||||
|
||||
let signature = authority
|
||||
.sign(&hash_attestation.encode())
|
||||
.expect("Signature should be created");
|
||||
|
||||
let tapestry_strand_key = (network_id, weaving_session, next_session, root_hash);
|
||||
|
||||
let weaving_state = WeavingStates::<T>::get(&network_id);
|
||||
assert_eq!(weaving_state.hash, H256::default());
|
||||
assert_eq!(weaving_state.next_session, 0);
|
||||
assert_eq!(weaving_state.count, 0);
|
||||
|
||||
assert!(!TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
|
||||
assert_eq!(
|
||||
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
|
||||
a - 1
|
||||
);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, hash_attestation, signature);
|
||||
|
||||
let new_weaving_state = WeavingStates::<T>::get(&network_id);
|
||||
assert_eq!(new_weaving_state.hash, root_hash);
|
||||
assert_eq!(new_weaving_state.next_session, next_session);
|
||||
assert_eq!(new_weaving_state.count, a as AuthIndex);
|
||||
|
||||
assert!(TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
|
||||
assert_eq!(
|
||||
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
|
||||
a
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn pull_utxo_thread(
|
||||
a: Linear<1, { crate::types::UTXO_MAX_HISTORY_DEPTH }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let session = 69;
|
||||
let max_history_depth = a as usize;
|
||||
let (network_id, _, _) = prepare_pallet::<T>(1);
|
||||
|
||||
// Real world tx at block #481838
|
||||
// Block https://blockstream.info/block/0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a
|
||||
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
|
||||
|
||||
let historical_hashes = vec![
|
||||
hex::decode("000000204a9037b3465b3c657997cddcffed602a949ee139cbdc1b010000000000000000669c3e2472384bc95993f154aada38d6b2113d8d32069e32e2724ba40915a1802a669e59e93c011801c8c735").unwrap(),
|
||||
hex::decode("02000020926b3b1cfec537afaa88f002a45a5a5c3dc25c0664dae7000000000000000000af0f270bf3f868d47089f395197809c360f1506636c960d96afed68bbd72d0b4496d9e59e93c01183ac94b67").unwrap(),
|
||||
hex::decode("0000002093b3d5716570bdd5afd75a1c7d87873bacc062936dda630000000000000000009ab437e7c66b10a59b67127f290d24f381aba4e03d5e16c062a356eea4d608dea56d9e59e93c0118264f310e").unwrap(),
|
||||
hex::decode("0200002026309618b52c68271702f57fbe684326663374eb6360ec00000000000000000023c441f913d1c77fb635544f9061fa761f876ce7d93ccbef5dc7f88e84891cb1aa6e9e59e93c01185bce3c9b").unwrap(),
|
||||
hex::decode("00000020a3776fc8ad2be759cd4bd3d60eaf8d8225c869864490520000000000000000006a8e7d27c71b9886b090a7422e7b5c12bb2c88255af0273d2724b69bb8c1a817b3709e59e93c01186a257ab3").unwrap(),
|
||||
hex::decode("1200002076d65fb74b1285c9b4d809645f6bb6f8f076a36295d9d400000000000000000062c8a7f4f7aa43726f753304ac9bc071877ef81273fa5f9e2f8e6f2df92f1211eb739e59e93c0118b2f12aee").unwrap(),
|
||||
hex::decode("02000020762d95fbba0466c983446595dd608487ffafaa71e123d9000000000000000000e639b7f88ac7d1187bf9e8c82e8e504eabb2af5f0f05beaaefa09bff7d7314d6a07a9e59e93c0118be840af6").unwrap(),
|
||||
hex::decode("020000203e5a55d70de3f462efd40e8780adee7dfdb451b57eda230100000000000000003efb1342b5fe12376a09f89a1dbb775e5855f9a1accc41b40929ec911ec1efd6f07f9e59e93c011829615752").unwrap(),
|
||||
hex::decode("020000206cac3182c85d630fe6938284a88177b1265f8875738b0a0000000000000000006b9d69ba608cc06e8d519373c2d0a78a62b0bcffd150e37f79f31e97a8af1af4568c9e59e93c01185f5c7c32").unwrap(),
|
||||
hex::decode("02000020e2f550d7404324de1bd8c83a79a1c0133635a34269120700000000000000000057819057a8a85c8f1cafb13e5eed7d6739e13d855d63421c9cef5bf1a98101a33b939e59e93c0118ee220467").unwrap(),
|
||||
hex::decode("12000020ce9c0848d500c2fea3efd38f4e2822d8962555a1d68b2f000000000000000000e38b12110e6b8978155dad2724cf0ce4d8ab827712f8e750ce7be253227a10bdc4969e59e93c0118dc3403b6").unwrap(),
|
||||
hex::decode("020000202431a9c869c26ce0e76b1ed6c6543be9b7fff0ace4735d00000000000000000073811d01111496203c08c0e5941fee2c7791e1b7cfdc852258864c873df8fa6b6c9e9e59e93c01187a4ed56c").unwrap(),
|
||||
hex::decode("12000020808101492d7f3237125b79288a39cf6f71bc279e45b5c9000000000000000000d7d57e05a60bae464dced00c83eff37b447fcb07e95fadce2f610267146c622fa2a39e59e93c0118919a5f2a").unwrap(),
|
||||
hex::decode("020000207272193a5971c9c5b8440ade64d14a0de46baa43c3faa3000000000000000000e7b1dbce250ea8e3e44e601fb26cb814e6e6e7b3b3e87e6239f6a745e3c9194780a79e59e93c011849f1593d").unwrap(),
|
||||
hex::decode("020000202e6cfa8b6c2ee74f0648137131bf782c6756a7f8462f4100000000000000000056fb99126c1a78c143d86e89787de7bfc08f5492bd26df1c4314280a34e64be797a79e59e93c0118ae471ff4").unwrap(),
|
||||
hex::decode("00000020be74317e078482f6a5a3723044aa62cb858b82dffd1be300000000000000000051111cc62fdcd7de11a577853ee3e4832394963da62e5bae92173ae2f366e4dabba89e59e93c01185adb0d11").unwrap(),
|
||||
hex::decode("000000209cd483b9091395be33ed7712c0e8c3695b35ed8f62e71b01000000000000000046df5dea1198544e5bbbbe9db155f97f79716cd811f8d208c06be7603b70c742a4ac9e59e93c01180c032031").unwrap(),
|
||||
hex::decode("000000207936ae0f3ffbf1673598053377e6283e3a27d9fb023e440000000000000000002ee47a3ec50659fbfb117eb7c21ed26b493908bb9f8d172ed19cea85e402e66b57b59e59e93c01188178d5bf").unwrap(),
|
||||
hex::decode("0000002094fb4f7b930b3c540db44b2dfc909ef5baccba57b435ee0000000000000000006dd2cc7164d9e6d2d0400609cf5d76531391ca93f879bbbcfafbf560b6b031e8cbb79e59e93c0118374c86df").unwrap(),
|
||||
hex::decode("00000020e4085486f70e220273bff0fa6fc767eb74b69d2c978d26000000000000000000c991b50232e52220d94177b6ee0b606c52c010037070fc101945e160ca24786c38b99e59e93c0118c1d0e6a4").unwrap(),
|
||||
hex::decode("020000200a5d7c8f4134ebd1e3aa19ad554dd8fd3281c94f8fda1b000000000000000000a95d611bdd5d90d2710a62915ebb1592d1bf224b104e3b8f5b289fe873d021eaf1bd9e59e93c0118d5e7ef59").unwrap(),
|
||||
hex::decode("00000020d7cafeddd0815ba95c6e90afc16970e0514729c0a7cd600000000000000000001b3886b882cfaca6d59367c1406f9b7556fc22c190b11ff414b8b2f0269c968aeec19e59e93c0118434c5f61").unwrap(),
|
||||
hex::decode("020000204384ba6db53aa87a604259c9f9c6844b99ced37e29db0b0100000000000000000c24d07c38afc18bd830aebc2476564df8fa64dfcc8d44dd68ed7df3fc5d206795c49e59e93c0118b415f46f").unwrap(),
|
||||
hex::decode("000000209986db62bd16a5de375470344aeec6d17ea322972d75bb000000000000000000d5c8ccb89f92e3b16e3a230d941d7ccc946b91fddae922c08c3245823210f47f43c89e59e93c011843c47fea").unwrap(),
|
||||
hex::decode("000000201e8afdebfade79e0a483e56f2fb87a277a513769add0410000000000000000003b8b9fc7fb33f2b8c8520dcf76eb751017c06d0ed4bac49c2ebdd036abd9ee76a4c99e59e93c0118b7b6dbcd").unwrap(),
|
||||
hex::decode("000000200a5fa5f3d854fecd0c8fea9a71879443347058ddec1c0f0000000000000000009d3249cb64dda39bf0ed9ab4b665780f6c6aa72d35460bd972f1212a32e84c75a5c99e59e93c0118ae216161").unwrap(),
|
||||
hex::decode("020000201cdefc545aa71150e97a9763785dbc2855710f33ba3f1e010000000000000000e072dd1b8043b5dd632d4a91769cb91ca55c0343dc3d8cbbbf838c6efb1940d229d09e59e93c011853549672").unwrap(),
|
||||
hex::decode("00000020375d4cbf7b4d0550803dcf160ebd264bcb594d3c82c56700000000000000000058dee1ace217c483b05544f9e5d77da2857b5b443380d22761ff14869fd8ea473dd39e59e93c01187e31b1b4").unwrap(),
|
||||
hex::decode("02000020560be37ca005f2c792755368aaf63b5a664ea36c2c69be00000000000000000026cda0eeaf84644b2aa9d072e5b5bf14c8c8b707a671e4b2232c7e569fc2a07763d69e59e93c0118e2b558a6").unwrap(),
|
||||
hex::decode("02000020546da8a3fde6bd1f52247bc8a6cd0251e772eae3b5556600000000000000000029b4e20ecbc778975e189bd28631e94b2809422d8efef15b7e440f624e79d9964fda9e59e93c01184f91c402").unwrap(),
|
||||
hex::decode("020000200bee1075fc01582e0b453bfacfe49add055d6cbccea7a500000000000000000020e6280b3d966f5af7290a164087a6c8cb177b41ec7be91035d0f5c45f1434f129dc9e59e93c0118cb35d326").unwrap(),
|
||||
hex::decode("00000020f07f65f1ed56e656a942eb424dbc76d78cde25acf1e7f30000000000000000006c6419dc5a46235b11f9a14f472ec166f7ee40f4ba8f031294c9ec8a20471bd6ffdd9e59e93c0118f55c1c76").unwrap(),
|
||||
hex::decode("020000201c30d0ecfff1a55c6531ce992467b0a02d25d6a545120e0100000000000000004349f28fae9da9ced1744f9988d6c7ffffe10892e2ee2a0a3228f2555c13639461e19e59e93c011802b33340").unwrap(),
|
||||
hex::decode("02000020f47f05efbfddee4a1d0a0c502f8a9604ef2236e60b02c8000000000000000000ad0f318ca8bb97a73a3490ac79c8cd1922ec53084493a5d2dd7c6bbe64889b1766e29e59e93c0118184be748").unwrap(),
|
||||
hex::decode("02000020a9446ad105a3a4434273e72d9d47f23544bd2d4223d4fc00000000000000000081078f171421ea27808a8bf0e74d9679d9e756197a832d17e44cad3a67e5b8104be79e59e93c011822774361").unwrap(),
|
||||
hex::decode("0200002060e290b87ee42f2d9497b8d588a144c8ff3013d4036ee4000000000000000000cf51f2b3e82a3da5adbf8baa8da3ce0dde7d9bc0f68ea7d241bfe7ddb6a6a5302be89e59e93c011841dd5d0e").unwrap(),
|
||||
hex::decode("0000002037e7cd68d910094e9963ae1953173141bd1dd22756bf1801000000000000000098416fb809d13de6c54ccce4927b167e9d561db613df3cd05fd2345daa1ceee5b5e99e59e93c0118a9f6e6fd").unwrap(),
|
||||
hex::decode("00000020cd116024d228583ad1f60c649452ff34224cba9943288f000000000000000000b9c1df740343350581e7b896f3e8edb4ed5a04d3f6fbccff4f69fa272916b110ffea9e59e93c011875c11682").unwrap(),
|
||||
hex::decode("0000002050599ca8da62a8cfd3a07144f555651df0036933cf72c0000000000000000000485624901693271d8c6e343e603c2ce8e90b471cca311f023ba85e010a0e21913cec9e59e93c0118945a3cda").unwrap(),
|
||||
hex::decode("020000205ca35aa16b669d252de566ff111a2800250bfdfae5e72300000000000000000099a99f1cb9f487794d70dd2dabdb63542aad056ff20cf5e435d1a350a5dd8f7435f09e59e93c01182d21bc70").unwrap(),
|
||||
hex::decode("02000020751510083e009cc429c10ce2a1f7ce1f95c87f4716275c000000000000000000d1c1b3b2a58a4d7780fd7855cb168a3187fbb48766a77e2237e61a987f7a04a0a4f49e59e93c01188c23e65e").unwrap(),
|
||||
hex::decode("02000020ca376c104bded3b6f2ecd502c4626b4e4ba77ede0f2817010000000000000000007ed4eebdd8353dbaf9d2038858dd5d075efbc8d88ecf8390c9bd4629561739b6f49e59e93c0118680992f1").unwrap(),
|
||||
hex::decode("0000002054ddd7cc1845abc2f45fb6fdd477fc060c621cfc3f64a300000000000000000027b24757e89286c84b438fdd7221d5eeec72dc3214f96bfcbb034480b8d17dfcf2f79e59e93c01189e1f97f6").unwrap(),
|
||||
hex::decode("00000020f49dc697e664b8b0c2029098b02ffef095d48c1e477145000000000000000000c644ff082cb1fa4ecce54bed1b73568f1d822871004222d5a72f3cc498e2f44763f99e59e93c0118f9f8e680").unwrap(),
|
||||
hex::decode("000000206549bb716f4d1f739f6ec0eec4e7bd579d13a08f32ec910000000000000000004d9a9c3c118ada3bc0bf92527bcf58b9dd9a2ebfcd32e863ddf55bf4b8ea51acd3fa9e59e93c0118dd521ee6").unwrap(),
|
||||
hex::decode("000000209a368fcdfa9401ade089f9caffa5f4247b8d98df587907000000000000000000b4f37f6ec854a54768e754813ffb7e8a28cbf1e96c83d591b991d3f18e314c2b35fc9e59e93c01180d8068c8").unwrap(),
|
||||
hex::decode("02000020452cab805c77683d7311ca8678c7597791da79fe540a090100000000000000006445c736195151df49c936a6f0d981b6a13cfe2786da44e49972753dc90af2e452fd9e59e93c0118f0572502").unwrap(),
|
||||
hex::decode("020000200d95d828b4772e62a3fda7c5105a5e62ae59bfa41b897c000000000000000000762cef2e97c31814210393e4acb278c155fe040b711635c6e91ee500e53754431f049f59e93c0118969361d6").unwrap(),
|
||||
hex::decode("00000020649b9a19c0145467f3a1a58e1bcc05a65ec1056ef93688000000000000000000dd4ccf726384ebdc3ccc54cb2fb333e1d53929c8ab31d53e67b53cd77d61454894079f59e93c0118bed9774f").unwrap(),
|
||||
hex::decode("020000206f3f9474e424ca652456920f944c7b17b3d240ad37aa3b0000000000000000009a92e0f32aeb11ff2edd48fb838cbb0a3e25c77110c903821f339f7af5b241fb540a9f59e93c0118b5e8548c").unwrap(),
|
||||
hex::decode("000000200aa99abfd7da0f5f5448aa667e6de3e077c883614be8b200000000000000000075761a440b33470907d040f1a155297ca051031a51f8fa86354759b07bd1f664a10a9f59e93c01184b5fa896").unwrap(),
|
||||
hex::decode("020000206bd57fe048b0bfc457f7d5d2391e01726fe292ab1fcb18010000000000000000172bba412df81c10668c127231b0aa6d0bb956e5e8fc0e8d049bdf2ad5ae7fb7ad0a9f59e93c0118503dab85").unwrap(),
|
||||
hex::decode("0000002001930a8eaedf739fcee4ff0b813f3c30e9893adc7c2f750000000000000000008c070fd6e6fb402464b941679aa9d4729039649e6d0291503f707b0e18f15e43050d9f59e93c011853eff7b1").unwrap(),
|
||||
hex::decode("0000002032065359c9d1ec45b0e44777179e815c5dea5df6d2b80d000000000000000000e0df9831a1340b0f43e9c1cb184b3c908088c22b3f7c0e83753899fadd7b19a4160f9f59e93c0118ae79fdbb").unwrap(),
|
||||
hex::decode("020000208790994382f84e67407b33e82f0a8e268d5728cdf51113000000000000000000012a6be8a94ed2b80d931826d1c3e9041b0db02d526bdb01f29cdafe867e8e1952169f59e93c0118baa84dfa").unwrap(),
|
||||
hex::decode("000000206ee4581dd794f9a4fdfe832fe065309357d674b8128834010000000000000000888e623880da62cf81367c8b77e3e2ce52747b2371e82f4abd0f1ba8e41f84fc78189f59e93c011811f10c19").unwrap(),
|
||||
hex::decode("00000020077535a944b5339304140abe45ae8074fc2608647123210100000000000000005ed755f1b7abccc300cfa05d5912b2735fd5af94005c483363750315352d91a0971b9f59e93c01186f9d5e9f").unwrap(),
|
||||
hex::decode("000000202c66b86db7a058c3c44539ddeffb4f1b57015f9b2b8fd5000000000000000000e9b0a033bc22b16c9947bb1cf8666c8b7e31ab98572a12df526c7941eab4325a4f209f59e93c0118d5062214").unwrap(),
|
||||
hex::decode("02000020f22aab764863f3f9e88a769ac022585b6ffe1bbad8ee510000000000000000000ee1ae23c89a4e6a19d737f0bf177d3a208bd00e1d0ad4cae38227e86ddfb36250229f59e93c01182447fb2c").unwrap(),
|
||||
hex::decode("02000020d1570bf5d1ac720a7452201a3287325bc5dae8524739b30000000000000000007a05c7cd9b70624c6a5d60bdd06839aba4fcc8e7252edd243b65c14243074437ce239f59e93c0118f63384cf").unwrap(),
|
||||
hex::decode("02000020aca96f50dac6c74043cc30b2ab63a11af71348913c99070000000000000000006dda0c090803e1207eb4d5151bfc572fec8c5869014dcd528255280fc3c6be0ff02b9f59e93c0118aaeafdd9").unwrap(),
|
||||
hex::decode("0000002062b7c2b4bfdac0e47f8a510e05e56ec3a2aa159a198b210100000000000000005d1c0f705fb6dda30d8787b483ec14391dca7dba3bc927b5c5d2d6ce5aa605f8e42c9f59e93c01182b168198").unwrap(),
|
||||
hex::decode("02000020ed0a2c738b93a2020850d1e6e696c409c7e1f386e36f5d000000000000000000ad0ab3a5dc8fa3680ec23d44ad6ae25c34ff4b8880c50ec5930ac8e2e539a59247319f59e93c0118a250018b").unwrap(),
|
||||
hex::decode("020000209ddcae121db975e6dd5e81c170baf5b3c282ab20394bde00000000000000000080f2facb6a18fafe52015165da8389eeaf7c3ecefb7407bc64746b4848d237a096319f59e93c0118832b1484").unwrap(),
|
||||
hex::decode("120000209eb0ab39e2c28b15ea5af8d29384b0cd75e0fd8ca333800000000000000000009e664c6c013e556a0e533ea58cdb611fd18f004780f2d8fa9e960f531077723ed3369f59e93c01183ed29cd1").unwrap(),
|
||||
hex::decode("0000002004b9908261d98fc854b3a60e7c6550be0e80f1faca2e9200000000000000000051ab7c2206c0af2fd0109857e2813667b20c60b44eba0429d12d2bf445b62dd18b399f59e93c011866f2e5ec").unwrap(),
|
||||
hex::decode("000000208aa7de3780cc6675d5dba2bf9cc9a282affe39a6f7150500000000000000000097f2a23d7efd9dfe84826085d7c60aa3af1838853b39e0de3eb2479070f86ad8b73e9f59e93c01189c2b7650").unwrap(),
|
||||
hex::decode("020000200446629b6bc0dcacc173df9cb47f001784913e61aff4db0000000000000000003118dcc3cbfd4f0d70955243b0f82945303bc0c1065d81dcb804cc960b2e229406409f59e93c011882e333c3").unwrap(),
|
||||
hex::decode("020000209dd0c92839267422e8a4c18abc1d098219bdc9cbcee6c500000000000000000020449bc225a5eb4fa2599bb828ad08315ff6070cdae1fcd54f4c12af5a738e78e4429f59e93c0118a43c58f5").unwrap(),
|
||||
hex::decode("00000020a8ce692a8a4feb0aa11b111173578f62fe62a1f9b7d1250000000000000000008da525d2694931ef00938ad1adf80dc1d98961da742899a3e8e9a9f9d52d139d5d479f59e93c0118299a5e44").unwrap(),
|
||||
hex::decode("020000209d0fd9053369245ce38bb78a5615f28c35e11a5973c5f700000000000000000063b3af6eb819369034653030bde01fdb932c387de6df52de2706247d85e459cc114d9f59e93c01184a0be1da").unwrap(),
|
||||
hex::decode("020000202edbe7bc738f7738539edbe8ef297845d6dd3172929008010000000000000000c168d44f58778e26a32f2cae4ad24438ef9a224247560597f90807150f12ce2c954d9f59e93c011837f3cd54").unwrap(),
|
||||
hex::decode("0000002015fa542454cfb0ae97e23ad4b4ce5703b3108716c01357000000000000000000a806a100d3b35f96c39ca66e22a1beda6f3f6fe713d42dc2e6ffeb28dd8c28e9565f9f59e93c011806b57820").unwrap(),
|
||||
hex::decode("020000205ec54cb5b7b605cfc74c84f8ba30b366967477158e7eaa00000000000000000003bba352d0ff1f1da8d6236d56537f04f010c244241cfada8413d411bb05f2acbf5f9f59e93c0118c1ddb052").unwrap(),
|
||||
hex::decode("0200002099e2bf03160945de2c32994587f206fc03a8729873f71b0100000000000000000a41ec1170bd9ae021715e237a8bceb081bbf7fe54c710bc53d5e847cf0e42f83e659f59e93c0118d5368f54").unwrap(),
|
||||
hex::decode("02000020f7039f439dc89d835d02760938b27f38aa23ca450edfe7000000000000000000d53db2eefe53fe8f7b0bbfda2f6b0fdcdf05b3db319c6d0f8b1c6de724cdfad4bd669f59e93c011886b1e482").unwrap(),
|
||||
hex::decode("02000020849f13fa4db5a9df69b6c7432787aa46ab907a73eb8958000000000000000000cce6f694fb27b616b555dc7102ec152bde972c81e2872a3e563ef42012aff052cb6b9f59e93c0118e0c9a5a7").unwrap(),
|
||||
hex::decode("02000020a2483433891049526ac33b6ea00686a35559e02754612e01000000000000000089f3b6be3169c4a876aca2ffe4356c3d0360318b0944361b50877c4c18684d0a466e9f59e93c0118d708e05a").unwrap(),
|
||||
hex::decode("0200002005b1a1dde1c62d56a012b4f03d8b728a2a94f673e32654000000000000000000d02cfa1f6e7718bcc995a9ae1058dd104b46f5f1602fa04342976a33b2d9de4fed6e9f59e93c0118565c6ad1").unwrap(),
|
||||
hex::decode("0200002058a8c2c6e79a29d7cae4638b45250afcba2c78931a1a4e000000000000000000047a409d56ecb504f03f625f02debe5e4f8e4afb2e7db585da0e6267e5a9c1241b749f59e93c01183cc51dda").unwrap(),
|
||||
hex::decode("020000208ea76979f75c77af7770c510ba0b29d10929a78a67a1ab0000000000000000000025f4241f566b426d0482decd6e50433e599e876a5eb19e077198490341c633d1749f59e93c0118eea47b4e").unwrap(),
|
||||
hex::decode("0200002017cbe57c7bdd58d8dd4a330e75134f505d23337545a0c3000000000000000000a0c7805564032c4837f6aadf9ca1f7c0c05e43a15b5c85b24113edf5463a3c2b63759f59e93c01180d829750").unwrap(),
|
||||
hex::decode("000000201daf8e737418b092385d91052dc9bf25c78db4947eb55b00000000000000000060a707c9f3152e692ded4eac22e1b94b5ad2ec5054146f252656824385e648a8b8759f59e93c01182341df8c").unwrap(),
|
||||
hex::decode("0000002096c2dcf962dbfa7ad045b4b8b8b7a4886cad2379ebaea6000000000000000000af2bd41304d6282d189dd7dfa2a9b4e4f9d6dbec56fbec8ca89a79c04f8120a3b1779f59e93c01181ab38dd1").unwrap(),
|
||||
hex::decode("0200002030656d4c0fd65650a9816435d319a28b65b7565ef68e1100000000000000000026b47d3d67ea2ac89e74a31b9db03d4d3055efa98bb8368e12699a10406e49e4437b9f59e93c01185a356dc0").unwrap(),
|
||||
hex::decode("02000020d595c94c828b7f0d7833781a9c7c7c698966f251ee3420000000000000000000b182443ec96ca031c8e2fbb6492a1a51ae6cc80e886122d29d01b69269e91ca03a7c9f59e93c0118bfd8da6e").unwrap(),
|
||||
hex::decode("0200002018ea7a48e4f65e887ae7f5f1fc1395d25c5e454432995c0000000000000000008533a46a3c5b3e2ade0cf423d12d400b62586f8b8bea62e6a075d6b8c976ff6e8f7e9f59e93c01180f245714").unwrap(),
|
||||
hex::decode("00000020bfdc2e0d03ac8d66bb2a18da3b9d03574e538c5e837a1d010000000000000000ac88acc66f208e84153c725753bdae77fc6d19ef0a640b6d1caf12cf8b851aeabf7f9f59e93c011805fc019a").unwrap(),
|
||||
hex::decode("00000020831e7f6ff11c94025c12c7ee0246309c60243cfa79c6340100000000000000002a4d15a07780dd0685e4f517ee3fe822c19c1f88c97fbef7d4cc9125156244de95809f59e93c0118032ce04b").unwrap(),
|
||||
hex::decode("0000002010fa7fe6e27ce9ad5f51bf00d5078a9e577f2d08115c1f00000000000000000062d112144604b9f9b83e4f421b234fdb17d0d7bd71590b2a1994ac73f0d9fb2759879f59e93c011870e590f2").unwrap(),
|
||||
hex::decode("0000002065fee6168588d22fbe99ac61f5767f7d1f38408d4a15390000000000000000002ae612abb95ed62f051af05b0e942fd1763ef6f3011662c71d4f4d9c984c6da1dd899f59e93c01181231b844").unwrap(),
|
||||
hex::decode("0200002024bf07643212552bd0d4fa4779762994eda338868ba20b010000000000000000700b14840728bb630ee467a705dacba32ef040e39245d4e18e0381a1fb42e5b2e2899f59e93c0118687dc2a8").unwrap(),
|
||||
hex::decode("02000020fd21bdf774f4ab391ee951404c7c3d9df1f20a21fbd6be000000000000000000e948d27a0e531f5cbab3a5c5cb3f33cddf8228215ca3d934336f26cdecc8a587aa979f59e93c011835ccc946").unwrap(),
|
||||
hex::decode("02000020153d617dd664fa4f49aa0ef145227331b4aef23a9beed0000000000000000000283f8529ed8419bbd45076bf1ef58a2f232c4cd869aa5e9a8687a932ea6b8cab2a9d9f59e93c011884f44a28").unwrap(),
|
||||
hex::decode("0200002094a5ac9376865366f4494ff6782e9e0558eb485671451101000000000000000031dbb81b18060f9afa4a82a55391911102a01122b6e25bbad2227d036aa402cf69a19f59e93c0118a38dcf53").unwrap(),
|
||||
hex::decode("1200002020311f7c3ea7fabfb3da39c120d7256421b8ba1f7c9d2700000000000000000043b494d744397958f9fcbdbc78e2fa238a65ce4a8c99c8cae41094bf5f620274dcab9f59e93c01187257ce43").unwrap(),
|
||||
hex::decode("02000020be1808de4c0bc3a8cca96651411de3bcb04b03ca9cd5ad00000000000000000061248b66ec93f45b75d1fe61bc62994b769ac8fc6a5140c2e1fcd8ed3ad8734bf9ab9f59e93c0118c46a224d").unwrap(),
|
||||
hex::decode("020000206de1e86d50d4043ea7b06394598eb0d39316de61ebdb1d010000000000000000cbad54eff729d2e964a3b97e754a01da55cdbc8924788f415ad0e5e04f030bfb6baf9f59e93c0118bc7d5352").unwrap(),
|
||||
hex::decode("12000020971fb5337db4cfcdabe3d0a19414e22f200d0dcfa60ae9000000000000000000f1e07bba0d854c76d60dfbdab247d68ebfc80ba5e95d4d83997178653fdbdcc5d1b09f59e93c01180b0f8fab").unwrap(),
|
||||
hex::decode("00000020b915dca0172c00dff21d436900f81fa55c4d0c94789708000000000000000000c0d80eedeab0a2e5ac800e4e0800b9a67a51d8095ff013977c56a5ee6a82564b91b19f59e93c0118626d6930").unwrap(),
|
||||
hex::decode("0200002038b29a8148601f802389e59448bd915254374cd89e3e07000000000000000000000f9be6ad775720838d044faabaa507c1d27374710fe36b66e7e855f275603c11b59f59e93c01186830dc10").unwrap(),
|
||||
hex::decode("00000020f66bbe99a28e70f00ed13fae994a12950025129ee29dbb0000000000000000006e2e590253d6d4de2cb44f95292681e8a8dfe425ba3a231aefb0e239241d338747b99f59e93c01186621bcd3").unwrap(),
|
||||
hex::decode("020000200b449c16f78cc9f3f56e7eb84c8c535b29230866ccc410010000000000000000b201b9bc39851f34c3d68a80453de339560947fa76f0d3d28aaa0fe6f3c09f0870bc9f59e93c0118a6f6bf4b").unwrap(),
|
||||
hex::decode("020000209073c42915b4ec2f93cb625299befbbe115867f9ffd6a400000000000000000065e326a16c150c95b145deff23e31b829ec224e16d62f426037fa0a9f515d96c11bf9f59e93c01189c3db0ca").unwrap(),
|
||||
hex::decode("0200002000a52727e49fa397f8f211204c5a0bc40497b9b142c311000000000000000000ac88a88861b6c454b4dfc4bb8e4cd2fb475cacbb003c80bb4dcc44d89e324ef4ddbf9f59e93c01184ca16dab").unwrap(),
|
||||
hex::decode("00000020643522aca18b6dd72993667995c1a0ed0db40a8ccfb014010000000000000000536ef1c8b3b657824e7dcf7650cb37bf8916773d0961ea46039880e81150dce130c19f59e93c01189304ead8").unwrap(),
|
||||
hex::decode("000000209f39c386609cf5fcde82901e09e3c21614d6c91bf1bf500000000000000000006a2196cfa5da0d2bfcddbafb8ef33c3535e2c6abfbbf0b9be5668a7d382d182b95c79f59e93c0118d7c51033").unwrap(),
|
||||
hex::decode("020000204171fce850d6b9972bc84b5c129097700fcc940801daed000000000000000000c802782b023172b77fd0d67868e2c6fff4691b9081385c0b1fa8ee9b0edb9488afc89f59e93c0118dc47db2e").unwrap(),
|
||||
hex::decode("000000208229fd4875d341096fe972b42ec09c37ba5b3f5761307a000000000000000000f6d6b2238c3510a57ea4c85efa9ce8131a2e0e18b5db6ccb3ba62cfdb360d79a7ecd9f59e93c011878faaac7").unwrap(),
|
||||
hex::decode("0200002016209868f23cee45d08d78e0a0a407cd2f44163e44cb3901000000000000000093d2238bcdc26dc75023b9c5118928bad163e39c2f71df8e95d6edd751ba79e648cf9f59e93c01188440a013").unwrap(),
|
||||
hex::decode("000000209207740837717a41d191055edaca417441ba8a518a008d000000000000000000ca7617ee77e441b43320206c73b0ab694e1a6e5cae68de543906bc78884ec96f54d19f59e93c0118ea3cdbf7").unwrap(),
|
||||
hex::decode("0200002095de633721183adca50ee64eb432278067f271ed5c6d110000000000000000008d58a1f0310aaa879af6d1567042c56316d0944a3745a48477eee8c0a9b160e0ded59f59e93c011840274171").unwrap(),
|
||||
hex::decode("00000020eac6f48951848edb142e7d2cb65e5325e168eff217a9b100000000000000000018a5f3609c392597179460b0eb4f552150ea919674c07110541b3b937d61895bbcd69f59e93c0118990775cc").unwrap(),
|
||||
hex::decode("00000020cd852383fdc3580f03d70427842597998ff05d642c301700000000000000000016d1f41abbeb84cf1260a156bb107f1831e1d4043087a62b91a802b6504ae82100de9f59e93c0118a2caa3e0").unwrap(),
|
||||
hex::decode("02000020ba3564570e67b5e9258a0741a9b0a8c7eb21fc531e47340000000000000000008d51759065e5b563aa22534da2fe013cb78081b109a430f5224f2e3de395c31423de9f59e93c0118f387b5b2").unwrap(),
|
||||
hex::decode("000000205dd7d70d5b3b8654915a80a502636d762de4349b7ea35e0000000000000000005fad44d833d5963038adccc421a1f027e3ecdd321d94ce2981958a92d543a9823fde9f59e93c011809f75b68").unwrap(),
|
||||
hex::decode("02000020f6a223280df450a99052b9644fdbbb17c873cc650079c2000000000000000000ce4ecb69781542236a03b3f1ac1bcf33ff0c5392ea1e10810092c71607ed9b5cadec9f59e93c011831acafd8").unwrap(),
|
||||
hex::decode("00000020e572b677fdbc2c58ccfd064be23380d1a48880e2f57630010000000000000000de94574c2ba3c13464037089196614b4c7d3cb4d5af1fd8c42b7dc48099ddb29ecef9f59e93c0118718ceb94").unwrap(),
|
||||
hex::decode("020000202b8b12967db07d4ebb88aa85ef66116c191a93e99b27120100000000000000003fd47c683e1e17d680a9110af97f0ca4c645760d1a2b9934dce5f2e88be5093c7af29f59e93c01185714a38b").unwrap(),
|
||||
hex::decode("000000207880fe8eb9ce3c0868742ae98e71b18e23bc94f62ab02a010000000000000000817fb1fa49e3aff325c1b0e445629e06d31699b565daebf741b1b731d1e3db4a8cfc9f59e93c0118fc165574").unwrap(),
|
||||
hex::decode("00000020eae605b0e27d01a4a967f8b2788222bdf7d06c3f2cffb70000000000000000004647da086d8d604990e4e95a832430372fa388532599f5f2dc2c4248d6c62019c1fc9f59e93c0118346c62f4").unwrap(),
|
||||
hex::decode("00000020fb5de90e254a00c4f2e88b478d927e97f8cc7bcde99de7000000000000000000d31910146254c1034ee847eeb991d7a758be6b475bcfa2039ed8b8746160c6365bff9f59e93c01184568f141").unwrap(),
|
||||
hex::decode("0000002001d8d41aac3bc8be7ec45a469db006bfd484e174c86d1400000000000000000080707a5e00bf7ee43650b1428d3fb7aa3ae10edce68fc1c44c0d50042251eee8c102a059e93c0118c28a0a25").unwrap(),
|
||||
hex::decode("00000020f4d340137354701e5acbaed498ebd833cb0bf6d4d4c0a400000000000000000058972fc8cad5065ec473d657070631c769714d5245f2a1c60819c0d58f2110d67306a059e93c01188ad1f723").unwrap(),
|
||||
hex::decode("000000201a0c546ed7a8560f402810f7db7e9ed31bb9ee33cca548000000000000000000fbc835eee64458ccf98e4e9b2f464b74c4a8f19ca62d88e91d370a5140ca849f8609a059e93c0118f85d071c").unwrap(),
|
||||
hex::decode("000000202ed3ce68191cf5f2a141e1beda144282aa3cb91c86bb3a0100000000000000006baff826721270cbb61b6e87a200878944de3bc288b89c1ad9f4660a90b0de592d0da059e93c01180913db03").unwrap(),
|
||||
hex::decode("0000002037fefbaa4a417acddcff01b67e375aa274a5159b00cd250000000000000000000ad0731c0295fa7ad74b78ab37dda1760d98feb225c38932ebed3b3ab86010219d15a059e93c01180029355d").unwrap(),
|
||||
hex::decode("020000208d3517e8c5ac9576de6376dbe18bbc3f68b714bd90ff06000000000000000000f09393c9f8cdb4fa5e45a556d7aa3ee3abc08ff175fab17c321144c1540da3aa9316a059e93c0118394eb993").unwrap(),
|
||||
hex::decode("0000002093b6ad64d95790be03f40782e42dd217d91a062514b482000000000000000000b4ce3863a62be400d10c4b06f31f8d805c52df6bbcc9d672fbf1ed110394f015f916a059e93c01188f006b8b").unwrap(),
|
||||
hex::decode("000000206af6d3062235cfbc8dbac1f5f33b790b48eb025e945677000000000000000000e0b4b06f31170c9cc54e623247a18d3dcb5b37a97799abadf5be22cac18ab650a818a059e93c01182a2eadc0").unwrap(),
|
||||
hex::decode("00000020e44d7e3e4e7ed258fd9dabb368bd20f42a5134ac63a9eb00000000000000000047da7bf618553524026b5f6054372b9fc26e3f561345290b4b2775f65214d76a7d22a059e93c011889243aa1").unwrap(),
|
||||
hex::decode("000000200428d486033251c65fc8921aeda5d627b4974b2ed8710a01000000000000000026049ae797b8658ddff66af57c00fff50d83d123ecf2d70c5f7b6f40537aabbe9526a059e93c0118568368f3").unwrap(),
|
||||
hex::decode("000000204025364ce1f01cdfaf82e8c16665cb47a1b8156ce2c5be000000000000000000c4a56c66a75c261587536605fd4442dcfc190a4ddf5a13d6a063110e140bc5177e2ea059e93c0118fe232164").unwrap(),
|
||||
hex::decode("0000002072dd4401dd1ee65956efb1ef3e3c468611d2c4cbb9dfae000000000000000000f6d476f9b69b756b87fb6596d16409bc5d40803489c5d52ffbbb03d12d594a174b30a059e93c0118ad048658").unwrap(),
|
||||
hex::decode("020000200f2e93d616b03ffa6d2377bb6c8a5eb31385fdb30f5ac7000000000000000000f6187a2f8e73a661cc90ed86546b62f45ab68a174e56d08d2d93ae12eda9b181ed32a059e93c01184d5d246d").unwrap(),
|
||||
hex::decode("020000207f3d929b96f2121056e0cad6a3363316d648a56a3baa2d0000000000000000001ba48a3c40fd114080ca67441bbf6c1b787b7caf4e3f19a124627493f0e3334e6234a059e93c0118b34d5e41").unwrap(),
|
||||
hex::decode("0000002091c6841e0adf8d4bea707e3ab1e412b45bbfc80068b37a000000000000000000c273a936450ebd6f0ec9f16b04a955ccadd42fa446fb4ba0ac8cbd2874af988a2838a059e93c011889d44f30").unwrap(),
|
||||
hex::decode("000000208a4af4109c84e53ca1ca671dc1ba75a55a2886f8eafd55000000000000000000e5b46a9cb2b8a975ab7c742fa57d942f5e52d0b92eff25954e916718f0eaaee1343aa059e93c0118e9904b69").unwrap(),
|
||||
hex::decode("020000202d6cf055320a0e2781b77d1c0525e2897cd01cb03c9118010000000000000000fc8093d44e1279783a4d478a53bddcb55b58c4d06d51ea4bd8aeda28b66b2e7b423aa059e93c0118bc883a1e").unwrap(),
|
||||
hex::decode("020000208d5c1f0e78fc6b87fba844848dd41a37bedebe409ebb0f0100000000000000008b34b89a7e9b732d940cb77f642cf9c9eabff198f369aa94220ebcdf07d3e138ab3aa059e93c0118e0078029").unwrap(),
|
||||
hex::decode("02000020c639651d0a5acf604c4feac92e0879c16154ca9cad8c940000000000000000001802b2fb1bf4dd20a96a68aa416cd86325201b813ac9b3081d025e06a8632a2cfc3ca059e93c011864300336").unwrap(),
|
||||
hex::decode("000000202ed62872636d3ae7803d9515f3a4360c0f096b08d9f8380100000000000000008476fe527a397800280d359dad7720ef220f2d749665fa8f3a5767a85d81a39f273fa059e93c0118ef87a752").unwrap(),
|
||||
hex::decode("000000206bf7dfa1d97a1bfca63d76e41644ddbf34e1df525ff657000000000000000000cb05597fae3e31ae3a0dc3696bad38552e47b3d87987111e2ee602a8112f9495463fa059e93c0118d8578079").unwrap(),
|
||||
hex::decode("0000002085dfc2bc2e4753ea4fa2079c69acc42a6d9da90b26530c00000000000000000069a1ec768f8b395c3efd9c04a7f11273b10589e1bfd4fa145fe26dc9cc603655d048a059e93c0118122e33dd").unwrap(),
|
||||
];
|
||||
|
||||
let ancestry_headers_vec: Vec<Vec<u8>> = historical_hashes
|
||||
.iter()
|
||||
.take(max_history_depth)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let gettxproof_bytes = hex::decode("00000020375a14cfcf5730daa8ff8d7a5bd2cf59fba797e289c2e0000000000000000000529cd017146361b57e6b7353b16d8d38d32f92ee842039dd779d35bfbfa2f3e53f649e59e93c01187900ea9fb40600000cfbd7b718845d123b1ae1a7fd3bce9c97acba097dc912c4ac67a920395db07ba4eef685960869ba70e0ca38890565c97dc11f1d7ae538e7b509069f81e38eff1a2e4a6b66fc4233d7d0580af4d195c7f0b274849f19d405dfc6b930bb9edd3f4a4c1f1e9fa3a336c50e8ea16039e4db4bef7ff5946e3a019937bdf626c2336d0e3926a83a575218f84e6dc55ffb9d0997a41e4b4d5425a85f22160c9f52362a95021a573ff96bb429bd80a8ec9dcb899971553efe91b6a0fe57c4694c3e8da8dc7f1babd514f2746da1ed06f08664baed853c334e0a3917d0c859b19ee14f448f77e624822070db3b606d5092027ca9ecf7e0243e6c151081021ebb3ddcdd28c1b8f9fdcffebd1962590477675f3d29d52602a1e7e941f772a3bbff42cf2eb6e74a1124bf424f767e294ae6253a21602c7c6b0381743dd6dcd57b025983e0e34375b24f67ac8beb00713d2f70594bbf797d5d94c061cb9965e6458f2f017f983d7ce37361fa13fda372f595dc1f22895ab93339457c72c480fed1c4d1164a242303ff3600").expect("Bytes32 from UTXO should be valid");
|
||||
let getrawtransaction_bytes = hex::decode("010000000299a43fae581f80baf44e021ddf7b11995f184db282d6e48c22248b9fe79c43bb010000008b483045022100e9db01784dcd879c65d66127c2d52a4f9244321d15796893d6dccadaa1146e3d02203d9bb80f8df8b66692d3d9517f7cbb898d7cc40a30b175432322e1956c9b973a0141048097e4f8bb8ee104d5c2ece11d42f7e4719df8ddba459ecadecaba87d233c81a3c14830371392dd8ed030ef970450c0b7af48d9db4ba20c046896481602eb9dbffffffffb3fd05bc6000a5a0384e9a613d452e9307568df0a964907068e00403a0a0e09e020000006a47304402205bb1cbad4b29421648c1e22cac32a56aa06c933fe655559823fffb71ffebbc0b022021648d970560aa5b2031560b52a0fd06305995a58a457663324c18c1a9b0bd0b8121020d6e50b2660af27933c42bc1395fe93df90ffac5e2a989f6a134919fb8cf8075ffffffff030000000000000000296a2769642b5bb666ac1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e7c150000000000001976a914c1c69d91a5719a23ba2094d89a4df3eadfa4aef788acefea2309000000001976a9144635b1794a22bfbe6c5c5eba17b693f4aaf0e34888ac00000000").expect("Bytes32 from UTXO should be valid");
|
||||
|
||||
let last_header = ancestry_headers_vec.last().unwrap();
|
||||
let first_sha = sp_io::hashing::sha2_256(&last_header[..80]);
|
||||
let mut native_le = sp_io::hashing::sha2_256(&first_sha);
|
||||
native_le.reverse();
|
||||
let dynamic_trusted_root = H256::from(native_le);
|
||||
|
||||
LoomStates::<T>::insert(&network_id, &session, dynamic_trusted_root);
|
||||
|
||||
let utxo_thread_proof = UtxoThreadProof::new(
|
||||
gettxproof_bytes,
|
||||
getrawtransaction_bytes,
|
||||
ancestry_headers_vec,
|
||||
);
|
||||
|
||||
let thread_proof = ThreadProof::UtxoThreadProof(utxo_thread_proof);
|
||||
let pulled_thread_key = thread_proof.get_unique_key(session);
|
||||
|
||||
assert!(!PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
#[extrinsic_call]
|
||||
pull_thread(RawOrigin::None, network_id, session, thread_proof);
|
||||
|
||||
assert!(PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn pull_evm_thread() -> Result<(), BenchmarkError> {
|
||||
let session = 69;
|
||||
let (network_id, _, _) = prepare_pallet::<T>(1);
|
||||
|
||||
// https://sepolia.etherscan.io/address/0x81acdf2da6b50d806c96aecd9a498a5359e373c0#readContract
|
||||
// getRoot: session 0, atBlock 11660400
|
||||
|
||||
let root_string =
|
||||
hex::decode("28d1e6b476b03a464f74d531d532572db29d6b57349e4f17f84e92843f6c4ee7")
|
||||
.expect("EVM Root hex should be valid");
|
||||
let expected_root = H256::from_slice(&root_string);
|
||||
LoomStates::<T>::insert(&network_id, &session, expected_root);
|
||||
|
||||
let receiver_32: [u8; 32] =
|
||||
hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e")
|
||||
.expect("Bytes32 from EVM should be valid")
|
||||
.try_into()
|
||||
.expect("Hex string must be exactly 32 bytes long");
|
||||
|
||||
let receiver_account = sp_runtime::AccountId32::from(receiver_32);
|
||||
let transfer_amount: BalanceOf<T> = 420u64.unique_saturated_into();
|
||||
|
||||
let proof = vec![
|
||||
H256::from_slice(&hex::decode("c5354289f0397c916949373043eb1d5ea99447f67fe014324d96c58ef02a2d11").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("da1fbdfefa1e9aedfedcfc48fb17cb0e731529f7a3695114e0a9caa1989dd420").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("1c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35f").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("d364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd071").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("68d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849f").expect("Proof should be valid")),
|
||||
H256::from_slice(&hex::decode("de9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040").expect("Proof should be valid")),
|
||||
];
|
||||
let slots = vec![H256::from_slice(
|
||||
&hex::decode("2e474dabf492786b3505d7b7cf0bf07c9dbce92c91982fa7344c77e24fd4310d")
|
||||
.expect("Slot should be valid hex"),
|
||||
)];
|
||||
|
||||
let evm_thread_proof = EvmThreadProof::new(0, 1, receiver_account, transfer_amount, proof, slots);
|
||||
|
||||
let thread_proof = ThreadProof::EvmThreadProof(evm_thread_proof);
|
||||
let pulled_thread_key = thread_proof.get_unique_key(session);
|
||||
|
||||
assert!(!PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
#[extrinsic_call]
|
||||
pull_thread(RawOrigin::None, network_id, session, thread_proof);
|
||||
|
||||
assert!(PulledThreads::<T>::contains_key(&pulled_thread_key));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(vec![], vec![]),
|
||||
crate::mock::TestRuntime
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user