From 21760a8f6ff8c5ef45d7d804085fa35c9f03cb27 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 11 Sep 2024 13:51:49 +0200 Subject: [PATCH 01/51] Move active fps calca to finality mod --- contracts/btc-staking/src/contract.rs | 6 ++-- contracts/btc-staking/src/finality.rs | 47 +++++++++++++++++++++++++-- contracts/btc-staking/src/staking.rs | 46 ++------------------------ 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 5e31e1e3..005ac226 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -11,9 +11,11 @@ use babylon_apis::btc_staking_api::SudoMsg; use babylon_bindings::BabylonMsg; use crate::error::ContractError; -use crate::finality::{handle_finality_signature, handle_public_randomness_commit}; +use crate::finality::{ + compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit, +}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::staking::{compute_active_finality_providers, handle_btc_staking}; +use crate::staking::handle_btc_staking; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; use crate::state::staking::ACTIVATED_HEIGHT; use crate::{finality, queries, state}; diff --git a/contracts/btc-staking/src/finality.rs b/contracts/btc-staking/src/finality.rs index c401118f..0d8ce476 100644 --- a/contracts/btc-staking/src/finality.rs +++ b/contracts/btc-staking/src/finality.rs @@ -5,7 +5,9 @@ use std::cmp::max; use std::collections::HashSet; use cosmwasm_std::Order::Ascending; -use cosmwasm_std::{to_json_binary, DepsMut, Env, Event, Response, StdResult, Storage, WasmMsg}; +use cosmwasm_std::{ + to_json_binary, DepsMut, Env, Event, Order, Response, StdResult, Storage, WasmMsg, +}; use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; use babylon_bindings::BabylonMsg; @@ -19,7 +21,7 @@ use crate::state::finality::{BLOCKS, EVIDENCES, NEXT_HEIGHT, SIGNATURES}; use crate::state::public_randomness::{ get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES, }; -use crate::state::staking::{fps, FPS, FP_SET}; +use crate::state::staking::{fps, FPS, FP_SET, TOTAL_POWER}; pub fn handle_public_randomness_commit( deps: DepsMut, @@ -502,6 +504,47 @@ fn finalize_block( Ok(ev) } +/// `compute_active_finality_providers` sorts all finality providers, counts the total voting +/// power of top finality providers, and records them in the contract state +pub fn compute_active_finality_providers( + storage: &mut dyn Storage, + env: Env, + max_active_fps: usize, +) -> Result<(), ContractError> { + // Sort finality providers by power + let (finality_providers, running_total): (_, Vec<_>) = fps() + .idx + .power + .range(storage, None, None, Order::Descending) + .take(max_active_fps) + .scan(0u64, |acc, item| { + let (pk_hex, fp_state) = item.ok()?; // Error ends the iteration + + let fp_info = FinalityProviderInfo { + btc_pk_hex: pk_hex, + power: fp_state.power, + }; + *acc += fp_state.power; + Some((fp_info, *acc)) + }) + .filter(|(fp, _)| { + // Filter out FPs with no voting power + fp.power > 0 + }) + .unzip(); + + // TODO: Online FPs verification + // TODO: Filter out slashed / offline / jailed FPs + // Save the new set of active finality providers + // TODO: Purge old (height - finality depth) FP_SET entries to avoid bloating the storage + FP_SET.save(storage, env.block.height, &finality_providers)?; + // Save the total voting power of the top n finality providers + let total_power = running_total.last().copied().unwrap_or_default(); + TOTAL_POWER.save(storage, &total_power)?; + + Ok(()) +} + #[cfg(test)] pub(crate) mod tests { use babylon_apis::btc_staking_api::SudoMsg; diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index 6bf0aa8b..2143a116 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -2,17 +2,16 @@ use bitcoin::absolute::LockTime; use bitcoin::consensus::deserialize; use bitcoin::hashes::Hash; use bitcoin::{Transaction, Txid}; -use cosmwasm_std::{DepsMut, Env, Event, MessageInfo, Order, Response, Storage}; +use cosmwasm_std::{DepsMut, Env, Event, MessageInfo, Response, Storage}; use hex::ToHex; use std::str::FromStr; use crate::error::ContractError; -use crate::msg::FinalityProviderInfo; use crate::state::config::{ADMIN, CONFIG, PARAMS}; use crate::state::staking::{ fps, BtcDelegation, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, DELEGATION_FPS, FPS, - FP_DELEGATIONS, FP_SET, TOTAL_POWER, + FP_DELEGATIONS, }; use crate::validation::{ verify_active_delegation, verify_new_fp, verify_slashed_delegation, verify_undelegation, @@ -336,47 +335,6 @@ fn btc_undelegate( Ok(()) } -/// `compute_active_finality_providers` sorts all finality providers, counts the total voting -/// power of top finality providers, and records them in the contract state -pub fn compute_active_finality_providers( - storage: &mut dyn Storage, - env: Env, - max_active_fps: usize, -) -> Result<(), ContractError> { - // Sort finality providers by power - let (finality_providers, running_total): (_, Vec<_>) = fps() - .idx - .power - .range(storage, None, None, Order::Descending) - .take(max_active_fps) - .scan(0u64, |acc, item| { - let (pk_hex, fp_state) = item.ok()?; // Error ends the iteration - - let fp_info = FinalityProviderInfo { - btc_pk_hex: pk_hex, - power: fp_state.power, - }; - *acc += fp_state.power; - Some((fp_info, *acc)) - }) - .filter(|(fp, _)| { - // Filter out FPs with no voting power - fp.power > 0 - }) - .unzip(); - - // TODO: Online FPs verification - // TODO: Filter out slashed / offline / jailed FPs - // Save the new set of active finality providers - // TODO: Purge old (height - finality depth) FP_SET entries to avoid bloating the storage - FP_SET.save(storage, env.block.height, &finality_providers)?; - // Save the total voting power of the top n finality providers - let total_power = running_total.last().copied().unwrap_or_default(); - TOTAL_POWER.save(storage, &total_power)?; - - Ok(()) -} - /// `slash_finality_provider` slashes a finality provider with the given PK. /// A slashed finality provider will not have voting power pub(crate) fn slash_finality_provider( From 78929327dcec259bb00a8a07d69961f810316f94 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 11 Sep 2024 13:57:50 +0200 Subject: [PATCH 02/51] Fix: Non-zero fp voting power check --- contracts/btc-staking/src/finality.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/contracts/btc-staking/src/finality.rs b/contracts/btc-staking/src/finality.rs index 0d8ce476..49fbcd21 100644 --- a/contracts/btc-staking/src/finality.rs +++ b/contracts/btc-staking/src/finality.rs @@ -156,9 +156,17 @@ pub fn handle_finality_signature( } // Ensure the finality provider has voting power at this height - fps() + if fps() .may_load_at_height(deps.storage, fp_btc_pk_hex, height)? - .ok_or_else(|| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))?; + .ok_or_else(|| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))? + .power + == 0 + { + return Err(ContractError::NoVotingPower( + fp_btc_pk_hex.to_string(), + height, + )); + } // Ensure the signature is not empty if signature.is_empty() { From ab5e5eafbfbd0cdb57b4cb624522565e11c4f258 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 12 Sep 2024 10:03:50 +0200 Subject: [PATCH 03/51] Move FP_SET to finality state where it belongs --- contracts/btc-staking/src/finality.rs | 4 ++-- contracts/btc-staking/src/state/finality.rs | 12 +++++++++++- contracts/btc-staking/src/state/staking.rs | 7 ------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/contracts/btc-staking/src/finality.rs b/contracts/btc-staking/src/finality.rs index 49fbcd21..1bceeceb 100644 --- a/contracts/btc-staking/src/finality.rs +++ b/contracts/btc-staking/src/finality.rs @@ -17,11 +17,11 @@ use crate::error::ContractError; use crate::msg::FinalityProviderInfo; use crate::staking; use crate::state::config::{CONFIG, PARAMS}; -use crate::state::finality::{BLOCKS, EVIDENCES, NEXT_HEIGHT, SIGNATURES}; +use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER}; use crate::state::public_randomness::{ get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES, }; -use crate::state::staking::{fps, FPS, FP_SET, TOTAL_POWER}; +use crate::state::staking::{fps, FPS}; pub fn handle_public_randomness_commit( deps: DepsMut, diff --git a/contracts/btc-staking/src/state/finality.rs b/contracts/btc-staking/src/state/finality.rs index 742d2a5d..bb698edb 100644 --- a/contracts/btc-staking/src/state/finality.rs +++ b/contracts/btc-staking/src/state/finality.rs @@ -1,6 +1,9 @@ -use babylon_apis::finality_api::{Evidence, IndexedBlock}; use cw_storage_plus::{Item, Map}; +use babylon_apis::finality_api::{Evidence, IndexedBlock}; + +use crate::msg::FinalityProviderInfo; + /// Map of signatures by block height and FP pub(crate) const SIGNATURES: Map<(u64, &str), Vec> = Map::new("fp_sigs"); @@ -10,5 +13,12 @@ pub(crate) const BLOCKS: Map = Map::new("blocks"); /// Next height to finalise pub(crate) const NEXT_HEIGHT: Item = Item::new("next_height"); +/// `FP_SET` is the calculated list of the active finality providers by height +pub const FP_SET: Map> = Map::new("fp_set"); + +/// `TOTAL_POWER` is the total power of all finality providers +// FIXME: Store by height? Remove? Not currently being used in the contract +pub const TOTAL_POWER: Item = Item::new("total_power"); + /// Map of double signing evidence by FP and block height pub(crate) const EVIDENCES: Map<(&str, u64), Evidence> = Map::new("evidences"); diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index fb842e89..6696af7c 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -1,7 +1,6 @@ use cosmwasm_schema::cw_serde; use cw_storage_plus::{IndexedSnapshotMap, Item, Map, MultiIndex, Strategy}; -use crate::msg::FinalityProviderInfo; use crate::state::fp_index::FinalityProviderIndexes; use babylon_apis::btc_staking_api::{BTCDelegationStatus, FinalityProvider, HASH_SIZE}; use babylon_apis::{btc_staking_api, Bytes}; @@ -216,12 +215,6 @@ pub const FP_POWER_KEY: &str = "fp_state__power"; /// The height at which the contract gets its first delegation pub const ACTIVATED_HEIGHT: Item = Item::new("activated_height"); -/// `FP_SET` is the calculated list of the active finality providers by height -pub const FP_SET: Map> = Map::new("fp_set"); -/// `TOTAL_POWER` is the total power of all finality providers -// FIXME: Store by height? Remove? Not currently being used in the contract -pub const TOTAL_POWER: Item = Item::new("total_power"); - /// Indexed snapshot map for finality providers. /// /// This allows querying the map finality providers, sorted by their (aggregated) power. From 12f826d34111322b22c0b4fbfa4787f3bacb2ce0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 12 Sep 2024 10:35:58 +0200 Subject: [PATCH 04/51] Split staking and finality execution handlers --- contracts/babylon-finality/Cargo.toml | 58 +++++++++++++++++++++++++++ packages/apis/src/btc_staking_api.rs | 36 ----------------- packages/apis/src/finality_api.rs | 42 +++++++++++++++++++ 3 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 contracts/babylon-finality/Cargo.toml diff --git a/contracts/babylon-finality/Cargo.toml b/contracts/babylon-finality/Cargo.toml new file mode 100644 index 00000000..235c6c7b --- /dev/null +++ b/contracts/babylon-finality/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "babylon-finality" +edition.workspace = true +version.workspace = true +license.workspace = true +repository.workspace = true +authors = ["Babylon Labs Ltd. "] +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[[bin]] +name = "finality-schema" +path = "src/bin/schema.rs" +test = false + +[features] +# Add feature "cranelift" to default if you need 32 bit or ARM support +default = [] +# Use cranelift backend instead of singlepass. This is required for development on 32 bit or ARM machines. +cranelift = ["cosmwasm-vm/cranelift"] +# for quicker tests, cargo test --lib +library = [] +# feature for enabling the full validation +full-validation = [] + +[dependencies] +babylon-apis = { path = "../../packages/apis" } +babylon-bindings = { path = "../../packages/bindings" } +babylon-contract = { path = "../babylon", features = [ "library" ] } +babylon-merkle = { path = "../../packages/merkle" } +babylon-proto = { path = "../../packages/proto" } +babylon-btcstaking = { path = "../../packages/btcstaking" } +babylon-bitcoin = { path = "../../packages/bitcoin" } +eots = { path = "../../packages/eots" } + +bitcoin = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +derivative = { workspace = true } +hex = { workspace = true } +k256 = { workspace = true } +prost = { workspace = true } +thiserror = { workspace = true } +cw-controllers = { workspace = true } + +[dev-dependencies] +test-utils = { path = "../../packages/test-utils" } +babylon-proto = { path = "../../packages/proto" } +cosmwasm-vm = { workspace = true } +prost = { workspace = true } diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index 78d4a663..d2af7699 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -4,8 +4,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Binary, Decimal}; -use babylon_merkle::Proof; - /// Hash size in bytes pub const HASH_SIZE: usize = 32; @@ -21,40 +19,6 @@ pub enum ExecuteMsg { slashed_del: Vec, unbonded_del: Vec, }, - /// Committing a sequence of public randomness for EOTS - // TODO: Move to its own module / contract - CommitPublicRandomness { - /// `fp_pubkey_hex` is the BTC PK of the finality provider that commits the public randomness - fp_pubkey_hex: String, - /// `start_height` is the start block height of the list of public randomness - start_height: u64, - /// `num_pub_rand` is the amount of public randomness committed - num_pub_rand: u64, - /// `commitment` is the commitment of these public randomness values. - /// Currently, it's the root of the Merkle tree that includes the public randomness - commitment: Binary, - /// `signature` is the signature on (start_height || num_pub_rand || commitment) signed by - /// the SK corresponding to `fp_pubkey_hex`. - /// This prevents others committing public randomness on behalf of `fp_pubkey_hex` - signature: Binary, - }, - /// Submit Finality Signature. - /// - /// This is a message that can be called by a finality provider to submit their finality - /// signature to the Consumer chain. - /// The signature is verified by the Consumer chain using the finality provider's public key - /// - /// This message is equivalent to the `MsgAddFinalitySig` message in the Babylon finality protobuf - /// defs. - // TODO: Move to its own module / contract - SubmitFinalitySignature { - fp_pubkey_hex: String, - height: u64, - pub_rand: Binary, - proof: Proof, - block_hash: Binary, - signature: Binary, - }, } #[cw_serde] diff --git a/packages/apis/src/finality_api.rs b/packages/apis/src/finality_api.rs index f60fbfa3..a0d6b569 100644 --- a/packages/apis/src/finality_api.rs +++ b/packages/apis/src/finality_api.rs @@ -2,9 +2,51 @@ /// The definitions here roughly follow the same structure as the equivalent IBC protobuf pub struct types, /// defined in `packages/proto/src/gen/babylon.finality.v1.rs` use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; + +use babylon_merkle::Proof; use crate::Bytes; +#[cw_serde] +/// babylon_finality execution handlers +pub enum ExecuteMsg { + /// Change the admin + UpdateAdmin { admin: Option }, + /// Committing a sequence of public randomness for EOTS + CommitPublicRandomness { + /// `fp_pubkey_hex` is the BTC PK of the finality provider that commits the public randomness + fp_pubkey_hex: String, + /// `start_height` is the start block height of the list of public randomness + start_height: u64, + /// `num_pub_rand` is the amount of public randomness committed + num_pub_rand: u64, + /// `commitment` is the commitment of these public randomness values. + /// Currently, it's the root of the Merkle tree that includes the public randomness + commitment: Binary, + /// `signature` is the signature on (start_height || num_pub_rand || commitment) signed by + /// the SK corresponding to `fp_pubkey_hex`. + /// This prevents others committing public randomness on behalf of `fp_pubkey_hex` + signature: Binary, + }, + /// Submit Finality Signature. + /// + /// This is a message that can be called by a finality provider to submit their finality + /// signature to the Consumer chain. + /// The signature is verified by the Consumer chain using the finality provider's public key + /// + /// This message is equivalent to the `MsgAddFinalitySig` message in the Babylon finality protobuf + /// defs. + SubmitFinalitySignature { + fp_pubkey_hex: String, + height: u64, + pub_rand: Binary, + proof: Proof, + block_hash: Binary, + signature: Binary, + }, +} + /// `IndexedBlock` is the necessary metadata and finalization status of a block #[cw_serde] pub struct IndexedBlock { From 5ee798701c8776c1086f6e22067b9d952ebe6490 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 12 Sep 2024 12:15:26 +0200 Subject: [PATCH 05/51] Move finality stuff out of btc-staking and into babylon-finality --- Cargo.lock | 28 ++++++ .../src/finality.rs | 0 .../src/state/finality.rs | 0 .../src/state/public_randomness.rs | 0 contracts/btc-staking/src/contract.rs | 97 ++----------------- contracts/btc-staking/src/lib.rs | 1 - contracts/btc-staking/src/msg.rs | 58 +---------- contracts/btc-staking/src/queries.rs | 77 +++------------ contracts/btc-staking/src/state/mod.rs | 2 - 9 files changed, 48 insertions(+), 215 deletions(-) rename contracts/{btc-staking => babylon-finality}/src/finality.rs (100%) rename contracts/{btc-staking => babylon-finality}/src/state/finality.rs (100%) rename contracts/{btc-staking => babylon-finality}/src/state/public_randomness.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b384c651..4dbce48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,34 @@ dependencies = [ "thousands", ] +[[package]] +name = "babylon-finality" +version = "0.9.0" +dependencies = [ + "babylon-apis", + "babylon-bindings", + "babylon-bitcoin", + "babylon-btcstaking", + "babylon-contract", + "babylon-merkle", + "babylon-proto", + "bitcoin", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-vm", + "cw-controllers", + "cw-storage-plus", + "cw-utils", + "cw2", + "derivative", + "eots", + "hex", + "k256", + "prost 0.11.9", + "test-utils", + "thiserror", +] + [[package]] name = "babylon-merkle" version = "0.9.0" diff --git a/contracts/btc-staking/src/finality.rs b/contracts/babylon-finality/src/finality.rs similarity index 100% rename from contracts/btc-staking/src/finality.rs rename to contracts/babylon-finality/src/finality.rs diff --git a/contracts/btc-staking/src/state/finality.rs b/contracts/babylon-finality/src/state/finality.rs similarity index 100% rename from contracts/btc-staking/src/state/finality.rs rename to contracts/babylon-finality/src/state/finality.rs diff --git a/contracts/btc-staking/src/state/public_randomness.rs b/contracts/babylon-finality/src/state/public_randomness.rs similarity index 100% rename from contracts/btc-staking/src/state/public_randomness.rs rename to contracts/babylon-finality/src/state/public_randomness.rs diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 005ac226..ce46784a 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -11,10 +11,8 @@ use babylon_apis::btc_staking_api::SudoMsg; use babylon_bindings::BabylonMsg; use crate::error::ContractError; -use crate::finality::{ - compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit, -}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::queries; use crate::staking::handle_btc_staking; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; use crate::state::staking::ACTIVATED_HEIGHT; @@ -91,46 +89,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Ok(to_json_binary( &queries::finality_providers_by_power(deps, start_after, limit)?, )?), - QueryMsg::FinalitySignature { btc_pk_hex, height } => Ok(to_json_binary( - &queries::finality_signature(deps, btc_pk_hex, height)?, - )?), - QueryMsg::PubRandCommit { - btc_pk_hex, - start_after, - limit, - reverse, - } => Ok(to_json_binary( - &state::public_randomness::get_pub_rand_commit( - deps.storage, - &btc_pk_hex, - start_after, - limit, - reverse, - )?, - )?), - QueryMsg::FirstPubRandCommit { btc_pk_hex } => Ok(to_json_binary( - &state::public_randomness::get_first_pub_rand_commit(deps.storage, &btc_pk_hex)?, - )?), - QueryMsg::LastPubRandCommit { btc_pk_hex } => Ok(to_json_binary( - &state::public_randomness::get_last_pub_rand_commit(deps.storage, &btc_pk_hex)?, - )?), QueryMsg::ActivatedHeight {} => Ok(to_json_binary(&queries::activated_height(deps)?)?), - QueryMsg::Block { height } => Ok(to_json_binary(&queries::block(deps, height)?)?), - QueryMsg::Blocks { - start_after, - limit, - finalised, - reverse, - } => Ok(to_json_binary(&queries::blocks( - deps, - start_after, - limit, - finalised, - reverse, - )?)?), - QueryMsg::Evidence { btc_pk_hex, height } => Ok(to_json_binary(&queries::evidence( - deps, btc_pk_hex, height, - )?)?), } } @@ -166,37 +125,6 @@ pub fn execute( &slashed_del, &unbonded_del, ), - ExecuteMsg::SubmitFinalitySignature { - fp_pubkey_hex, - height, - pub_rand, - proof, - block_hash, - signature, - } => handle_finality_signature( - deps, - env, - &fp_pubkey_hex, - height, - &pub_rand, - &proof, - &block_hash, - &signature, - ), - ExecuteMsg::CommitPublicRandomness { - fp_pubkey_hex, - start_height, - num_pub_rand, - commitment, - signature, - } => handle_public_randomness_commit( - deps, - &fp_pubkey_hex, - start_height, - num_pub_rand, - &commitment, - &signature, - ), } } @@ -224,23 +152,12 @@ fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result Result, ContractError> { - // If the BTC staking protocol is activated i.e. there exists a height where at least one - // finality provider has voting power, start indexing and tallying blocks - let mut res = Response::new(); - if let Some(activated_height) = ACTIVATED_HEIGHT.may_load(deps.storage)? { - // Index the current block - let ev = finality::index_block(deps, env.block.height, &hex::decode(app_hash_hex)?)?; - res = res.add_event(ev); - // Tally all non-finalised blocks - let events = finality::tally_blocks(deps, activated_height, env.block.height)?; - res = res.add_events(events); - } - Ok(res) + Ok(Response::new()) } #[cfg(test)] @@ -272,8 +189,8 @@ pub(crate) mod tests { get_fp_sk_bytes, get_pub_rand_commit, }; - pub(crate) const CREATOR: &str = "creator"; - pub(crate) const INIT_ADMIN: &str = "initial_admin"; + const CREATOR: &str = "creator"; + const INIT_ADMIN: &str = "initial_admin"; const NEW_ADMIN: &str = "new_admin"; fn new_params(params: ProtoParams) -> Params { diff --git a/contracts/btc-staking/src/lib.rs b/contracts/btc-staking/src/lib.rs index fb28b697..ecb8d13a 100644 --- a/contracts/btc-staking/src/lib.rs +++ b/contracts/btc-staking/src/lib.rs @@ -1,4 +1,3 @@ -mod finality; mod staking; mod validation; diff --git a/contracts/btc-staking/src/msg.rs b/contracts/btc-staking/src/msg.rs index 781d123f..6ab8a241 100644 --- a/contracts/btc-staking/src/msg.rs +++ b/contracts/btc-staking/src/msg.rs @@ -2,7 +2,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cw_controllers::AdminResponse; use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; -use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; +use babylon_apis::finality_api::{Evidence, IndexedBlock}; use crate::state::config::{Config, Params}; use crate::state::staking::BtcDelegation; @@ -82,66 +82,10 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, - /// `FinalitySignature` returns the signature of the finality provider for a given block height - /// - #[returns(FinalitySignatureResponse)] - FinalitySignature { btc_pk_hex: String, height: u64 }, - /// `PubRandCommit` returns the public random commitments for a given FP. - /// - /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. - /// - /// `start_after` is the height of to start after (before, if `reverse` is `true`), - /// or `None` to start from the beginning (end, if `reverse` is `true`). - /// `limit` is the maximum number of commitments to return. - /// `reverse` is an optional flag to return the commitments in reverse order - #[returns(PubRandCommit)] - PubRandCommit { - btc_pk_hex: String, - start_after: Option, - limit: Option, - reverse: Option, - }, - /// `FirstPubRandCommit` returns the first public random commitment (if any) for a given FP. - /// - /// It's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to - /// false. - /// - /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. - #[returns(Option)] - FirstPubRandCommit { btc_pk_hex: String }, - /// `LastPubRandCommit` returns the last public random commitment (if any) for a given FP. - /// - /// It's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to - /// true. - /// - /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. - #[returns(Option)] - LastPubRandCommit { btc_pk_hex: String }, /// `ActivatedHeight` returns the height at which the contract gets its first delegation, if any /// #[returns(ActivatedHeightResponse)] ActivatedHeight {}, - /// `Block` returns the indexed block information at height - /// - #[returns(IndexedBlock)] - Block { height: u64 }, - /// `Blocks` return the list of indexed blocks. - /// - /// `start_after` is the height of the block to start after (before, if `reverse` is `true`), - /// or `None` to start from the beginning (end, if `reverse` is `true`). - /// `limit` is the maximum number of blocks to return. - /// `finalised` is an optional filter to return only finalised blocks. - /// `reverse` is an optional flag to return the blocks in reverse order - #[returns(BlocksResponse)] - Blocks { - start_after: Option, - limit: Option, - finalised: Option, - reverse: Option, - }, - /// `Evidence` returns the evidence for a given FP and block height - #[returns(EvidenceResponse)] - Evidence { btc_pk_hex: String, height: u64 }, } #[cw_serde] diff --git a/contracts/btc-staking/src/queries.rs b/contracts/btc-staking/src/queries.rs index ad03bc42..1160ee6f 100644 --- a/contracts/btc-staking/src/queries.rs +++ b/contracts/btc-staking/src/queries.rs @@ -3,22 +3,19 @@ use std::str::FromStr; use bitcoin::hashes::Hash; use bitcoin::Txid; -use cosmwasm_std::Order::{Ascending, Descending}; +use cosmwasm_std::Order::Descending; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use babylon_apis::btc_staking_api::FinalityProvider; -use babylon_apis::finality_api::IndexedBlock; use crate::error::ContractError; use crate::msg::{ - ActivatedHeightResponse, BlocksResponse, BtcDelegationsResponse, DelegationsByFPResponse, - EvidenceResponse, FinalityProviderInfo, FinalityProvidersByPowerResponse, - FinalityProvidersResponse, FinalitySignatureResponse, + ActivatedHeightResponse, BtcDelegationsResponse, DelegationsByFPResponse, FinalityProviderInfo, + FinalityProvidersByPowerResponse, FinalityProvidersResponse, }; use crate::state::config::{Config, Params}; use crate::state::config::{CONFIG, PARAMS}; -use crate::state::finality::{BLOCKS, EVIDENCES}; use crate::state::staking::{ fps, BtcDelegation, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, FPS, FP_DELEGATIONS, }; @@ -173,19 +170,6 @@ pub fn finality_providers_by_power( Ok(FinalityProvidersByPowerResponse { fps }) } -pub fn finality_signature( - deps: Deps, - btc_pk_hex: String, - height: u64, -) -> StdResult { - match crate::state::finality::SIGNATURES.may_load(deps.storage, (height, &btc_pk_hex))? { - Some(sig) => Ok(FinalitySignatureResponse { signature: sig }), - None => Ok(FinalitySignatureResponse { - signature: Vec::new(), - }), // Empty signature response - } -} - pub fn activated_height(deps: Deps) -> Result { let activated_height = ACTIVATED_HEIGHT.may_load(deps.storage)?.unwrap_or_default(); Ok(ActivatedHeightResponse { @@ -193,56 +177,13 @@ pub fn activated_height(deps: Deps) -> Result StdResult { - BLOCKS.load(deps.storage, height) -} - -/// Get list of blocks. -/// `start_after`: The height to start after, if any. -/// `finalised`: List only finalised blocks if true, otherwise list all blocks. -/// `reverse`: List in descending order if present and true, otherwise in ascending order. -pub fn blocks( - deps: Deps, - start_after: Option, - limit: Option, - finalised: Option, - reverse: Option, -) -> Result { - let finalised = finalised.unwrap_or_default(); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_after = start_after.map(Bound::exclusive); - let (start, end, order) = if reverse.unwrap_or(false) { - (None, start_after, Descending) - } else { - (start_after, None, Ascending) - }; - let blocks = BLOCKS - .range_raw(deps.storage, start, end, order) - .filter(|item| { - if let Ok((_, block)) = item { - !finalised || block.finalized - } else { - true // don't filter errors - } - }) - .take(limit) - .map(|item| item.map(|(_, v)| v)) - .collect::, _>>()?; - Ok(BlocksResponse { blocks }) -} - -pub fn evidence(deps: Deps, btc_pk_hex: String, height: u64) -> StdResult { - let evidence = EVIDENCES.may_load(deps.storage, (&btc_pk_hex, height))?; - Ok(EvidenceResponse { evidence }) -} - #[cfg(test)] mod tests { use cosmwasm_std::storage_keys::namespace_with_key; use cosmwasm_std::testing::message_info; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::StdError::NotFound; - use cosmwasm_std::{from_json, Storage}; + use cosmwasm_std::{from_json, Binary, Env, Storage}; use babylon_apis::btc_staking_api::{FinalityProvider, UnbondedBtcDelegation}; @@ -251,7 +192,6 @@ mod tests { }; use crate::contract::{execute, instantiate}; use crate::error::ContractError; - use crate::finality::tests::mock_env_height; use crate::msg::{ExecuteMsg, FinalityProviderInfo, InstantiateMsg}; use crate::staking::tests::staking_tx_hash; use crate::state::config::PARAMS; @@ -259,6 +199,13 @@ mod tests { const CREATOR: &str = "creator"; + fn mock_env_height(height: u64) -> Env { + let mut env = mock_env(); + env.block.height = height; + + env + } + // Sort delegations by staking tx hash fn sort_delegations(dels: &[BtcDelegation]) -> Vec { let mut dels = dels.to_vec(); @@ -649,7 +596,7 @@ mod tests { let mut deps = mock_dependencies(); let info = message_info(&deps.api.addr_make(CREATOR), &[]); - let initial_env = crate::finality::tests::mock_env_height(10); + let initial_env = mock_env_height(10); instantiate( deps.as_mut(), diff --git a/contracts/btc-staking/src/state/mod.rs b/contracts/btc-staking/src/state/mod.rs index 9a0de080..018e8070 100644 --- a/contracts/btc-staking/src/state/mod.rs +++ b/contracts/btc-staking/src/state/mod.rs @@ -1,6 +1,4 @@ pub mod config; -pub mod finality; -pub mod public_randomness; pub mod staking; mod fp_index; From c56df7733256f572692bf353c62448226649fbe5 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 13 Sep 2024 10:19:52 +0200 Subject: [PATCH 06/51] Add babylon-finality contract (WIP) --- Cargo.lock | 1 + contracts/babylon-finality/Cargo.toml | 1 + contracts/babylon-finality/src/bin/schema.rs | 24 + contracts/babylon-finality/src/contract.rs | 528 +++++++++++ contracts/babylon-finality/src/error.rs | 97 ++ contracts/babylon-finality/src/finality.rs | 89 +- contracts/babylon-finality/src/lib.rs | 7 + contracts/babylon-finality/src/msg.rs | 101 ++ contracts/babylon-finality/src/queries.rs | 888 ++++++++++++++++++ .../babylon-finality/src/state/config.rs | 61 ++ .../babylon-finality/src/state/finality.rs | 10 +- contracts/babylon-finality/src/state/mod.rs | 3 + .../src/state/public_randomness.rs | 5 +- 13 files changed, 1769 insertions(+), 46 deletions(-) create mode 100644 contracts/babylon-finality/src/bin/schema.rs create mode 100644 contracts/babylon-finality/src/contract.rs create mode 100644 contracts/babylon-finality/src/error.rs create mode 100644 contracts/babylon-finality/src/lib.rs create mode 100644 contracts/babylon-finality/src/msg.rs create mode 100644 contracts/babylon-finality/src/queries.rs create mode 100644 contracts/babylon-finality/src/state/config.rs create mode 100644 contracts/babylon-finality/src/state/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4dbce48d..3108eebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,7 @@ dependencies = [ "babylon-merkle", "babylon-proto", "bitcoin", + "btc-staking", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vm", diff --git a/contracts/babylon-finality/Cargo.toml b/contracts/babylon-finality/Cargo.toml index 235c6c7b..cee30c62 100644 --- a/contracts/babylon-finality/Cargo.toml +++ b/contracts/babylon-finality/Cargo.toml @@ -36,6 +36,7 @@ babylon-merkle = { path = "../../packages/merkle" } babylon-proto = { path = "../../packages/proto" } babylon-btcstaking = { path = "../../packages/btcstaking" } babylon-bitcoin = { path = "../../packages/bitcoin" } +btc-staking = { path = "../btc-staking", features = [ "library" ] } eots = { path = "../../packages/eots" } bitcoin = { workspace = true } diff --git a/contracts/babylon-finality/src/bin/schema.rs b/contracts/babylon-finality/src/bin/schema.rs new file mode 100644 index 00000000..b975175a --- /dev/null +++ b/contracts/babylon-finality/src/bin/schema.rs @@ -0,0 +1,24 @@ +use cosmwasm_schema::write_api; +use cosmwasm_std::Empty; + +use btc_staking::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + // Clear & write standard API + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + migrate: Empty, + execute: ExecuteMsg, + } + + // Schemas for inter-contract communication + // let mut out_dir = current_dir().unwrap(); + // out_dir.push("schema"); + // export_schema(&schema_for!(PacketMsg), &out_dir); // TODO: find a way to export schema for IBC packet + // export_schema_with_title( + // &schema_for!(AcknowledgementMsg), + // &out_dir, + // "AcknowledgementMsgBtcTimestamp", + // ); +} diff --git a/contracts/babylon-finality/src/contract.rs b/contracts/babylon-finality/src/contract.rs new file mode 100644 index 00000000..a4dd570d --- /dev/null +++ b/contracts/babylon-finality/src/contract.rs @@ -0,0 +1,528 @@ +use babylon_contract::msg::btc_header::BtcHeaderResponse; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, + StdResult, +}; +use cw2::set_contract_version; +use cw_utils::{maybe_addr, nonpayable}; + +use babylon_apis::btc_staking_api::SudoMsg; +use babylon_bindings::BabylonMsg; + +use crate::error::ContractError; +use crate::finality::{ + compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit, +}; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; +use crate::{finality, queries, state}; +use babylon_contract::msg::contract::QueryMsg as BabylonQueryMsg; + +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + mut deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result, ContractError> { + nonpayable(&info)?; + let denom = deps.querier.query_bonded_denom()?; + let config = Config { + denom, + babylon: info.sender, + }; + CONFIG.save(deps.storage, &config)?; + + let api = deps.api; + ADMIN.set(deps.branch(), maybe_addr(api, msg.admin.clone())?)?; + + let params = msg.params.unwrap_or_default(); + PARAMS.save(deps.storage, ¶ms)?; + // initialize storage, so no issue when reading for the first time + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::new().add_attribute("action", "instantiate")) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(_deps: DepsMut, _env: Env, _reply: Reply) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { + match msg { + QueryMsg::Config {} => Ok(to_json_binary(&queries::config(deps)?)?), + QueryMsg::Params {} => Ok(to_json_binary(&queries::params(deps)?)?), + QueryMsg::Admin {} => to_json_binary(&ADMIN.query_admin(deps)?).map_err(Into::into), + QueryMsg::FinalityProvider { btc_pk_hex } => Ok(to_json_binary( + &queries::finality_provider(deps, btc_pk_hex)?, + )?), + QueryMsg::FinalityProviders { start_after, limit } => Ok(to_json_binary( + &queries::finality_providers(deps, start_after, limit)?, + )?), + QueryMsg::Delegation { + staking_tx_hash_hex, + } => Ok(to_json_binary(&queries::delegation( + deps, + staking_tx_hash_hex, + )?)?), + QueryMsg::Delegations { + start_after, + limit, + active, + } => Ok(to_json_binary(&queries::delegations( + deps, + start_after, + limit, + active, + )?)?), + QueryMsg::DelegationsByFP { btc_pk_hex } => Ok(to_json_binary( + &queries::delegations_by_fp(deps, btc_pk_hex)?, + )?), + QueryMsg::FinalityProviderInfo { btc_pk_hex, height } => Ok(to_json_binary( + &queries::finality_provider_info(deps, btc_pk_hex, height)?, + )?), + QueryMsg::FinalityProvidersByPower { start_after, limit } => Ok(to_json_binary( + &queries::finality_providers_by_power(deps, start_after, limit)?, + )?), + QueryMsg::FinalitySignature { btc_pk_hex, height } => Ok(to_json_binary( + &queries::finality_signature(deps, btc_pk_hex, height)?, + )?), + QueryMsg::PubRandCommit { + btc_pk_hex, + start_after, + limit, + reverse, + } => Ok(to_json_binary( + &state::public_randomness::get_pub_rand_commit( + deps.storage, + &btc_pk_hex, + start_after, + limit, + reverse, + )?, + )?), + QueryMsg::FirstPubRandCommit { btc_pk_hex } => Ok(to_json_binary( + &state::public_randomness::get_first_pub_rand_commit(deps.storage, &btc_pk_hex)?, + )?), + QueryMsg::LastPubRandCommit { btc_pk_hex } => Ok(to_json_binary( + &state::public_randomness::get_last_pub_rand_commit(deps.storage, &btc_pk_hex)?, + )?), + QueryMsg::ActivatedHeight {} => Ok(to_json_binary(&queries::activated_height(deps)?)?), + QueryMsg::Block { height } => Ok(to_json_binary(&queries::block(deps, height)?)?), + QueryMsg::Blocks { + start_after, + limit, + finalised, + reverse, + } => Ok(to_json_binary(&queries::blocks( + deps, + start_after, + limit, + finalised, + reverse, + )?)?), + QueryMsg::Evidence { btc_pk_hex, height } => Ok(to_json_binary(&queries::evidence( + deps, btc_pk_hex, height, + )?)?), + } +} + +/// This is a no-op just to test how this integrates with wasmd +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result, ContractError> { + let api = deps.api; + match msg { + ExecuteMsg::UpdateAdmin { admin } => ADMIN + .execute_update_admin(deps, info, maybe_addr(api, admin)?) + .map_err(Into::into), + ExecuteMsg::BtcStaking { + new_fp, + active_del, + slashed_del, + unbonded_del, + } => handle_btc_staking( + deps, + env, + &info, + &new_fp, + &active_del, + &slashed_del, + &unbonded_del, + ), + ExecuteMsg::SubmitFinalitySignature { + fp_pubkey_hex, + height, + pub_rand, + proof, + block_hash, + signature, + } => handle_finality_signature( + deps, + env, + &fp_pubkey_hex, + height, + &pub_rand, + &proof, + &block_hash, + &signature, + ), + ExecuteMsg::CommitPublicRandomness { + fp_pubkey_hex, + start_height, + num_pub_rand, + commitment, + signature, + } => handle_public_randomness_commit( + deps, + &fp_pubkey_hex, + start_height, + num_pub_rand, + &commitment, + &signature, + ), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo( + mut deps: DepsMut, + env: Env, + msg: SudoMsg, +) -> Result, ContractError> { + match msg { + SudoMsg::BeginBlock { .. } => handle_begin_block(&mut deps, env), + SudoMsg::EndBlock { + hash_hex, + app_hash_hex, + } => handle_end_block(&mut deps, env, &hash_hex, &app_hash_hex), + } +} + +fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result, ContractError> { + // Index BTC height at the current height + index_btc_height(deps, env.block.height)?; + + // Compute active finality provider set + let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize; + compute_active_finality_providers(deps.storage, env, max_active_fps)?; + + Ok(Response::new()) +} + +// index_btc_height indexes the current BTC height, and saves it to the state +fn index_btc_height(deps: &mut DepsMut, height: u64) -> Result<(), ContractError> { + // FIXME: Turn this into a hard error. Requires `babylon-contract` instance, and up and running + // BTC light client loop (which requires a running BTC node / simulator) + let btc_tip_height = get_btc_tip_height(deps) //?; + .ok() + .unwrap_or_default(); + + Ok(BTC_HEIGHT.save(deps.storage, height, &btc_tip_height)?) +} + +/// get_btc_tip_height queries the Babylon contract for the latest BTC tip height +fn get_btc_tip_height(deps: &DepsMut) -> Result { + // Get the BTC tip from the babylon contract through a raw query + let babylon_addr = CONFIG.load(deps.storage)?.babylon; + + // Query the Babylon contract + // TODO: use a raw query for performance / efficiency + let query_msg = BabylonQueryMsg::BtcTipHeader {}; + let tip: BtcHeaderResponse = deps.querier.query_wasm_smart(babylon_addr, &query_msg)?; + + Ok(tip.height) +} + +fn handle_end_block( + deps: &mut DepsMut, + env: Env, + _hash_hex: &str, + app_hash_hex: &str, +) -> Result, ContractError> { + // If the BTC staking protocol is activated i.e. there exists a height where at least one + // finality provider has voting power, start indexing and tallying blocks + let mut res = Response::new(); + if let Some(activated_height) = ACTIVATED_HEIGHT.may_load(deps.storage)? { + // Index the current block + let ev = finality::index_block(deps, env.block.height, &hex::decode(app_hash_hex)?)?; + res = res.add_event(ev); + // Tally all non-finalised blocks + let events = finality::tally_blocks(deps, activated_height, env.block.height)?; + res = res.add_events(events); + } + Ok(res) +} + +#[cfg(test)] +pub(crate) mod tests { + use std::str::FromStr; + + use super::*; + + use crate::state::config::Params; + use babylon_apis::btc_staking_api::{ + ActiveBtcDelegation, BtcUndelegationInfo, CovenantAdaptorSignatures, + FinalityProviderDescription, NewFinalityProvider, ProofOfPossessionBtc, + }; + use babylon_apis::finality_api::PubRandCommit; + use babylon_bitcoin::chain_params::Network; + use babylon_proto::babylon::btcstaking::v1::{ + BtcDelegation, FinalityProvider, Params as ProtoParams, + }; + use cosmwasm_std::{ + from_json, + testing::{message_info, mock_dependencies, mock_env}, + Binary, Decimal, + }; + use cw_controllers::AdminResponse; + use hex::ToHex; + use test_utils::{get_btc_delegation, get_finality_provider, get_pub_rand_commit}; + + pub(crate) const CREATOR: &str = "creator"; + pub(crate) const INIT_ADMIN: &str = "initial_admin"; + const NEW_ADMIN: &str = "new_admin"; + + fn new_params(params: ProtoParams) -> Params { + Params { + covenant_pks: params.covenant_pks.iter().map(hex::encode).collect(), + covenant_quorum: params.covenant_quorum, + btc_network: Network::Regtest, // TODO: fix this + max_active_finality_providers: params.max_active_finality_providers, + min_pub_rand: 10, // TODO: fix this + slashing_address: params.slashing_address, + min_slashing_tx_fee_sat: params.min_slashing_tx_fee_sat as u64, + slashing_rate: "0.01".to_string(), // TODO: fix this + } + } + + pub(crate) fn get_params() -> Params { + let proto_params = test_utils::get_params(); + new_params(proto_params) + } + + fn new_finality_provider(fp: FinalityProvider) -> NewFinalityProvider { + NewFinalityProvider { + addr: fp.addr, + description: fp.description.map(|desc| FinalityProviderDescription { + moniker: desc.moniker, + identity: desc.identity, + website: desc.website, + security_contact: desc.security_contact, + details: desc.details, + }), + commission: Decimal::from_str(&fp.commission).unwrap(), + btc_pk_hex: fp.btc_pk.encode_hex(), + pop: match fp.pop { + Some(pop) => Some(ProofOfPossessionBtc { + btc_sig_type: pop.btc_sig_type, + btc_sig: Binary::new(pop.btc_sig.to_vec()), + }), + None => None, + }, + consumer_id: fp.consumer_id, + } + } + + fn new_active_btc_delegation(del: BtcDelegation) -> ActiveBtcDelegation { + let btc_undelegation = del.btc_undelegation.unwrap(); + + ActiveBtcDelegation { + staker_addr: del.staker_addr, + btc_pk_hex: del.btc_pk.encode_hex(), + fp_btc_pk_list: del + .fp_btc_pk_list + .iter() + .map(|fp_btc_pk| fp_btc_pk.encode_hex()) + .collect(), + start_height: del.start_height, + end_height: del.end_height, + total_sat: del.total_sat, + staking_tx: Binary::new(del.staking_tx.to_vec()), + slashing_tx: Binary::new(del.slashing_tx.to_vec()), + delegator_slashing_sig: Binary::new(del.delegator_sig.to_vec()), + covenant_sigs: del + .covenant_sigs + .iter() + .map(|cov_sig| CovenantAdaptorSignatures { + cov_pk: Binary::new(cov_sig.cov_pk.to_vec()), + adaptor_sigs: cov_sig + .adaptor_sigs + .iter() + .map(|adaptor_sig| Binary::new(adaptor_sig.to_vec())) + .collect(), + }) + .collect(), + staking_output_idx: del.staking_output_idx, + unbonding_time: del.unbonding_time, + undelegation_info: BtcUndelegationInfo { + unbonding_tx: Binary::new(btc_undelegation.unbonding_tx.to_vec()), + slashing_tx: Binary::new(btc_undelegation.slashing_tx.to_vec()), + delegator_unbonding_sig: Binary::new( + btc_undelegation.delegator_unbonding_sig.to_vec(), + ), + delegator_slashing_sig: Binary::new( + btc_undelegation.delegator_slashing_sig.to_vec(), + ), + covenant_unbonding_sig_list: vec![], + covenant_slashing_sigs: vec![], + }, + params_version: del.params_version, + } + } + + /// Build an active BTC delegation from a BTC delegation + pub(crate) fn get_active_btc_delegation() -> ActiveBtcDelegation { + let del = get_btc_delegation(1, vec![1]); + new_active_btc_delegation(del) + } + + // Build a derived active BTC delegation from the base (from testdata) BTC delegation + pub(crate) fn get_derived_btc_delegation(del_id: i32, fp_ids: &[i32]) -> ActiveBtcDelegation { + let del = get_btc_delegation(del_id, fp_ids.to_vec()); + new_active_btc_delegation(del) + } + + /// Get public randomness public key, commitment, and signature information + /// + /// Signature is a Schnorr signature over the commitment + pub(crate) fn get_public_randomness_commitment() -> (String, PubRandCommit, Vec) { + let pub_rand_commitment_msg = get_pub_rand_commit(); + ( + pub_rand_commitment_msg.fp_btc_pk.encode_hex(), + PubRandCommit { + start_height: pub_rand_commitment_msg.start_height, + num_pub_rand: pub_rand_commitment_msg.num_pub_rand, + commitment: pub_rand_commitment_msg.commitment.to_vec(), + }, + pub_rand_commitment_msg.sig.to_vec(), + ) + } + + pub(crate) fn create_new_finality_provider(id: i32) -> NewFinalityProvider { + let fp = get_finality_provider(id); + new_finality_provider(fp) + } + + #[test] + fn instantiate_without_admin() { + let mut deps = mock_dependencies(); + + // Create an InstantiateMsg with admin set to None + let msg = InstantiateMsg { + params: None, + admin: None, // No admin provided + }; + + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + // Call the instantiate function + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // Assert that no messages were sent + assert_eq!(0, res.messages.len()); + + // Query the admin to verify it was not set + let res = ADMIN.query_admin(deps.as_ref()).unwrap(); + assert_eq!(None, res.admin); + } + + #[test] + fn instantiate_with_admin() { + let mut deps = mock_dependencies(); + let init_admin = deps.api.addr_make(INIT_ADMIN); + + // Create an InstantiateMsg with admin set to Some(INIT_ADMIN.into()) + let msg = InstantiateMsg { + params: None, + admin: Some(init_admin.to_string()), // Admin provided + }; + + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + // Call the instantiate function + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // Assert that no messages were sent + assert_eq!(0, res.messages.len()); + + // Use assert_admin to verify that the admin was set correctly + // This uses the assert_admin helper function provided by the Admin crate + ADMIN.assert_admin(deps.as_ref(), &init_admin).unwrap(); + + // ensure the admin is queryable as well + let res = query(deps.as_ref(), mock_env(), QueryMsg::Admin {}).unwrap(); + let admin: AdminResponse = from_json(res).unwrap(); + assert_eq!(admin.admin.unwrap(), init_admin.as_str()) + } + + #[test] + fn test_update_admin() { + let mut deps = mock_dependencies(); + let init_admin = deps.api.addr_make(INIT_ADMIN); + let new_admin = deps.api.addr_make(NEW_ADMIN); + + // Create an InstantiateMsg with admin set to Some(INIT_ADMIN.into()) + let instantiate_msg = InstantiateMsg { + params: None, + admin: Some(init_admin.to_string()), // Admin provided + }; + + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + // Call the instantiate function + let res = instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap(); + + // Assert that no messages were sent + assert_eq!(0, res.messages.len()); + + // Use assert_admin to verify that the admin was set correctly + ADMIN.assert_admin(deps.as_ref(), &init_admin).unwrap(); + + // Update the admin to new_admin + let update_admin_msg = ExecuteMsg::UpdateAdmin { + admin: Some(new_admin.to_string()), + }; + + // Execute the UpdateAdmin message with non-admin info + let non_admin_info = message_info(&deps.api.addr_make("non_admin"), &[]); + let err = execute( + deps.as_mut(), + mock_env(), + non_admin_info, + update_admin_msg.clone(), + ) + .unwrap_err(); + assert_eq!( + err, + ContractError::Admin(cw_controllers::AdminError::NotAdmin {}) + ); + + // Execute the UpdateAdmin message with the initial admin info + let admin_info = message_info(&init_admin, &[]); + let res = execute(deps.as_mut(), mock_env(), admin_info, update_admin_msg).unwrap(); + + // Assert that no messages were sent + assert_eq!(0, res.messages.len()); + + // Use assert_admin to verify that the admin was updated correctly + ADMIN.assert_admin(deps.as_ref(), &new_admin).unwrap(); + } +} diff --git a/contracts/babylon-finality/src/error.rs b/contracts/babylon-finality/src/error.rs new file mode 100644 index 00000000..b96481aa --- /dev/null +++ b/contracts/babylon-finality/src/error.rs @@ -0,0 +1,97 @@ +use hex::FromHexError; +use prost::DecodeError; +use thiserror::Error; + +use bitcoin::hashes::FromSliceError; +use bitcoin::hex::HexToArrayError; + +use cosmwasm_std::StdError; +use cw_controllers::AdminError; +use cw_utils::PaymentError; + +use babylon_apis::error::StakingApiError; +use babylon_merkle::error::MerkleError; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Admin(#[from] AdminError), + #[error("{0}")] + Std(#[from] StdError), + #[error("{0}")] + Payment(#[from] PaymentError), + #[error("{0}")] + BTCStaking(#[from] babylon_btcstaking::error::Error), + #[error("error converting from hex to array: {0}")] + HexArrayError(#[from] HexToArrayError), + #[error("{0}")] + SliceError(#[from] FromSliceError), + #[error("{0}")] + StakingError(#[from] StakingApiError), + #[error("{0}")] + MerkleError(#[from] MerkleError), + #[error("{0}")] + ProtoError(#[from] DecodeError), + #[error("{0}")] + HexError(#[from] FromHexError), + #[error("EOTS error: {0}")] + EotsError(#[from] eots::Error), + #[error("{0}")] + SecP256K1Error(String), // TODO: inherit errors from k256 + #[error("Unauthorized")] + Unauthorized, + #[error("Failed to verify the finality provider registration request: {0}")] + FinalityProviderVerificationError(String), + #[error("Finality provider already exists: {0}")] + FinalityProviderAlreadyExists(String), + #[error("No finality providers are registered in this Consumer")] + FinalityProviderNotRegistered, + #[error("Finality provider not found: {0}")] + FinalityProviderNotFound(String), + #[error("Staking tx hash already exists: {0}")] + DelegationAlreadyExists(String), + #[error("Invalid Btc tx: {0}")] + InvalidBtcTx(String), + #[error("Empty signature from the delegator")] + EmptySignature, + #[error("Invalid lock type: seconds")] + ErrInvalidLockType, + #[error("Invalid lock time blocks: {0}, max: {1}")] + ErrInvalidLockTime(u32, u32), + #[error("The finality provider {0} does not have voting power at height {1}")] + NoVotingPower(String, u64), + #[error("The chain has not reached the given height yet")] + HeightTooHigh, + #[error("The finality provider {0} signed two different blocks at height {1}")] + DuplicateFinalityVote(String, u64), + #[error("The request contains too few public randomness. Required minimum: {0}, actual: {1}")] + TooFewPubRand(u64, u64), + #[error("The start height ({0}) has overlap with the height of the highest public randomness committed ({1})")] + InvalidPubRandHeight(u64, u64), + #[error("Invalid signature over the public randomness list")] + InvalidPubRandSignature, + #[error("Public randomness not found for finality provider {0} at height {1}")] + MissingPubRandCommit(String, u64), + #[error("The inclusion proof for height {0} does not correspond to the given height ({1})")] + InvalidFinalitySigHeight(u64, u64), + #[error("The total amount of public randomnesses in the proof ({0}) does not match the amount of public committed randomness ({1})")] + InvalidFinalitySigAmount(u64, u64), + #[error("Invalid finality signature: {0}")] + InvalidSignature(String), + #[error("Failed to verify signature: {0}")] + FailedSignatureVerification(String), + #[error("Block {0} is finalized, but last finalized height does not reach here")] + FinalisedBlockWithFinalityProviderSet(u64), + #[error("Block {0} is finalized, but does not have a finality provider set")] + FinalisedBlockWithoutFinalityProviderSet(u64), + #[error("Block {0} is not found: {1}")] + BlockNotFound(u64, String), + #[error("The finality provider {0} has already been slashed")] + FinalityProviderAlreadySlashed(String), + #[error("Failed to slash finality provider: {0}")] + FailedToSlashFinalityProvider(String), + #[error("Failed to extract secret key: {0}")] + SecretKeyExtractionError(String), + #[error("Hash length error: {0}")] + WrongHashLength(String), +} diff --git a/contracts/babylon-finality/src/finality.rs b/contracts/babylon-finality/src/finality.rs index 1bceeceb..654fc8af 100644 --- a/contracts/babylon-finality/src/finality.rs +++ b/contracts/babylon-finality/src/finality.rs @@ -9,19 +9,16 @@ use cosmwasm_std::{ to_json_binary, DepsMut, Env, Event, Order, Response, StdResult, Storage, WasmMsg, }; -use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; -use babylon_bindings::BabylonMsg; -use babylon_merkle::Proof; - use crate::error::ContractError; -use crate::msg::FinalityProviderInfo; -use crate::staking; use crate::state::config::{CONFIG, PARAMS}; use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER}; use crate::state::public_randomness::{ get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES, }; -use crate::state::staking::{fps, FPS}; +use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; +use babylon_bindings::BabylonMsg; +use babylon_merkle::Proof; +use btc_staking::msg::FinalityProviderInfo; pub fn handle_public_randomness_commit( deps: DepsMut, @@ -39,7 +36,10 @@ pub fn handle_public_randomness_commit( // TODO: ensure log_2(num_pub_rand) is an integer? // Ensure the finality provider is registered - if !FPS.has(deps.storage, fp_pubkey_hex) { + if !deps.querier.query_wasm_smart( + CONFIG.load(deps.storage)?.staking, + btc_staking::msg::QueryMsg::FinalityProvider {}, + )? { return Err(ContractError::FinalityProviderNotFound( fp_pubkey_hex.to_string(), )); @@ -129,7 +129,12 @@ pub fn handle_finality_signature( signature: &[u8], ) -> Result, ContractError> { // Ensure the finality provider exists - let fp = FPS.load(deps.storage, fp_btc_pk_hex)?; + let fp = deps.querier.query_wasm_smart( + CONFIG.load(deps.storage)?.staking, + &btc_staking::msg::QueryMsg::FinalityProvider { + btc_pk_hex: fp_btc_pk_hex.to_string(), + }, + )?; // Ensure the finality provider is not slashed at this time point // NOTE: It's possible that the finality provider equivocates for height h, and the signature is @@ -156,11 +161,20 @@ pub fn handle_finality_signature( } // Ensure the finality provider has voting power at this height - if fps() - .may_load_at_height(deps.storage, fp_btc_pk_hex, height)? - .ok_or_else(|| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))? - .power - == 0 + // if fps() + // .may_load_at_height(deps.storage, fp_btc_pk_hex, height)? + // .ok_or_else(|| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))? + // .power + // == 0 + if deps.querier.query_wasm_smart( + CONFIG.load(deps.storage)?.staking, + btc_staking::msg::QueryMsg::FinalityProvider {}, + )? { + return Err(ContractError::NoVotingPower( + fp_btc_pk_hex.to_string(), + height, + )); + } { return Err(ContractError::NoVotingPower( fp_btc_pk_hex.to_string(), @@ -554,7 +568,11 @@ pub fn compute_active_finality_providers( } #[cfg(test)] -pub(crate) mod tests { +mod tests { + use crate::contract::instantiate; + use crate::contract::tests::{create_new_finality_provider, get_public_randomness_commitment}; + use crate::error::ContractError; + use crate::msg::InstantiateMsg; use babylon_apis::btc_staking_api::SudoMsg; use babylon_apis::finality_api::IndexedBlock; use babylon_bindings::BabylonMsg; @@ -565,14 +583,11 @@ pub(crate) mod tests { use hex::ToHex; use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; - use crate::contract::tests::{ - create_new_finality_provider, get_params, get_public_randomness_commitment, CREATOR, - }; - use crate::contract::{execute, instantiate}; - use crate::msg::{ExecuteMsg, FinalitySignatureResponse, InstantiateMsg}; - use crate::queries; + const CREATOR: &str = "creator"; + const INIT_ADMIN: &str = "initial_admin"; + const NEW_ADMIN: &str = "new_admin"; - pub(crate) fn mock_env_height(height: u64) -> Env { + fn mock_env_height(height: u64) -> Env { let mut env = mock_env(); env.block.height = height; @@ -580,11 +595,11 @@ pub(crate) mod tests { } #[track_caller] - pub(crate) fn call_begin_block( + pub fn call_begin_block( deps: &mut OwnedDeps, app_hash: &[u8], height: u64, - ) -> Result, crate::error::ContractError> { + ) -> Result, ContractError> { let env = mock_env_height(height); // Hash is not used in the begin-block handler let hash_hex = "deadbeef".to_string(); @@ -601,11 +616,11 @@ pub(crate) mod tests { } #[track_caller] - pub(crate) fn call_end_block( + pub fn call_end_block( deps: &mut OwnedDeps, app_hash: &[u8], height: u64, - ) -> Result, crate::error::ContractError> { + ) -> Result, ContractError> { let env = mock_env_height(height); // Hash is not used in the end-block handler let hash_hex = "deadbeef".to_string(); @@ -712,11 +727,11 @@ pub(crate) mod tests { let _res = execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); // Activated height is not set - let res = crate::queries::activated_height(deps.as_ref()).unwrap(); + let res = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); assert_eq!(res.height, 0); // Add a delegation, so that the finality provider has some power - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; let msg = ExecuteMsg::BtcStaking { @@ -729,7 +744,7 @@ pub(crate) mod tests { execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); // Activated height is now set - let activated_height = crate::queries::activated_height(deps.as_ref()).unwrap(); + let activated_height = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); assert_eq!(activated_height.height, initial_height + 1); // Submit public randomness commitment for the FP and the involved heights @@ -795,7 +810,7 @@ pub(crate) mod tests { .unwrap(); // Query finality signature for that exact height - let sig = crate::queries::finality_signature( + let sig = btc_staking::queries::finality_signature( deps.as_ref(), pk_hex.to_string(), initial_height + 1, @@ -852,7 +867,7 @@ pub(crate) mod tests { execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); // Add a delegation, so that the finality provider has some power - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; let msg = ExecuteMsg::BtcStaking { @@ -962,7 +977,7 @@ pub(crate) mod tests { assert_eq!(0, res.messages.len()); // Assert the submitted block has been indexed and finalised - let indexed_block = crate::queries::block(deps.as_ref(), submit_height).unwrap(); + let indexed_block = btc_staking::queries::block(deps.as_ref(), submit_height).unwrap(); assert_eq!( indexed_block, IndexedBlock { @@ -1015,7 +1030,7 @@ pub(crate) mod tests { let _res = execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); // Add a delegation, so that the finality provider has some power - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; let msg = ExecuteMsg::BtcStaking { @@ -1103,7 +1118,7 @@ pub(crate) mod tests { // Assert the double signing evidence is proper let btc_pk = hex::decode(pk_hex.clone()).unwrap(); - let evidence = crate::queries::evidence(deps.as_ref(), pk_hex.clone(), submit_height) + let evidence = btc_staking::queries::evidence(deps.as_ref(), pk_hex.clone(), submit_height) .unwrap() .evidence .unwrap(); @@ -1113,7 +1128,7 @@ pub(crate) mod tests { // Assert the slashing propagation msg is there assert_eq!(1, res.messages.len()); // Assert the slashing propagation msg is proper - let babylon_addr = crate::queries::config(deps.as_ref()).unwrap().babylon; + let babylon_addr = btc_staking::queries::config(deps.as_ref()).unwrap().babylon; // Assert the slashing event is there assert_eq!(1, res.events.len()); // Assert the slashing event is proper @@ -1138,7 +1153,7 @@ pub(crate) mod tests { call_end_block(&mut deps, "deadbeef02".as_bytes(), final_height).unwrap(); // Assert the canonical block has been indexed (and finalised) - let indexed_block = crate::queries::block(deps.as_ref(), submit_height).unwrap(); + let indexed_block = btc_staking::queries::block(deps.as_ref(), submit_height).unwrap(); assert_eq!( indexed_block, IndexedBlock { @@ -1149,7 +1164,7 @@ pub(crate) mod tests { ); // Assert the finality provider has been slashed - let fp = crate::queries::finality_provider(deps.as_ref(), pk_hex).unwrap(); + let fp = btc_staking::queries::finality_provider(deps.as_ref(), pk_hex).unwrap(); assert_eq!(fp.slashed_height, submit_height); } } diff --git a/contracts/babylon-finality/src/lib.rs b/contracts/babylon-finality/src/lib.rs new file mode 100644 index 00000000..55a81eab --- /dev/null +++ b/contracts/babylon-finality/src/lib.rs @@ -0,0 +1,7 @@ +mod finality; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod queries; +pub mod state; diff --git a/contracts/babylon-finality/src/msg.rs b/contracts/babylon-finality/src/msg.rs new file mode 100644 index 00000000..891500f9 --- /dev/null +++ b/contracts/babylon-finality/src/msg.rs @@ -0,0 +1,101 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cw_controllers::AdminResponse; + +use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; +use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; + +use crate::state::config::{Config, Params}; + +#[cw_serde] +#[derive(Default)] +pub struct InstantiateMsg { + pub params: Option, + pub admin: Option, +} + +pub type ExecuteMsg = babylon_apis::finality_api::ExecuteMsg; + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// `Config` returns the current configuration of the babylon-finality contract + #[returns(Config)] + Config {}, + /// `Params` returns the current Consumer-specific parameters of the babylon-finality contract + #[returns(Params)] + Params {}, + /// `Admin` returns the current admin of the contract + #[returns(AdminResponse)] + Admin {}, + /// `FinalitySignature` returns the signature of the finality provider for a given block height + /// + #[returns(FinalitySignatureResponse)] + FinalitySignature { btc_pk_hex: String, height: u64 }, + /// `PubRandCommit` returns the public random commitments for a given FP. + /// + /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. + /// + /// `start_after` is the height of to start after (before, if `reverse` is `true`), + /// or `None` to start from the beginning (end, if `reverse` is `true`). + /// `limit` is the maximum number of commitments to return. + /// `reverse` is an optional flag to return the commitments in reverse order + #[returns(PubRandCommit)] + PubRandCommit { + btc_pk_hex: String, + start_after: Option, + limit: Option, + reverse: Option, + }, + /// `FirstPubRandCommit` returns the first public random commitment (if any) for a given FP. + /// + /// It's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to + /// false. + /// + /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. + #[returns(Option)] + FirstPubRandCommit { btc_pk_hex: String }, + /// `LastPubRandCommit` returns the last public random commitment (if any) for a given FP. + /// + /// It's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to + /// true. + /// + /// `btc_pk_hex` is the BTC public key of the finality provider, in hex format. + #[returns(Option)] + LastPubRandCommit { btc_pk_hex: String }, + /// `Block` returns the indexed block information at height + /// + #[returns(IndexedBlock)] + Block { height: u64 }, + /// `Blocks` return the list of indexed blocks. + /// + /// `start_after` is the height of the block to start after (before, if `reverse` is `true`), + /// or `None` to start from the beginning (end, if `reverse` is `true`). + /// `limit` is the maximum number of blocks to return. + /// `finalised` is an optional filter to return only finalised blocks. + /// `reverse` is an optional flag to return the blocks in reverse order + #[returns(BlocksResponse)] + Blocks { + start_after: Option, + limit: Option, + finalised: Option, + reverse: Option, + }, + /// `Evidence` returns the evidence for a given FP and block height + #[returns(EvidenceResponse)] + Evidence { btc_pk_hex: String, height: u64 }, +} + +#[cw_serde] +pub struct FinalitySignatureResponse { + pub signature: Vec, +} + +#[cw_serde] +pub struct BlocksResponse { + pub blocks: Vec, +} + +#[cw_serde] +pub struct EvidenceResponse { + pub evidence: Option, +} diff --git a/contracts/babylon-finality/src/queries.rs b/contracts/babylon-finality/src/queries.rs new file mode 100644 index 00000000..1f8dcffa --- /dev/null +++ b/contracts/babylon-finality/src/queries.rs @@ -0,0 +1,888 @@ +use std::str::FromStr; + +use bitcoin::hashes::Hash; +use bitcoin::Txid; + +use cosmwasm_std::Order::{Ascending, Descending}; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; + +use babylon_apis::btc_staking_api::FinalityProvider; +use babylon_apis::finality_api::IndexedBlock; + +use crate::error::ContractError; +use crate::msg::{BlocksResponse, EvidenceResponse, FinalitySignatureResponse}; +use crate::state::config::{Config, Params}; +use crate::state::config::{CONFIG, PARAMS}; +use crate::state::finality::{BLOCKS, EVIDENCES}; + +pub fn config(deps: Deps) -> StdResult { + CONFIG.load(deps.storage) +} + +pub fn params(deps: Deps) -> StdResult { + PARAMS.load(deps.storage) +} + +pub fn finality_provider(deps: Deps, btc_pk_hex: String) -> StdResult { + FPS.load(deps.storage, &btc_pk_hex) +} + +// Settings for pagination +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +pub fn finality_providers( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_after = start_after.as_ref().map(|s| Bound::exclusive(&**s)); + let fps = FPS + .range_raw(deps.storage, start_after, None, Order::Ascending) + .take(limit) + .map(|item| item.map(|(_, v)| v)) + .collect::>>()?; + Ok(FinalityProvidersResponse { fps }) +} + +/// Get the delegation info by staking tx hash. +/// `staking_tx_hash_hex`: The (reversed) staking tx hash, in hex +pub fn delegation(deps: Deps, staking_tx_hash_hex: String) -> Result { + let staking_tx_hash = Txid::from_str(&staking_tx_hash_hex)?; + Ok(DELEGATIONS.load(deps.storage, staking_tx_hash.as_ref())?) +} + +/// Get list of delegations. +/// `start_after`: The (reversed) associated staking tx hash of the delegation in hex, if provided. +/// `active`: List only active delegations if true, otherwise list all delegations. +pub fn delegations( + deps: Deps, + start_after: Option, + limit: Option, + active: Option, +) -> Result { + let active = active.unwrap_or_default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_after = start_after + .as_ref() + .map(|s| Txid::from_str(s)) + .transpose()?; + let start_after = start_after.as_ref().map(|s| s.as_ref()); + let start_after = start_after.map(Bound::exclusive); + let delegations = DELEGATIONS + .range_raw(deps.storage, start_after, None, Order::Ascending) + .filter(|item| { + if let Ok((_, del)) = item { + !active || del.is_active() + } else { + true // don't filter errors + } + }) + .take(limit) + .map(|item| item.map(|(_, v)| v)) + .collect::, _>>()?; + Ok(BtcDelegationsResponse { delegations }) +} + +/// Delegation hashes by FP query. +/// +/// `btc_pk_hex`: The BTC public key of the finality provider, in hex +pub fn delegations_by_fp( + deps: Deps, + btc_pk_hex: String, +) -> Result { + let tx_hashes = FP_DELEGATIONS.load(deps.storage, &btc_pk_hex)?; + let tx_hashes = tx_hashes + .iter() + .map(|h| Ok(Txid::from_slice(h)?.to_string())) + .collect::>()?; + Ok(DelegationsByFPResponse { hashes: tx_hashes }) +} + +/// Active / all delegations by FP convenience query. +/// +/// This is an alternative to `delegations_by_fp` that returns the actual delegations instead of +/// just the hashes. +/// +/// `btc_pk_hex`: The BTC public key of the finality provider, in hex. +/// `active` is a filter to return only active delegations +pub fn active_delegations_by_fp( + deps: Deps, + btc_pk_hex: String, + active: bool, +) -> Result { + let tx_hashes = FP_DELEGATIONS.load(deps.storage, &btc_pk_hex)?; + let delegations = tx_hashes + .iter() + .map(|h| Ok(DELEGATIONS.load(deps.storage, Txid::from_slice(h)?.as_ref())?)) + .filter(|item| { + if let Ok(del) = item { + !active || del.is_active() + } else { + true // don't filter errors + } + }) + .collect::, ContractError>>()?; + Ok(BtcDelegationsResponse { delegations }) +} + +pub fn finality_provider_info( + deps: Deps, + btc_pk_hex: String, + height: Option, +) -> StdResult { + let fp_state = match height { + Some(h) => fps().may_load_at_height(deps.storage, &btc_pk_hex, h), + None => fps().may_load(deps.storage, &btc_pk_hex), + }? + .unwrap_or_default(); + + Ok(FinalityProviderInfo { + btc_pk_hex, + power: fp_state.power, + }) +} + +pub fn finality_providers_by_power( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.map(|fpp| Bound::exclusive((fpp.power, fpp.btc_pk_hex.clone()))); + let fps = fps() + .idx + .power + .range(deps.storage, None, start, Descending) + .take(limit) + .map(|item| { + let (btc_pk_hex, FinalityProviderState { power }) = item?; + Ok(FinalityProviderInfo { btc_pk_hex, power }) + }) + .collect::>>()?; + + Ok(FinalityProvidersByPowerResponse { fps }) +} + +pub fn finality_signature( + deps: Deps, + btc_pk_hex: String, + height: u64, +) -> StdResult { + match babylon_finality::state::finality::SIGNATURES + .may_load(deps.storage, (height, &btc_pk_hex))? + { + Some(sig) => Ok(FinalitySignatureResponse { signature: sig }), + None => Ok(FinalitySignatureResponse { + signature: Vec::new(), + }), // Empty signature response + } +} + +pub fn activated_height(deps: Deps) -> Result { + let activated_height = ACTIVATED_HEIGHT.may_load(deps.storage)?.unwrap_or_default(); + Ok(ActivatedHeightResponse { + height: activated_height, + }) +} + +pub fn block(deps: Deps, height: u64) -> StdResult { + BLOCKS.load(deps.storage, height) +} + +/// Get list of blocks. +/// `start_after`: The height to start after, if any. +/// `finalised`: List only finalised blocks if true, otherwise list all blocks. +/// `reverse`: List in descending order if present and true, otherwise in ascending order. +pub fn blocks( + deps: Deps, + start_after: Option, + limit: Option, + finalised: Option, + reverse: Option, +) -> Result { + let finalised = finalised.unwrap_or_default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_after = start_after.map(Bound::exclusive); + let (start, end, order) = if reverse.unwrap_or(false) { + (None, start_after, Descending) + } else { + (start_after, None, Ascending) + }; + let blocks = BLOCKS + .range_raw(deps.storage, start, end, order) + .filter(|item| { + if let Ok((_, block)) = item { + !finalised || block.finalized + } else { + true // don't filter errors + } + }) + .take(limit) + .map(|item| item.map(|(_, v)| v)) + .collect::, _>>()?; + Ok(BlocksResponse { blocks }) +} + +pub fn evidence(deps: Deps, btc_pk_hex: String, height: u64) -> StdResult { + let evidence = EVIDENCES.may_load(deps.storage, (&btc_pk_hex, height))?; + Ok(EvidenceResponse { evidence }) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::storage_keys::namespace_with_key; + use cosmwasm_std::testing::message_info; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::StdError::NotFound; + use cosmwasm_std::{from_json, Binary, Storage}; + + use babylon_apis::btc_staking_api::{FinalityProvider, UnbondedBtcDelegation}; + + use crate::contract::tests::{create_new_finality_provider, get_params}; + use crate::contract::{execute, instantiate}; + use crate::error::ContractError; + use crate::msg::{ExecuteMsg, InstantiateMsg}; + use crate::state::config::PARAMS; + + const CREATOR: &str = "creator"; + + // Sort delegations by staking tx hash + fn sort_delegations(dels: &[BtcDelegation]) -> Vec { + let mut dels = dels.to_vec(); + dels.sort_by_key(staking_tx_hash); + dels + } + + #[test] + fn test_finality_providers() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a couple finality providers + let new_fp1 = create_new_finality_provider(1); + let new_fp2 = create_new_finality_provider(2); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone(), new_fp2.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query finality providers + let fps = crate::queries::finality_providers(deps.as_ref(), None, None) + .unwrap() + .fps; + + let fp1 = FinalityProvider::from(&new_fp1); + let fp2 = FinalityProvider::from(&new_fp2); + let expected_fps = vec![fp1.clone(), fp2.clone()]; + assert_eq!(fps.len(), expected_fps.len()); + for fp in fps { + assert!(expected_fps.contains(&fp)); + } + + // Query finality providers with limit + let fps = crate::queries::finality_providers(deps.as_ref(), None, Some(1)) + .unwrap() + .fps; + assert_eq!(fps.len(), 1); + assert!(fps[0] == fp1 || fps[0] == fp2); + + // Query finality providers with start_after + let fp_pk = fps[0].btc_pk_hex.clone(); + let fps = crate::queries::finality_providers(deps.as_ref(), Some(fp_pk), None) + .unwrap() + .fps; + assert_eq!(fps.len(), 1); + assert!(fps[0] == fp1 || fps[0] == fp2); + } + + #[test] + fn test_delegations() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a couple finality providers + let new_fp1 = create_new_finality_provider(1); + let new_fp2 = create_new_finality_provider(2); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone(), new_fp2.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add a couple delegations + let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query delegations + let dels = crate::queries::delegations(deps.as_ref(), None, None, None) + .unwrap() + .delegations; + + assert_eq!(dels.len(), 2); + // Sort original delegations by staking tx hash (to compare with the query result) + let sorted_dels = sort_delegations(&[del1.into(), del2.into()]); + assert_eq!(dels[0], sorted_dels[0]); + assert_eq!(dels[1], sorted_dels[1]); + + // Query delegations with limit + let dels = crate::queries::delegations(deps.as_ref(), None, Some(1), None) + .unwrap() + .delegations; + + assert_eq!(dels.len(), 1); + assert_eq!(dels[0], sorted_dels[0]); + + // Query delegations with start_after + let staking_tx_hash_hex = staking_tx_hash(&sorted_dels[0]).to_string(); + let dels = + crate::queries::delegations(deps.as_ref(), Some(staking_tx_hash_hex), None, None) + .unwrap() + .delegations; + + assert_eq!(dels.len(), 1); + assert_eq!(dels[0], sorted_dels[1]); + } + + #[test] + fn test_active_delegations() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a finality provider + let new_fp1 = create_new_finality_provider(1); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add a couple delegations + let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query only active delegations + let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(true)) + .unwrap() + .delegations; + assert_eq!(dels.len(), 2); + // Sort original delegations by staking tx hash (to compare with the query result) + let sorted_dels = sort_delegations(&[del1.clone().into(), del2.clone().into()]); + assert_eq!(dels[0], sorted_dels[0]); + assert_eq!(dels[1], sorted_dels[1]); + + // Unbond the second delegation + // Compute staking tx hash + let staking_tx_hash_hex = staking_tx_hash(&del2.into()).to_string(); + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![UnbondedBtcDelegation { + staking_tx_hash: staking_tx_hash_hex, + unbonding_tx_sig: Binary::new(vec![0x01, 0x02, 0x03]), + }], + }; + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query only active delegations + let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(true)) + .unwrap() + .delegations; + assert_eq!(dels.len(), 1); + assert_eq!(dels[0], del1.into()); + + // Query all delegations (with active set to false) + let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(false)) + .unwrap() + .delegations; + assert_eq!(dels.len(), 2); + + // Query all delegations (without active set) + let dels = crate::queries::delegations(deps.as_ref(), None, None, None) + .unwrap() + .delegations; + assert_eq!(dels.len(), 2); + } + + #[test] + fn test_delegations_by_fp() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a couple finality providers + let new_fp1 = create_new_finality_provider(1); + let fp1_pk = new_fp1.btc_pk_hex.clone(); + let new_fp2 = create_new_finality_provider(2); + let fp2_pk = new_fp2.btc_pk_hex.clone(); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone(), new_fp2.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add a couple delegations + let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query delegations by finality provider + let dels1 = crate::queries::delegations_by_fp(deps.as_ref(), fp1_pk.clone()) + .unwrap() + .hashes; + assert_eq!(dels1.len(), 1); + let dels2 = crate::queries::delegations_by_fp(deps.as_ref(), fp2_pk.clone()) + .unwrap() + .hashes; + assert_eq!(dels2.len(), 1); + assert_ne!(dels1[0], dels2[0]); + let err = crate::queries::delegations_by_fp(deps.as_ref(), "f3".to_string()).unwrap_err(); + assert!(matches!(err, ContractError::Std(NotFound { .. }))); + } + + #[test] + fn test_active_delegations_by_fp() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a finality provider + let new_fp1 = create_new_finality_provider(1); + let fp1_pk = new_fp1.btc_pk_hex.clone(); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add a couple delegations + let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); + + // Adjust staking amounts + del1.total_sat = 100; + del2.total_sat = 200; + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query all delegations by finality provider + let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), false) + .unwrap() + .delegations; + assert_eq!(dels1.len(), 2); + + // Query active delegations by finality provider + let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), true) + .unwrap() + .delegations; + assert_eq!(dels1.len(), 2); + + // Unbond the first delegation + // Compute staking tx hash + let staking_tx_hash_hex = staking_tx_hash(&del1.into()).to_string(); + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![UnbondedBtcDelegation { + staking_tx_hash: staking_tx_hash_hex, + unbonding_tx_sig: Binary::new(vec![0x01, 0x02, 0x03]), + }], + }; + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query all delegations by finality provider + let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), false) + .unwrap() + .delegations; + assert_eq!(dels1.len(), 2); + + // Query active delegations by finality provider + let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), true) + .unwrap() + .delegations; + assert_eq!(dels1.len(), 1); + let err = crate::queries::active_delegations_by_fp(deps.as_ref(), "f2".to_string(), false) + .unwrap_err(); + assert!(matches!(err, ContractError::Std(NotFound { .. }))); + } + + #[test] + fn test_fp_info() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + let initial_env = crate::finality::tests::mock_env_height(10); + + instantiate( + deps.as_mut(), + initial_env.clone(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a finality provider + let new_fp1 = create_new_finality_provider(1); + let fp1_pk = new_fp1.btc_pk_hex.clone(); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); + + // Add a couple delegations + let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); + + // Adjust staking amounts + del1.total_sat = 100; + del2.total_sat = 150; + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env_height(11), info.clone(), msg).unwrap(); + + // Query finality provider info + let fp = + crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), None).unwrap(); + assert_eq!( + fp, + FinalityProviderInfo { + btc_pk_hex: fp1_pk.clone(), + power: 250, + } + ); + + // Query finality provider info with same height as execute call + let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(11)) + .unwrap(); + assert_eq!( + fp, + FinalityProviderInfo { + btc_pk_hex: fp1_pk.clone(), + power: 0, // Historical data is not checkpoint yet + } + ); + + // Query finality provider info with past height as execute call + let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(12)) + .unwrap(); + assert_eq!( + fp, + FinalityProviderInfo { + btc_pk_hex: fp1_pk.clone(), + power: 250, + } + ); + + // Query finality provider info with some larger height + let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(1000)) + .unwrap(); + assert_eq!( + fp, + FinalityProviderInfo { + btc_pk_hex: fp1_pk.clone(), + power: 250, + } + ); + } + + #[test] + fn test_fp_info_raw_query() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a finality provider + let new_fp1 = create_new_finality_provider(1); + let fp1_pk = new_fp1.btc_pk_hex.clone(); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add a delegation + let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); + // Adjust staking amount + del1.total_sat = 100; + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone()], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Build raw key + let prefixed_key = namespace_with_key(&[FP_STATE_KEY.as_bytes()], fp1_pk.as_bytes()); + // Read directly from storage + let fp_state_raw = deps.storage.get(&prefixed_key).unwrap(); + // Deserialize result + let fp_state: FinalityProviderState = from_json(fp_state_raw).unwrap(); + + assert_eq!(fp_state, FinalityProviderState { power: 100 }); + } + + #[test] + fn test_fps_by_power() { + let mut deps = mock_dependencies(); + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + InstantiateMsg { + params: None, + admin: None, + }, + ) + .unwrap(); + + let params = get_params(); + PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); + + // Add a couple finality providers + let new_fp1 = create_new_finality_provider(1); + let fp1_pk = new_fp1.btc_pk_hex.clone(); + let new_fp2 = create_new_finality_provider(2); + let fp2_pk = new_fp2.btc_pk_hex.clone(); + let new_fp3 = create_new_finality_provider(3); + let fp3_pk = new_fp3.btc_pk_hex.clone(); + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![new_fp1.clone(), new_fp2.clone(), new_fp3.clone()], + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }; + + let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Add some delegations + let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1, 3]); + let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); + let mut del3 = crate::contract::tests::get_derived_btc_delegation(3, &[2]); + + // Adjust staking amounts + del1.total_sat = 100; + del2.total_sat = 150; + del3.total_sat = 75; + + let msg = ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: vec![del1.clone(), del2.clone(), del3], + slashed_del: vec![], + unbonded_del: vec![], + }; + + execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // Query finality providers by power + let fps = crate::queries::finality_providers_by_power(deps.as_ref(), None, None) + .unwrap() + .fps; + assert_eq!(fps.len(), 3); + assert_eq!(fps[0], { + FinalityProviderInfo { + btc_pk_hex: fp2_pk.clone(), + power: 225, + } + }); + // fp1 and fp3 can be in arbitrary order + let fp1_info = FinalityProviderInfo { + btc_pk_hex: fp1_pk.clone(), + power: 100, + }; + let fp3_info = FinalityProviderInfo { + btc_pk_hex: fp3_pk.clone(), + power: 100, + }; + assert!( + (fps[1] == fp1_info && fps[2] == fp3_info) + || (fps[1] == fp3_info && fps[2] == fp1_info) + ); + + // Query finality providers power with limit + let fps = crate::queries::finality_providers_by_power(deps.as_ref(), None, Some(2)) + .unwrap() + .fps; + assert_eq!(fps.len(), 2); + assert_eq!(fps[0], { + FinalityProviderInfo { + btc_pk_hex: fp2_pk.clone(), + power: 225, + } + }); + assert!(fps[1] == fp1_info || fps[1] == fp3_info); + + // Query finality providers power with start_after + let fps = + crate::queries::finality_providers_by_power(deps.as_ref(), Some(fps[1].clone()), None) + .unwrap() + .fps; + assert_eq!(fps.len(), 1); + assert!(fps[0] == fp1_info || fps[0] == fp3_info); + } +} diff --git a/contracts/babylon-finality/src/state/config.rs b/contracts/babylon-finality/src/state/config.rs new file mode 100644 index 00000000..113d6790 --- /dev/null +++ b/contracts/babylon-finality/src/state/config.rs @@ -0,0 +1,61 @@ +use derivative::Derivative; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; + +use cw_controllers::Admin; +use cw_storage_plus::Item; + +use babylon_bitcoin::chain_params::Network; + +pub(crate) const CONFIG: Item = Item::new("config"); +pub(crate) const PARAMS: Item = Item::new("params"); +/// Storage for admin +pub(crate) const ADMIN: Admin = Admin::new("admin"); + +/// Config are Babylon-selectable BTC finality configuration +// TODO: Add / enable config entries as needed +#[cw_serde] +pub struct Config { + pub denom: String, + pub babylon: Addr, + pub staking: Addr, +} + +// TODO: Add / enable param entries as needed +#[cw_serde] +#[derive(Derivative)] +#[derivative(Default)] +pub struct Params { + // covenant_pks is the list of public keys held by the covenant committee each PK + // follows encoding in BIP-340 spec on Bitcoin + pub covenant_pks: Vec, + // covenant_quorum is the minimum number of signatures needed for the covenant multi-signature + pub covenant_quorum: u32, + #[derivative(Default(value = "Network::Regtest"))] + // ntc_network is the network the BTC staking protocol is running on + pub btc_network: Network, + // `min_commission_rate` is the chain-wide minimum commission rate that a finality provider + // can charge their delegators + // pub min_commission_rate: Decimal, + /// `max_active_finality_providers` is the maximum number of active finality providers in the + /// BTC staking protocol + #[derivative(Default(value = "100"))] + pub max_active_finality_providers: u32, + /// `min_pub_rand` is the minimum amount of public randomness each public randomness commitment + /// should commit + #[derivative(Default(value = "1"))] + pub min_pub_rand: u64, + /// `slashing_address` is the address that the slashed BTC goes to. + /// The address is in string format on Bitcoin. + #[derivative(Default(value = "String::from(\"n4cV57jePmAAue2WTTBQzH3k3R2rgWBQwY\")"))] + pub slashing_address: String, + /// `min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for + /// the pre-signed slashing tx + #[derivative(Default(value = "1000"))] + pub min_slashing_tx_fee_sat: u64, + /// `slashing_rate` determines the portion of the staked amount to be slashed, + /// expressed as a decimal (e.g. 0.5 for 50%). + #[derivative(Default(value = "String::from(\"0.1\")"))] + pub slashing_rate: String, +} diff --git a/contracts/babylon-finality/src/state/finality.rs b/contracts/babylon-finality/src/state/finality.rs index bb698edb..42dcb02d 100644 --- a/contracts/babylon-finality/src/state/finality.rs +++ b/contracts/babylon-finality/src/state/finality.rs @@ -2,16 +2,14 @@ use cw_storage_plus::{Item, Map}; use babylon_apis::finality_api::{Evidence, IndexedBlock}; -use crate::msg::FinalityProviderInfo; - /// Map of signatures by block height and FP -pub(crate) const SIGNATURES: Map<(u64, &str), Vec> = Map::new("fp_sigs"); +pub const SIGNATURES: Map<(u64, &str), Vec> = Map::new("fp_sigs"); /// Map of blocks information by height -pub(crate) const BLOCKS: Map = Map::new("blocks"); +pub const BLOCKS: Map = Map::new("blocks"); /// Next height to finalise -pub(crate) const NEXT_HEIGHT: Item = Item::new("next_height"); +pub const NEXT_HEIGHT: Item = Item::new("next_height"); /// `FP_SET` is the calculated list of the active finality providers by height pub const FP_SET: Map> = Map::new("fp_set"); @@ -21,4 +19,4 @@ pub const FP_SET: Map> = Map::new("fp_set"); pub const TOTAL_POWER: Item = Item::new("total_power"); /// Map of double signing evidence by FP and block height -pub(crate) const EVIDENCES: Map<(&str, u64), Evidence> = Map::new("evidences"); +pub const EVIDENCES: Map<(&str, u64), Evidence> = Map::new("evidences"); diff --git a/contracts/babylon-finality/src/state/mod.rs b/contracts/babylon-finality/src/state/mod.rs new file mode 100644 index 00000000..10d555c9 --- /dev/null +++ b/contracts/babylon-finality/src/state/mod.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod finality; +pub mod public_randomness; diff --git a/contracts/babylon-finality/src/state/public_randomness.rs b/contracts/babylon-finality/src/state/public_randomness.rs index 8c08d0c1..40862755 100644 --- a/contracts/babylon-finality/src/state/public_randomness.rs +++ b/contracts/babylon-finality/src/state/public_randomness.rs @@ -2,13 +2,12 @@ use cosmwasm_std::Order::{Ascending, Descending}; use cosmwasm_std::{StdResult, Storage}; use cw_storage_plus::{Bound, Map}; -use crate::error::ContractError; use babylon_apis::finality_api::PubRandCommit; /// Map of public randomness commitments by fp and block height -pub(crate) const PUB_RAND_COMMITS: Map<(&str, u64), PubRandCommit> = Map::new("fp_pub_rand_commit"); +pub const PUB_RAND_COMMITS: Map<(&str, u64), PubRandCommit> = Map::new("fp_pub_rand_commit"); /// Map of public randomness values by fp and block height -pub(crate) const PUB_RAND_VALUES: Map<(&str, u64), Vec> = Map::new("fp_pub_rand"); +pub const PUB_RAND_VALUES: Map<(&str, u64), Vec> = Map::new("fp_pub_rand"); pub fn get_pub_rand_commit_for_height( storage: &dyn Storage, From 806409e9517a9b01ce621ee9e77abed077747989 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 15 Sep 2024 08:41:39 +0200 Subject: [PATCH 07/51] Fix rebase errors --- contracts/btc-staking/src/contract.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index ce46784a..1a227d71 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -15,8 +15,6 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; use crate::staking::handle_btc_staking; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; -use crate::state::staking::ACTIVATED_HEIGHT; -use crate::{finality, queries, state}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -189,8 +187,8 @@ pub(crate) mod tests { get_fp_sk_bytes, get_pub_rand_commit, }; - const CREATOR: &str = "creator"; - const INIT_ADMIN: &str = "initial_admin"; + pub(crate) const CREATOR: &str = "creator"; + pub(crate) const INIT_ADMIN: &str = "initial_admin"; const NEW_ADMIN: &str = "new_admin"; fn new_params(params: ProtoParams) -> Params { From 44f1919c6aa9df003c1ffea9b3adaebac260bf32 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 15 Sep 2024 08:45:51 +0200 Subject: [PATCH 08/51] Remove sudo handlers from the staking contract --- contracts/btc-staking/src/contract.rs | 56 +-------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 1a227d71..8d3741ef 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -7,7 +7,6 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_utils::{maybe_addr, nonpayable}; -use babylon_apis::btc_staking_api::SudoMsg; use babylon_bindings::BabylonMsg; use crate::error::ContractError; @@ -126,38 +125,6 @@ pub fn execute( } } -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn sudo( - mut deps: DepsMut, - env: Env, - msg: SudoMsg, -) -> Result, ContractError> { - match msg { - SudoMsg::BeginBlock { .. } => handle_begin_block(&mut deps, env), - SudoMsg::EndBlock { - hash_hex, - app_hash_hex, - } => handle_end_block(&mut deps, env, &hash_hex, &app_hash_hex), - } -} - -fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result, ContractError> { - // Compute active finality provider set - let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize; - compute_active_finality_providers(deps.storage, env, max_active_fps)?; - - Ok(Response::new()) -} - -fn handle_end_block( - _deps: &mut DepsMut, - _env: Env, - _hash_hex: &str, - _app_hash_hex: &str, -) -> Result, ContractError> { - Ok(Response::new()) -} - #[cfg(test)] pub(crate) mod tests { use std::str::FromStr; @@ -169,7 +136,6 @@ pub(crate) mod tests { ActiveBtcDelegation, BtcUndelegationInfo, CovenantAdaptorSignatures, FinalityProviderDescription, NewFinalityProvider, ProofOfPossessionBtc, }; - use babylon_apis::finality_api::PubRandCommit; use babylon_bitcoin::chain_params::Network; use babylon_proto::babylon::btcstaking::v1::{ BtcDelegation, FinalityProvider, Params as ProtoParams, @@ -181,11 +147,7 @@ pub(crate) mod tests { }; use cw_controllers::AdminResponse; use hex::ToHex; - use k256::schnorr::{Signature, SigningKey}; - use test_utils::{ - get_btc_del_unbonding_sig_bytes, get_btc_delegation, get_finality_provider, - get_fp_sk_bytes, get_pub_rand_commit, - }; + use test_utils::{get_btc_delegation, get_finality_provider}; pub(crate) const CREATOR: &str = "creator"; pub(crate) const INIT_ADMIN: &str = "initial_admin"; @@ -296,22 +258,6 @@ pub(crate) mod tests { Signature::try_from(sig_bytes.as_slice()).unwrap() } - /// Get public randomness public key, commitment, and signature information - /// - /// Signature is a Schnorr signature over the commitment - pub(crate) fn get_public_randomness_commitment() -> (String, PubRandCommit, Vec) { - let pub_rand_commitment_msg = get_pub_rand_commit(); - ( - pub_rand_commitment_msg.fp_btc_pk.encode_hex(), - PubRandCommit { - start_height: pub_rand_commitment_msg.start_height, - num_pub_rand: pub_rand_commitment_msg.num_pub_rand, - commitment: pub_rand_commitment_msg.commitment.to_vec(), - }, - pub_rand_commitment_msg.sig.to_vec(), - ) - } - pub(crate) fn create_new_finality_provider(id: i32) -> NewFinalityProvider { let fp = get_finality_provider(id); new_finality_provider(fp) From 3177ada2904eced999da3a5de0548b8b6f25d945 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 15 Sep 2024 09:14:11 +0200 Subject: [PATCH 09/51] Add staking slash handler / msg --- contracts/btc-staking/src/contract.rs | 6 +++-- contracts/btc-staking/src/staking.rs | 29 ++++++++++++++++------- contracts/btc-staking/src/state/config.rs | 1 + packages/apis/src/btc_staking_api.rs | 7 ++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 8d3741ef..7b5ec3d8 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, + to_json_binary, Addr, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, StdResult, }; use cw2::set_contract_version; @@ -12,7 +12,7 @@ use babylon_bindings::BabylonMsg; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; -use crate::staking::handle_btc_staking; +use crate::staking::{handle_btc_staking, handle_slash_fp}; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -30,6 +30,7 @@ pub fn instantiate( let config = Config { denom, babylon: info.sender, + finality: Addr::unchecked(""), // TODO: Instantiate finality contract and set address in reply handler }; CONFIG.save(deps.storage, &config)?; @@ -122,6 +123,7 @@ pub fn execute( &slashed_del, &unbonded_del, ), + ExecuteMsg::Slash { fp_btc_pk_hex } => handle_slash_fp(deps, env, &info, &fp_btc_pk_hex), } } diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index 2143a116..a988ceae 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -313,6 +313,20 @@ fn handle_slashed_delegation( Ok(slashing_event) } +/// handle_slash_fp handles FP slashing at the staking level +pub fn handle_slash_fp( + deps: DepsMut, + env: Env, + info: &MessageInfo, + fp_btc_pk_hex: &str, +) -> Result, ContractError> { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.finality && !ADMIN.is_admin(deps.as_ref(), &info.sender)? { + return Err(ContractError::Unauthorized); + } + slash_finality_provider(deps, env, fp_btc_pk_hex) +} + /// btc_undelegate adds the signature of the unbonding tx signed by the staker to the given BTC /// delegation fn btc_undelegate( @@ -338,11 +352,10 @@ fn btc_undelegate( /// `slash_finality_provider` slashes a finality provider with the given PK. /// A slashed finality provider will not have voting power pub(crate) fn slash_finality_provider( - deps: &mut DepsMut, + deps: DepsMut, env: Env, fp_btc_pk_hex: &str, - height: u64, -) -> Result<(), ContractError> { +) -> Result, ContractError> { // Ensure finality provider exists let mut fp = FPS.load(deps.storage, fp_btc_pk_hex)?; @@ -353,12 +366,12 @@ pub(crate) fn slash_finality_provider( )); } // Set the finality provider as slashed - fp.slashed_height = height; + fp.slashed_height = env.block.height; // Set BTC slashing height (if available from the babylon contract) // FIXME: Turn this into a hard error // return fmt.Errorf("failed to get current BTC tip") - let btc_height = get_btc_tip_height(deps).unwrap_or_default(); + let btc_height = get_btc_tip_height(&deps).unwrap_or_default(); fp.slashed_btc_height = btc_height; // Record slashed event. The next `BeginBlock` will consume this event for updating the active @@ -374,7 +387,8 @@ pub(crate) fn slash_finality_provider( // Save the finality provider back FPS.save(deps.storage, fp_btc_pk_hex, &fp)?; - Ok(()) + // TODO: Add events + Ok(Response::new()) } /// get_btc_tip_height queries the Babylon contract for the latest BTC tip height @@ -397,8 +411,7 @@ pub(crate) mod tests { use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; use crate::contract::tests::{ - create_new_finality_provider, create_new_fp_sk, get_active_btc_delegation, - get_btc_del_unbonding_sig, get_derived_btc_delegation, get_params, CREATOR, INIT_ADMIN, + create_new_finality_provider, get_active_btc_delegation, get_params, CREATOR, INIT_ADMIN, }; use crate::contract::{execute, instantiate}; use crate::msg::{ExecuteMsg, InstantiateMsg}; diff --git a/contracts/btc-staking/src/state/config.rs b/contracts/btc-staking/src/state/config.rs index cf3dbfa5..2bfa7111 100644 --- a/contracts/btc-staking/src/state/config.rs +++ b/contracts/btc-staking/src/state/config.rs @@ -17,6 +17,7 @@ pub(crate) const ADMIN: Admin = Admin::new("admin"); pub struct Config { pub denom: String, pub babylon: Addr, + pub finality: Addr, // covenant_pks is the list of public keys held by the covenant committee each PK // follows encoding in BIP-340 spec on Bitcoin // pub covenant_pks: Vec, diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index d2af7699..d83c4ee3 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -19,6 +19,13 @@ pub enum ExecuteMsg { slashed_del: Vec, unbonded_del: Vec, }, + /// Slash finality provider staking power. + /// Used by the finality contract only. + /// The finality contract will call this message to slash the finality provider's staking power + /// when the finality provider is found to be malicious + Slash { + fp_btc_pk_hex: String + }, } #[cw_serde] From 065e1eff86574d1bd3d67c9c48048aabaa72e98d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 16 Sep 2024 09:58:37 +0200 Subject: [PATCH 10/51] Refactor: use staking queries, and batched active FPs processing --- contracts/babylon-finality/src/contract.rs | 109 ++++-------- contracts/babylon-finality/src/finality.rs | 154 ++++++++++------- contracts/babylon-finality/src/msg.rs | 1 - contracts/babylon-finality/src/queries.rs | 159 +----------------- .../babylon-finality/src/state/config.rs | 25 --- .../babylon-finality/src/state/finality.rs | 1 + .../src/state/public_randomness.rs | 1 + 7 files changed, 129 insertions(+), 321 deletions(-) diff --git a/contracts/babylon-finality/src/contract.rs b/contracts/babylon-finality/src/contract.rs index a4dd570d..55674139 100644 --- a/contracts/babylon-finality/src/contract.rs +++ b/contracts/babylon-finality/src/contract.rs @@ -1,10 +1,8 @@ -use babylon_contract::msg::btc_header::BtcHeaderResponse; - #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, - StdResult, + to_json_binary, Addr, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, QuerierWrapper, + QueryRequest, QueryResponse, Reply, Response, StdResult, WasmQuery, }; use cw2::set_contract_version; use cw_utils::{maybe_addr, nonpayable}; @@ -19,7 +17,7 @@ use crate::finality::{ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; use crate::{finality, queries, state}; -use babylon_contract::msg::contract::QueryMsg as BabylonQueryMsg; +use btc_staking::msg::ActivatedHeightResponse; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -36,6 +34,7 @@ pub fn instantiate( let config = Config { denom, babylon: info.sender, + staking: Addr::unchecked("staking"), // TODO: instantiate staking contract and set address in reply }; CONFIG.save(deps.storage, &config)?; @@ -61,37 +60,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&queries::config(deps)?)?), QueryMsg::Params {} => Ok(to_json_binary(&queries::params(deps)?)?), QueryMsg::Admin {} => to_json_binary(&ADMIN.query_admin(deps)?).map_err(Into::into), - QueryMsg::FinalityProvider { btc_pk_hex } => Ok(to_json_binary( - &queries::finality_provider(deps, btc_pk_hex)?, - )?), - QueryMsg::FinalityProviders { start_after, limit } => Ok(to_json_binary( - &queries::finality_providers(deps, start_after, limit)?, - )?), - QueryMsg::Delegation { - staking_tx_hash_hex, - } => Ok(to_json_binary(&queries::delegation( - deps, - staking_tx_hash_hex, - )?)?), - QueryMsg::Delegations { - start_after, - limit, - active, - } => Ok(to_json_binary(&queries::delegations( - deps, - start_after, - limit, - active, - )?)?), - QueryMsg::DelegationsByFP { btc_pk_hex } => Ok(to_json_binary( - &queries::delegations_by_fp(deps, btc_pk_hex)?, - )?), - QueryMsg::FinalityProviderInfo { btc_pk_hex, height } => Ok(to_json_binary( - &queries::finality_provider_info(deps, btc_pk_hex, height)?, - )?), - QueryMsg::FinalityProvidersByPower { start_after, limit } => Ok(to_json_binary( - &queries::finality_providers_by_power(deps, start_after, limit)?, - )?), QueryMsg::FinalitySignature { btc_pk_hex, height } => Ok(to_json_binary( &queries::finality_signature(deps, btc_pk_hex, height)?, )?), @@ -115,7 +83,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Ok(to_json_binary( &state::public_randomness::get_last_pub_rand_commit(deps.storage, &btc_pk_hex)?, )?), - QueryMsg::ActivatedHeight {} => Ok(to_json_binary(&queries::activated_height(deps)?)?), QueryMsg::Block { height } => Ok(to_json_binary(&queries::block(deps, height)?)?), QueryMsg::Blocks { start_after, @@ -153,20 +120,6 @@ pub fn execute( ExecuteMsg::UpdateAdmin { admin } => ADMIN .execute_update_admin(deps, info, maybe_addr(api, admin)?) .map_err(Into::into), - ExecuteMsg::BtcStaking { - new_fp, - active_del, - slashed_del, - unbonded_del, - } => handle_btc_staking( - deps, - env, - &info, - &new_fp, - &active_del, - &slashed_del, - &unbonded_del, - ), ExecuteMsg::SubmitFinalitySignature { fp_pubkey_hex, height, @@ -217,40 +170,13 @@ pub fn sudo( } fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result, ContractError> { - // Index BTC height at the current height - index_btc_height(deps, env.block.height)?; - // Compute active finality provider set let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize; - compute_active_finality_providers(deps.storage, env, max_active_fps)?; + compute_active_finality_providers(deps, env, max_active_fps)?; Ok(Response::new()) } -// index_btc_height indexes the current BTC height, and saves it to the state -fn index_btc_height(deps: &mut DepsMut, height: u64) -> Result<(), ContractError> { - // FIXME: Turn this into a hard error. Requires `babylon-contract` instance, and up and running - // BTC light client loop (which requires a running BTC node / simulator) - let btc_tip_height = get_btc_tip_height(deps) //?; - .ok() - .unwrap_or_default(); - - Ok(BTC_HEIGHT.save(deps.storage, height, &btc_tip_height)?) -} - -/// get_btc_tip_height queries the Babylon contract for the latest BTC tip height -fn get_btc_tip_height(deps: &DepsMut) -> Result { - // Get the BTC tip from the babylon contract through a raw query - let babylon_addr = CONFIG.load(deps.storage)?.babylon; - - // Query the Babylon contract - // TODO: use a raw query for performance / efficiency - let query_msg = BabylonQueryMsg::BtcTipHeader {}; - let tip: BtcHeaderResponse = deps.querier.query_wasm_smart(babylon_addr, &query_msg)?; - - Ok(tip.height) -} - fn handle_end_block( deps: &mut DepsMut, env: Env, @@ -259,8 +185,10 @@ fn handle_end_block( ) -> Result, ContractError> { // If the BTC staking protocol is activated i.e. there exists a height where at least one // finality provider has voting power, start indexing and tallying blocks + let cfg = CONFIG.load(deps.storage)?; let mut res = Response::new(); - if let Some(activated_height) = ACTIVATED_HEIGHT.may_load(deps.storage)? { + let activated_height = get_activated_height(&cfg.staking, &deps.querier)?; + if activated_height > 0 { // Index the current block let ev = finality::index_block(deps, env.block.height, &hex::decode(app_hash_hex)?)?; res = res.add_event(ev); @@ -271,6 +199,27 @@ fn handle_end_block( Ok(res) } +pub fn get_activated_height(staking_addr: &Addr, querier: &QuerierWrapper) -> StdResult { + // TODO: Use a raw query + let query = encode_smart_query( + staking_addr, + &btc_staking::msg::QueryMsg::ActivatedHeight {}, + )?; + let res: ActivatedHeightResponse = querier.query(&query)?; + Ok(res.height) +} + +pub(crate) fn encode_smart_query( + addr: &Addr, + msg: &btc_staking::msg::QueryMsg, +) -> StdResult> { + Ok(WasmQuery::Smart { + contract_addr: addr.to_string(), + msg: to_json_binary(&msg)?, + } + .into()) +} + #[cfg(test)] pub(crate) mod tests { use std::str::FromStr; diff --git a/contracts/babylon-finality/src/finality.rs b/contracts/babylon-finality/src/finality.rs index 654fc8af..d4ec7921 100644 --- a/contracts/babylon-finality/src/finality.rs +++ b/contracts/babylon-finality/src/finality.rs @@ -4,21 +4,23 @@ use k256::sha2::{Digest, Sha256}; use std::cmp::max; use std::collections::HashSet; -use cosmwasm_std::Order::Ascending; -use cosmwasm_std::{ - to_json_binary, DepsMut, Env, Event, Order, Response, StdResult, Storage, WasmMsg, -}; - +use crate::contract::encode_smart_query; use crate::error::ContractError; use crate::state::config::{CONFIG, PARAMS}; use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER}; use crate::state::public_randomness::{ get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES, }; +use babylon_apis::btc_staking_api::FinalityProvider; use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; use babylon_bindings::BabylonMsg; use babylon_merkle::Proof; -use btc_staking::msg::FinalityProviderInfo; +use btc_staking::msg::{FinalityProviderInfo, FinalityProvidersByPowerResponse}; +use cosmwasm_std::Order::Ascending; +use cosmwasm_std::{ + to_json_binary, Addr, DepsMut, Env, Event, QuerierWrapper, Response, StdResult, Storage, + WasmMsg, +}; pub fn handle_public_randomness_commit( deps: DepsMut, @@ -38,7 +40,9 @@ pub fn handle_public_randomness_commit( // Ensure the finality provider is registered if !deps.querier.query_wasm_smart( CONFIG.load(deps.storage)?.staking, - btc_staking::msg::QueryMsg::FinalityProvider {}, + &btc_staking::msg::QueryMsg::FinalityProvider { + btc_pk_hex: fp_pubkey_hex.to_string(), + }, )? { return Err(ContractError::FinalityProviderNotFound( fp_pubkey_hex.to_string(), @@ -129,8 +133,9 @@ pub fn handle_finality_signature( signature: &[u8], ) -> Result, ContractError> { // Ensure the finality provider exists - let fp = deps.querier.query_wasm_smart( - CONFIG.load(deps.storage)?.staking, + let staking_addr = CONFIG.load(deps.storage)?.staking; + let fp: FinalityProvider = deps.querier.query_wasm_smart( + staking_addr.clone(), &btc_staking::msg::QueryMsg::FinalityProvider { btc_pk_hex: fp_btc_pk_hex.to_string(), }, @@ -161,21 +166,17 @@ pub fn handle_finality_signature( } // Ensure the finality provider has voting power at this height - // if fps() - // .may_load_at_height(deps.storage, fp_btc_pk_hex, height)? - // .ok_or_else(|| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))? - // .power - // == 0 - if deps.querier.query_wasm_smart( - CONFIG.load(deps.storage)?.staking, - btc_staking::msg::QueryMsg::FinalityProvider {}, - )? { - return Err(ContractError::NoVotingPower( - fp_btc_pk_hex.to_string(), - height, - )); - } - { + let fp: FinalityProviderInfo = deps + .querier + .query_wasm_smart( + staking_addr.clone(), + &btc_staking::msg::QueryMsg::FinalityProviderInfo { + btc_pk_hex: fp_btc_pk_hex.to_string(), + height: Some(height), + }, + ) + .map_err(|_| ContractError::NoVotingPower(fp_btc_pk_hex.to_string(), height))?; + if fp.power == 0 { return Err(ContractError::NoVotingPower( fp_btc_pk_hex.to_string(), height, @@ -247,8 +248,13 @@ pub fn handle_finality_signature( evidence.canonical_finality_sig = canonical_sig; // Slash this finality provider, including setting its voting power to zero, extracting // its BTC SK, and emitting an event - let (msg, ev) = slash_finality_provider(&mut deps, env, fp_btc_pk_hex, &evidence)?; - res = res.add_message(msg); + let (msgs, ev) = slash_finality_provider( + &mut deps, + &staking_addr.clone(), + fp_btc_pk_hex, + &evidence, + )?; + res = res.add_messages(msgs); res = res.add_event(ev); } // TODO?: Also slash if this finality provider has signed another fork before @@ -276,8 +282,9 @@ pub fn handle_finality_signature( // Slash this finality provider, including setting its voting power to zero, extracting its // BTC SK, and emitting an event - let (msg, ev) = slash_finality_provider(&mut deps, env, fp_btc_pk_hex, &evidence)?; - res = res.add_message(msg); + let (msgs, ev) = + slash_finality_provider(&mut deps, &staking_addr, fp_btc_pk_hex, &evidence)?; + res = res.add_messages(msgs); res = res.add_event(ev); } @@ -288,13 +295,21 @@ pub fn handle_finality_signature( /// its voting power to zero, extracting its BTC SK, and emitting an event fn slash_finality_provider( deps: &mut DepsMut, - env: Env, + staking_addr: &Addr, fp_btc_pk_hex: &str, evidence: &Evidence, -) -> Result<(WasmMsg, Event), ContractError> { +) -> Result<(Vec, Event), ContractError> { + let mut wasm_msgs = vec![]; // Slash this finality provider, i.e., set its slashing height to the block height - staking::slash_finality_provider(deps, env, fp_btc_pk_hex, evidence.block_height) - .map_err(|err| ContractError::FailedToSlashFinalityProvider(err.to_string()))?; + let msg = btc_staking::msg::ExecuteMsg::Slash { + fp_btc_pk_hex: fp_btc_pk_hex.to_string(), + }; + let staking_msg = WasmMsg::Execute { + contract_addr: staking_addr.to_string(), + msg: to_json_binary(&msg)?, + funds: vec![], + }; + wasm_msgs.push(staking_msg); // Extract BTC SK using the evidence let pk = eots::PublicKey::from_hex(fp_btc_pk_hex)?; @@ -317,11 +332,12 @@ fn slash_finality_provider( let babylon_addr = CONFIG.load(deps.storage)?.babylon; - let wasm_msg = WasmMsg::Execute { + let babylon_msg = WasmMsg::Execute { contract_addr: babylon_addr.to_string(), msg: to_json_binary(&msg)?, funds: vec![], }; + wasm_msgs.push(babylon_msg); let ev = Event::new("slashed_finality_provider") .add_attribute("module", "finality") @@ -341,7 +357,7 @@ fn slash_finality_provider( hex::encode(&evidence.fork_finality_sig), ) .add_attribute("secret_key", hex::encode(btc_sk.to_bytes())); - Ok((wasm_msg, ev)) + Ok((wasm_msgs, ev)) } /// Verifies the finality signature message w.r.t. the public randomness commitment: @@ -526,47 +542,67 @@ fn finalize_block( Ok(ev) } +const QUERY_LIMIT: Option = Some(30); + /// `compute_active_finality_providers` sorts all finality providers, counts the total voting /// power of top finality providers, and records them in the contract state pub fn compute_active_finality_providers( - storage: &mut dyn Storage, + deps: &mut DepsMut, env: Env, max_active_fps: usize, ) -> Result<(), ContractError> { - // Sort finality providers by power - let (finality_providers, running_total): (_, Vec<_>) = fps() - .idx - .power - .range(storage, None, None, Order::Descending) - .take(max_active_fps) - .scan(0u64, |acc, item| { - let (pk_hex, fp_state) = item.ok()?; // Error ends the iteration - - let fp_info = FinalityProviderInfo { - btc_pk_hex: pk_hex, - power: fp_state.power, - }; - *acc += fp_state.power; - Some((fp_info, *acc)) - }) - .filter(|(fp, _)| { - // Filter out FPs with no voting power - fp.power > 0 - }) - .unzip(); + let cfg = CONFIG.load(deps.storage)?; + // Get all finality providers from the staking contract, filtered + let mut batch = list_fps_by_power(&cfg.staking, &deps.querier, None, QUERY_LIMIT)?; + + let mut finality_providers = vec![]; + let mut total_power: u64 = 0; + while !batch.is_empty() && finality_providers.len() < max_active_fps { + let last = batch.last().cloned(); + + let (filtered, running_total): (Vec<_>, Vec<_>) = batch + .into_iter() + .filter(|fp| { + // Filter out FPs with no voting power + fp.power > 0 + }) + .scan(total_power, |acc, fp| { + *acc += fp.power; + Some((fp, *acc)) + }) + .unzip(); + finality_providers.extend_from_slice(&filtered); + total_power = running_total.last().copied().unwrap_or_default(); + + // and get the next page + batch = list_fps_by_power(&cfg.staking, &deps.querier, last, QUERY_LIMIT)?; + } // TODO: Online FPs verification // TODO: Filter out slashed / offline / jailed FPs // Save the new set of active finality providers // TODO: Purge old (height - finality depth) FP_SET entries to avoid bloating the storage - FP_SET.save(storage, env.block.height, &finality_providers)?; + FP_SET.save(deps.storage, env.block.height, &finality_providers)?; // Save the total voting power of the top n finality providers - let total_power = running_total.last().copied().unwrap_or_default(); - TOTAL_POWER.save(storage, &total_power)?; + TOTAL_POWER.save(deps.storage, &total_power)?; Ok(()) } +pub fn list_fps_by_power( + staking_addr: &Addr, + querier: &QuerierWrapper, + start_after: Option, + limit: Option, +) -> StdResult> { + let query = encode_smart_query( + staking_addr, + &btc_staking::msg::QueryMsg::FinalityProvidersByPower { start_after, limit }, + )?; + let res: FinalityProvidersByPowerResponse = querier.query(&query)?; + Ok(res.fps) +} + #[cfg(test)] mod tests { use crate::contract::instantiate; diff --git a/contracts/babylon-finality/src/msg.rs b/contracts/babylon-finality/src/msg.rs index 891500f9..ff521ed0 100644 --- a/contracts/babylon-finality/src/msg.rs +++ b/contracts/babylon-finality/src/msg.rs @@ -1,7 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cw_controllers::AdminResponse; -use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; use crate::state::config::{Config, Params}; diff --git a/contracts/babylon-finality/src/queries.rs b/contracts/babylon-finality/src/queries.rs index 1f8dcffa..dbcedd69 100644 --- a/contracts/babylon-finality/src/queries.rs +++ b/contracts/babylon-finality/src/queries.rs @@ -1,20 +1,14 @@ -use std::str::FromStr; - -use bitcoin::hashes::Hash; -use bitcoin::Txid; - use cosmwasm_std::Order::{Ascending, Descending}; -use cosmwasm_std::{Deps, Order, StdResult}; +use cosmwasm_std::{Deps, StdResult}; use cw_storage_plus::Bound; -use babylon_apis::btc_staking_api::FinalityProvider; use babylon_apis::finality_api::IndexedBlock; use crate::error::ContractError; use crate::msg::{BlocksResponse, EvidenceResponse, FinalitySignatureResponse}; use crate::state::config::{Config, Params}; use crate::state::config::{CONFIG, PARAMS}; -use crate::state::finality::{BLOCKS, EVIDENCES}; +use crate::state::finality::{BLOCKS, EVIDENCES, SIGNATURES}; pub fn config(deps: Deps) -> StdResult { CONFIG.load(deps.storage) @@ -24,156 +18,16 @@ pub fn params(deps: Deps) -> StdResult { PARAMS.load(deps.storage) } -pub fn finality_provider(deps: Deps, btc_pk_hex: String) -> StdResult { - FPS.load(deps.storage, &btc_pk_hex) -} - // Settings for pagination const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; -pub fn finality_providers( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_after = start_after.as_ref().map(|s| Bound::exclusive(&**s)); - let fps = FPS - .range_raw(deps.storage, start_after, None, Order::Ascending) - .take(limit) - .map(|item| item.map(|(_, v)| v)) - .collect::>>()?; - Ok(FinalityProvidersResponse { fps }) -} - -/// Get the delegation info by staking tx hash. -/// `staking_tx_hash_hex`: The (reversed) staking tx hash, in hex -pub fn delegation(deps: Deps, staking_tx_hash_hex: String) -> Result { - let staking_tx_hash = Txid::from_str(&staking_tx_hash_hex)?; - Ok(DELEGATIONS.load(deps.storage, staking_tx_hash.as_ref())?) -} - -/// Get list of delegations. -/// `start_after`: The (reversed) associated staking tx hash of the delegation in hex, if provided. -/// `active`: List only active delegations if true, otherwise list all delegations. -pub fn delegations( - deps: Deps, - start_after: Option, - limit: Option, - active: Option, -) -> Result { - let active = active.unwrap_or_default(); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_after = start_after - .as_ref() - .map(|s| Txid::from_str(s)) - .transpose()?; - let start_after = start_after.as_ref().map(|s| s.as_ref()); - let start_after = start_after.map(Bound::exclusive); - let delegations = DELEGATIONS - .range_raw(deps.storage, start_after, None, Order::Ascending) - .filter(|item| { - if let Ok((_, del)) = item { - !active || del.is_active() - } else { - true // don't filter errors - } - }) - .take(limit) - .map(|item| item.map(|(_, v)| v)) - .collect::, _>>()?; - Ok(BtcDelegationsResponse { delegations }) -} - -/// Delegation hashes by FP query. -/// -/// `btc_pk_hex`: The BTC public key of the finality provider, in hex -pub fn delegations_by_fp( - deps: Deps, - btc_pk_hex: String, -) -> Result { - let tx_hashes = FP_DELEGATIONS.load(deps.storage, &btc_pk_hex)?; - let tx_hashes = tx_hashes - .iter() - .map(|h| Ok(Txid::from_slice(h)?.to_string())) - .collect::>()?; - Ok(DelegationsByFPResponse { hashes: tx_hashes }) -} - -/// Active / all delegations by FP convenience query. -/// -/// This is an alternative to `delegations_by_fp` that returns the actual delegations instead of -/// just the hashes. -/// -/// `btc_pk_hex`: The BTC public key of the finality provider, in hex. -/// `active` is a filter to return only active delegations -pub fn active_delegations_by_fp( - deps: Deps, - btc_pk_hex: String, - active: bool, -) -> Result { - let tx_hashes = FP_DELEGATIONS.load(deps.storage, &btc_pk_hex)?; - let delegations = tx_hashes - .iter() - .map(|h| Ok(DELEGATIONS.load(deps.storage, Txid::from_slice(h)?.as_ref())?)) - .filter(|item| { - if let Ok(del) = item { - !active || del.is_active() - } else { - true // don't filter errors - } - }) - .collect::, ContractError>>()?; - Ok(BtcDelegationsResponse { delegations }) -} - -pub fn finality_provider_info( - deps: Deps, - btc_pk_hex: String, - height: Option, -) -> StdResult { - let fp_state = match height { - Some(h) => fps().may_load_at_height(deps.storage, &btc_pk_hex, h), - None => fps().may_load(deps.storage, &btc_pk_hex), - }? - .unwrap_or_default(); - - Ok(FinalityProviderInfo { - btc_pk_hex, - power: fp_state.power, - }) -} - -pub fn finality_providers_by_power( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.map(|fpp| Bound::exclusive((fpp.power, fpp.btc_pk_hex.clone()))); - let fps = fps() - .idx - .power - .range(deps.storage, None, start, Descending) - .take(limit) - .map(|item| { - let (btc_pk_hex, FinalityProviderState { power }) = item?; - Ok(FinalityProviderInfo { btc_pk_hex, power }) - }) - .collect::>>()?; - - Ok(FinalityProvidersByPowerResponse { fps }) -} - pub fn finality_signature( deps: Deps, btc_pk_hex: String, height: u64, ) -> StdResult { - match babylon_finality::state::finality::SIGNATURES - .may_load(deps.storage, (height, &btc_pk_hex))? - { + match SIGNATURES.may_load(deps.storage, (height, &btc_pk_hex))? { Some(sig) => Ok(FinalitySignatureResponse { signature: sig }), None => Ok(FinalitySignatureResponse { signature: Vec::new(), @@ -181,13 +35,6 @@ pub fn finality_signature( } } -pub fn activated_height(deps: Deps) -> Result { - let activated_height = ACTIVATED_HEIGHT.may_load(deps.storage)?.unwrap_or_default(); - Ok(ActivatedHeightResponse { - height: activated_height, - }) -} - pub fn block(deps: Deps, height: u64) -> StdResult { BLOCKS.load(deps.storage, height) } diff --git a/contracts/babylon-finality/src/state/config.rs b/contracts/babylon-finality/src/state/config.rs index 113d6790..6755a032 100644 --- a/contracts/babylon-finality/src/state/config.rs +++ b/contracts/babylon-finality/src/state/config.rs @@ -6,8 +6,6 @@ use cosmwasm_std::Addr; use cw_controllers::Admin; use cw_storage_plus::Item; -use babylon_bitcoin::chain_params::Network; - pub(crate) const CONFIG: Item = Item::new("config"); pub(crate) const PARAMS: Item = Item::new("params"); /// Storage for admin @@ -27,17 +25,6 @@ pub struct Config { #[derive(Derivative)] #[derivative(Default)] pub struct Params { - // covenant_pks is the list of public keys held by the covenant committee each PK - // follows encoding in BIP-340 spec on Bitcoin - pub covenant_pks: Vec, - // covenant_quorum is the minimum number of signatures needed for the covenant multi-signature - pub covenant_quorum: u32, - #[derivative(Default(value = "Network::Regtest"))] - // ntc_network is the network the BTC staking protocol is running on - pub btc_network: Network, - // `min_commission_rate` is the chain-wide minimum commission rate that a finality provider - // can charge their delegators - // pub min_commission_rate: Decimal, /// `max_active_finality_providers` is the maximum number of active finality providers in the /// BTC staking protocol #[derivative(Default(value = "100"))] @@ -46,16 +33,4 @@ pub struct Params { /// should commit #[derivative(Default(value = "1"))] pub min_pub_rand: u64, - /// `slashing_address` is the address that the slashed BTC goes to. - /// The address is in string format on Bitcoin. - #[derivative(Default(value = "String::from(\"n4cV57jePmAAue2WTTBQzH3k3R2rgWBQwY\")"))] - pub slashing_address: String, - /// `min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for - /// the pre-signed slashing tx - #[derivative(Default(value = "1000"))] - pub min_slashing_tx_fee_sat: u64, - /// `slashing_rate` determines the portion of the staked amount to be slashed, - /// expressed as a decimal (e.g. 0.5 for 50%). - #[derivative(Default(value = "String::from(\"0.1\")"))] - pub slashing_rate: String, } diff --git a/contracts/babylon-finality/src/state/finality.rs b/contracts/babylon-finality/src/state/finality.rs index 42dcb02d..06202abf 100644 --- a/contracts/babylon-finality/src/state/finality.rs +++ b/contracts/babylon-finality/src/state/finality.rs @@ -1,6 +1,7 @@ use cw_storage_plus::{Item, Map}; use babylon_apis::finality_api::{Evidence, IndexedBlock}; +use btc_staking::msg::FinalityProviderInfo; /// Map of signatures by block height and FP pub const SIGNATURES: Map<(u64, &str), Vec> = Map::new("fp_sigs"); diff --git a/contracts/babylon-finality/src/state/public_randomness.rs b/contracts/babylon-finality/src/state/public_randomness.rs index 40862755..68227626 100644 --- a/contracts/babylon-finality/src/state/public_randomness.rs +++ b/contracts/babylon-finality/src/state/public_randomness.rs @@ -2,6 +2,7 @@ use cosmwasm_std::Order::{Ascending, Descending}; use cosmwasm_std::{StdResult, Storage}; use cw_storage_plus::{Bound, Map}; +use crate::error::ContractError; use babylon_apis::finality_api::PubRandCommit; /// Map of public randomness commitments by fp and block height From b09b5427c30451e033911cb97751369a70105673 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 16 Sep 2024 10:29:45 +0200 Subject: [PATCH 11/51] cargo fmt --- packages/apis/src/btc_staking_api.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index d83c4ee3..eb6a7faf 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -23,9 +23,7 @@ pub enum ExecuteMsg { /// Used by the finality contract only. /// The finality contract will call this message to slash the finality provider's staking power /// when the finality provider is found to be malicious - Slash { - fp_btc_pk_hex: String - }, + Slash { fp_btc_pk_hex: String }, } #[cw_serde] From 262b4a4d0687f0fb3b997ef560ee5c2963006399 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 10:52:59 +0200 Subject: [PATCH 12/51] Add babylon contract version to store in passing --- Cargo.lock | 1 + contracts/babylon/Cargo.toml | 1 + contracts/babylon/src/contract.rs | 7 ++++++- contracts/babylon/src/msg/contract.rs | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3108eebd..793365bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw-utils", + "cw2", "derivative", "hex", "ics23", diff --git a/contracts/babylon/Cargo.toml b/contracts/babylon/Cargo.toml index 12588565..d499205c 100644 --- a/contracts/babylon/Cargo.toml +++ b/contracts/babylon/Cargo.toml @@ -37,6 +37,7 @@ babylon-bitcoin = { path = "../../packages/bitcoin" } blst = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw2 = { workspace = true } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } hex = { workspace = true } diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 2f6b3bf3..e1f6a41b 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, }; +use cw2::set_contract_version; use cw_utils::ParseReplyError; use crate::ibc::{ibc_packet, IBC_CHANNEL}; @@ -12,7 +13,10 @@ use crate::state::btc_light_client; use crate::state::config::{Config, CONFIG}; use babylon_bindings::BabylonMsg; -const REPLY_ID_INSTANTIATE: u64 = 1; +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const REPLY_ID_INSTANTIATE: u64 = 2; /// When we instantiate the Babylon contract, it will optionally instantiate a BTC staking /// contract – if its code id is provided – to work with it for BTC re-staking support, @@ -61,6 +65,7 @@ pub fn instantiate( // Save the config after potentially updating it CONFIG.save(deps.storage, &cfg)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(res) } diff --git a/contracts/babylon/src/msg/contract.rs b/contracts/babylon/src/msg/contract.rs index 24d30b95..2dec28b2 100644 --- a/contracts/babylon/src/msg/contract.rs +++ b/contracts/babylon/src/msg/contract.rs @@ -27,7 +27,7 @@ pub struct InstantiateMsg { pub notify_cosmos_zone: bool, /// If set, this will instantiate a BTC staking contract for BTC re-staking pub btc_staking_code_id: Option, - /// If set, this will define the instantiate message for the BTC staking contract. + /// If set, this will define the instantiation message for the BTC staking contract. /// This message is opaque to the Babylon contract, and depends on the specific staking contract /// being instantiated pub btc_staking_msg: Option, From 63cdd1f24cf1979433a39b84984ff0c7c095bb8f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 12:05:02 +0200 Subject: [PATCH 13/51] Add finality contract instantiation --- contracts/babylon/src/contract.rs | 78 ++++++++++++++++++--------- contracts/babylon/src/msg/contract.rs | 15 ++++-- contracts/babylon/src/state/config.rs | 9 ++-- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index e1f6a41b..c3833715 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -16,7 +16,8 @@ use babylon_bindings::BabylonMsg; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const REPLY_ID_INSTANTIATE: u64 = 2; +const REPLY_ID_INSTANTIATE_STAKING: u64 = 2; +const REPLY_ID_INSTANTIATE_FINALITY: u64 = 3; /// When we instantiate the Babylon contract, it will optionally instantiate a BTC staking /// contract – if its code id is provided – to work with it for BTC re-staking support, @@ -38,6 +39,7 @@ pub fn instantiate( checkpoint_finalization_timeout: msg.checkpoint_finalization_timeout, notify_cosmos_zone: msg.notify_cosmos_zone, btc_staking: None, // Will be set in `reply` if `btc_staking_code_id` is provided + btc_finality: None, // Will be set in `reply` if `btc_finality_code_id` is provided consumer_name: None, consumer_description: None, }; @@ -51,13 +53,27 @@ pub fn instantiate( // Instantiate BTC staking contract let init_msg = WasmMsg::Instantiate { - admin: msg.admin, + admin: msg.admin.clone(), code_id: btc_staking_code_id, msg: msg.btc_staking_msg.unwrap_or(Binary::from(b"{}")), funds: vec![], label: "BTC Staking".into(), }; - let init_msg = SubMsg::reply_on_success(init_msg, REPLY_ID_INSTANTIATE); + let init_msg = SubMsg::reply_on_success(init_msg, REPLY_ID_INSTANTIATE_STAKING); + + res = res.add_submessage(init_msg); + } + + if let Some(btc_finality_code_id) = msg.btc_finality_code_id { + // Instantiate BTC finality contract + let init_msg = WasmMsg::Instantiate { + admin: msg.admin, + code_id: btc_finality_code_id, + msg: msg.btc_finality_msg.unwrap_or(Binary::from(b"{}")), + funds: vec![], + label: "BTC Finality".into(), + }; + let init_msg = SubMsg::reply_on_success(init_msg, REPLY_ID_INSTANTIATE_FINALITY); res = res.add_submessage(init_msg); } @@ -75,46 +91,56 @@ pub fn reply( reply: Reply, ) -> Result, ContractError> { match reply.id { - REPLY_ID_INSTANTIATE => reply_init_callback(deps, reply.result.unwrap()), + REPLY_ID_INSTANTIATE_STAKING => reply_init_callback_staking(deps, reply.result.unwrap()), + REPLY_ID_INSTANTIATE_FINALITY => reply_init_finality_callback(deps, reply.result.unwrap()), _ => Err(ContractError::InvalidReplyId(reply.id)), } } -/// Store virtual BTC staking address -fn reply_init_callback( - deps: DepsMut, - reply: SubMsgResponse, -) -> Result, ContractError> { - // Try to get contract address from events in reply +/// Tries to get contract address from events in reply +fn reply_init_get_contract_address(reply: SubMsgResponse) -> Result { for event in reply.events { if event.ty == "instantiate" { for attr in event.attributes { if attr.key == "_contract_address" { - let btc_staking = Addr::unchecked(attr.value); - CONFIG.update(deps.storage, |mut cfg| { - cfg.btc_staking = Some(btc_staking.clone()); - Ok::<_, ContractError>(cfg) - })?; - return Ok(Response::new()); + return Ok(Addr::unchecked(attr.value)); } } } } - // Fall back to deprecated way of getting contract address from data - // TODO: Remove this if the method above works - // TODO: Use the new `msg_responses` field if / when available - // let init_data = parse_instantiate_response_data(&reply.data.unwrap())?; - // let btc_staking = Addr::unchecked(init_data.contract_address); - // CONFIG.update(deps.storage, |mut cfg| { - // cfg.btc_staking = Some(btc_staking.clone()); - // Ok::<_, ContractError>(cfg) - // })?; - // Ok(Response::new()) Err(ContractError::ParseReply(ParseReplyError::ParseFailure( "Cannot parse contract address".to_string(), ))) } +/// Store BTC staking address +fn reply_init_callback_staking( + deps: DepsMut, + reply: SubMsgResponse, +) -> Result, ContractError> { + // Try to get contract address from events in reply + let addr = reply_init_get_contract_address(reply)?; + CONFIG.update(deps.storage, |mut cfg| { + cfg.btc_staking = Some(addr); + Ok::<_, ContractError>(cfg) + })?; + Ok(Response::new()) +} + +/// Store BTC finality address +fn reply_init_finality_callback( + deps: DepsMut, + reply: SubMsgResponse, +) -> Result, ContractError> { + // Try to get contract address from events in reply + let addr = reply_init_get_contract_address(reply)?; + CONFIG.update(deps.storage, |mut cfg| { + cfg.btc_finality = Some(addr); + Ok::<_, ContractError>(cfg) + })?; + Ok(Response::new()) +} + pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { QueryMsg::Config {} => Ok(to_json_binary(&queries::config(deps)?)?), diff --git a/contracts/babylon/src/msg/contract.rs b/contracts/babylon/src/msg/contract.rs index 2dec28b2..7093e56a 100644 --- a/contracts/babylon/src/msg/contract.rs +++ b/contracts/babylon/src/msg/contract.rs @@ -22,8 +22,10 @@ pub struct InstantiateMsg { pub babylon_tag: String, pub btc_confirmation_depth: u64, pub checkpoint_finalization_timeout: u64, - // notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers - // NOTE: if set true, then the Cosmos zone needs to integrate the corresponding message handler as well + /// notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised + /// headers. + /// NOTE: If set to true, then the Cosmos zone needs to integrate the corresponding message handler + /// as well pub notify_cosmos_zone: bool, /// If set, this will instantiate a BTC staking contract for BTC re-staking pub btc_staking_code_id: Option, @@ -31,7 +33,14 @@ pub struct InstantiateMsg { /// This message is opaque to the Babylon contract, and depends on the specific staking contract /// being instantiated pub btc_staking_msg: Option, - /// If set, this will be the Wasm migration / upgrade admin of the BTC staking contract + /// If set, this will instantiate a BTC finality contract + pub btc_finality_code_id: Option, + /// If set, this will define the instantiation message for the BTC finality contract. + /// This message is opaque to the Babylon contract, and depends on the specific finality contract + /// being instantiated + pub btc_finality_msg: Option, + /// If set, this will be the Wasm migration / upgrade admin of the BTC staking contract and the + /// BTC finality contract pub admin: Option, /// Name of the consumer pub consumer_name: Option, diff --git a/contracts/babylon/src/state/config.rs b/contracts/babylon/src/state/config.rs index c0c2c726..87cb70c9 100644 --- a/contracts/babylon/src/state/config.rs +++ b/contracts/babylon/src/state/config.rs @@ -11,12 +11,15 @@ pub struct Config { pub babylon_tag: Vec, pub btc_confirmation_depth: u64, pub checkpoint_finalization_timeout: u64, - // notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers - // NOTE: if set true, then the Cosmos zone needs to integrate the corresponding message handler as well + /// notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. + /// NOTE: if set to true, then the Cosmos zone needs to integrate the corresponding message + /// handler as well pub notify_cosmos_zone: bool, /// If set, this stores a BTC staking contract used for BTC re-staking pub btc_staking: Option, - /// Consumer name + /// If set, this stores a BTC finality contract used for BTC finality on the Consumer + pub btc_finality: Option, + /// Consumer name pub consumer_name: Option, /// Consumer description pub consumer_description: Option, From 4b6bd78499260f31a6adb8fe09b3df49b7841a9f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 12:05:23 +0200 Subject: [PATCH 14/51] Remove finality dependendency --- contracts/btc-staking/src/contract.rs | 3 +-- contracts/btc-staking/src/staking.rs | 2 +- contracts/btc-staking/src/state/config.rs | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 7b5ec3d8..a9abe58c 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, + to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, StdResult, }; use cw2::set_contract_version; @@ -30,7 +30,6 @@ pub fn instantiate( let config = Config { denom, babylon: info.sender, - finality: Addr::unchecked(""), // TODO: Instantiate finality contract and set address in reply handler }; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index a988ceae..a22d6ad7 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -321,7 +321,7 @@ pub fn handle_slash_fp( fp_btc_pk_hex: &str, ) -> Result, ContractError> { let config = CONFIG.load(deps.storage)?; - if info.sender != config.finality && !ADMIN.is_admin(deps.as_ref(), &info.sender)? { + if info.sender != config.babylon && !ADMIN.is_admin(deps.as_ref(), &info.sender)? { return Err(ContractError::Unauthorized); } slash_finality_provider(deps, env, fp_btc_pk_hex) diff --git a/contracts/btc-staking/src/state/config.rs b/contracts/btc-staking/src/state/config.rs index 2bfa7111..29912e2b 100644 --- a/contracts/btc-staking/src/state/config.rs +++ b/contracts/btc-staking/src/state/config.rs @@ -17,12 +17,6 @@ pub(crate) const ADMIN: Admin = Admin::new("admin"); pub struct Config { pub denom: String, pub babylon: Addr, - pub finality: Addr, - // covenant_pks is the list of public keys held by the covenant committee each PK - // follows encoding in BIP-340 spec on Bitcoin - // pub covenant_pks: Vec, - // covenant_quorum is the minimum number of signatures needed for the covenant multi-signature - // pub covenant_quorum: u32, } /// Params define Consumer-selectable BTC staking parameters From feed346856fc6303d5fa1d89abd95e56a3354c6d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 12:09:51 +0200 Subject: [PATCH 15/51] Rename to btc-finality for consistency --- Cargo.lock | 58 +++++++++---------- .../Cargo.toml | 2 +- .../src/bin/schema.rs | 0 .../src/contract.rs | 0 .../src/error.rs | 0 .../src/finality.rs | 0 .../src/lib.rs | 0 .../src/msg.rs | 4 +- .../src/queries.rs | 0 .../src/state/config.rs | 0 .../src/state/finality.rs | 0 .../src/state/mod.rs | 0 .../src/state/public_randomness.rs | 0 13 files changed, 32 insertions(+), 32 deletions(-) rename contracts/{babylon-finality => btc-finality}/Cargo.toml (98%) rename contracts/{babylon-finality => btc-finality}/src/bin/schema.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/contract.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/error.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/finality.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/lib.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/msg.rs (96%) rename contracts/{babylon-finality => btc-finality}/src/queries.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/state/config.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/state/finality.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/state/mod.rs (100%) rename contracts/{babylon-finality => btc-finality}/src/state/public_randomness.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 793365bb..531e6981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,35 +181,6 @@ dependencies = [ "thousands", ] -[[package]] -name = "babylon-finality" -version = "0.9.0" -dependencies = [ - "babylon-apis", - "babylon-bindings", - "babylon-bitcoin", - "babylon-btcstaking", - "babylon-contract", - "babylon-merkle", - "babylon-proto", - "bitcoin", - "btc-staking", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-vm", - "cw-controllers", - "cw-storage-plus", - "cw-utils", - "cw2", - "derivative", - "eots", - "hex", - "k256", - "prost 0.11.9", - "test-utils", - "thiserror", -] - [[package]] name = "babylon-merkle" version = "0.9.0" @@ -407,6 +378,35 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "btc-finality" +version = "0.9.0" +dependencies = [ + "babylon-apis", + "babylon-bindings", + "babylon-bitcoin", + "babylon-btcstaking", + "babylon-contract", + "babylon-merkle", + "babylon-proto", + "bitcoin", + "btc-staking", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-vm", + "cw-controllers", + "cw-storage-plus", + "cw-utils", + "cw2", + "derivative", + "eots", + "hex", + "k256", + "prost 0.11.9", + "test-utils", + "thiserror", +] + [[package]] name = "btc-staking" version = "0.9.0" diff --git a/contracts/babylon-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml similarity index 98% rename from contracts/babylon-finality/Cargo.toml rename to contracts/btc-finality/Cargo.toml index cee30c62..f7c107d8 100644 --- a/contracts/babylon-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "babylon-finality" +name = "btc-finality" edition.workspace = true version.workspace = true license.workspace = true diff --git a/contracts/babylon-finality/src/bin/schema.rs b/contracts/btc-finality/src/bin/schema.rs similarity index 100% rename from contracts/babylon-finality/src/bin/schema.rs rename to contracts/btc-finality/src/bin/schema.rs diff --git a/contracts/babylon-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs similarity index 100% rename from contracts/babylon-finality/src/contract.rs rename to contracts/btc-finality/src/contract.rs diff --git a/contracts/babylon-finality/src/error.rs b/contracts/btc-finality/src/error.rs similarity index 100% rename from contracts/babylon-finality/src/error.rs rename to contracts/btc-finality/src/error.rs diff --git a/contracts/babylon-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs similarity index 100% rename from contracts/babylon-finality/src/finality.rs rename to contracts/btc-finality/src/finality.rs diff --git a/contracts/babylon-finality/src/lib.rs b/contracts/btc-finality/src/lib.rs similarity index 100% rename from contracts/babylon-finality/src/lib.rs rename to contracts/btc-finality/src/lib.rs diff --git a/contracts/babylon-finality/src/msg.rs b/contracts/btc-finality/src/msg.rs similarity index 96% rename from contracts/babylon-finality/src/msg.rs rename to contracts/btc-finality/src/msg.rs index ff521ed0..8210420b 100644 --- a/contracts/babylon-finality/src/msg.rs +++ b/contracts/btc-finality/src/msg.rs @@ -17,10 +17,10 @@ pub type ExecuteMsg = babylon_apis::finality_api::ExecuteMsg; #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// `Config` returns the current configuration of the babylon-finality contract + /// `Config` returns the current configuration of the btc-finality contract #[returns(Config)] Config {}, - /// `Params` returns the current Consumer-specific parameters of the babylon-finality contract + /// `Params` returns the current Consumer-specific parameters of the btc-finality contract #[returns(Params)] Params {}, /// `Admin` returns the current admin of the contract diff --git a/contracts/babylon-finality/src/queries.rs b/contracts/btc-finality/src/queries.rs similarity index 100% rename from contracts/babylon-finality/src/queries.rs rename to contracts/btc-finality/src/queries.rs diff --git a/contracts/babylon-finality/src/state/config.rs b/contracts/btc-finality/src/state/config.rs similarity index 100% rename from contracts/babylon-finality/src/state/config.rs rename to contracts/btc-finality/src/state/config.rs diff --git a/contracts/babylon-finality/src/state/finality.rs b/contracts/btc-finality/src/state/finality.rs similarity index 100% rename from contracts/babylon-finality/src/state/finality.rs rename to contracts/btc-finality/src/state/finality.rs diff --git a/contracts/babylon-finality/src/state/mod.rs b/contracts/btc-finality/src/state/mod.rs similarity index 100% rename from contracts/babylon-finality/src/state/mod.rs rename to contracts/btc-finality/src/state/mod.rs diff --git a/contracts/babylon-finality/src/state/public_randomness.rs b/contracts/btc-finality/src/state/public_randomness.rs similarity index 100% rename from contracts/babylon-finality/src/state/public_randomness.rs rename to contracts/btc-finality/src/state/public_randomness.rs From 246ea4b833fec910cb11c7e54e85c10c8c89024c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 12:29:41 +0200 Subject: [PATCH 16/51] Refactor: route slashing through babylon only --- contracts/btc-finality/src/finality.rs | 35 ++++++-------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index d4ec7921..7b3ae629 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -248,13 +248,8 @@ pub fn handle_finality_signature( evidence.canonical_finality_sig = canonical_sig; // Slash this finality provider, including setting its voting power to zero, extracting // its BTC SK, and emitting an event - let (msgs, ev) = slash_finality_provider( - &mut deps, - &staking_addr.clone(), - fp_btc_pk_hex, - &evidence, - )?; - res = res.add_messages(msgs); + let (msg, ev) = slash_finality_provider(&mut deps, fp_btc_pk_hex, &evidence)?; + res = res.add_message(msg); res = res.add_event(ev); } // TODO?: Also slash if this finality provider has signed another fork before @@ -282,9 +277,8 @@ pub fn handle_finality_signature( // Slash this finality provider, including setting its voting power to zero, extracting its // BTC SK, and emitting an event - let (msgs, ev) = - slash_finality_provider(&mut deps, &staking_addr, fp_btc_pk_hex, &evidence)?; - res = res.add_messages(msgs); + let (msg, ev) = slash_finality_provider(&mut deps, fp_btc_pk_hex, &evidence)?; + res = res.add_message(msg); res = res.add_event(ev); } @@ -295,23 +289,9 @@ pub fn handle_finality_signature( /// its voting power to zero, extracting its BTC SK, and emitting an event fn slash_finality_provider( deps: &mut DepsMut, - staking_addr: &Addr, fp_btc_pk_hex: &str, evidence: &Evidence, -) -> Result<(Vec, Event), ContractError> { - let mut wasm_msgs = vec![]; - // Slash this finality provider, i.e., set its slashing height to the block height - let msg = btc_staking::msg::ExecuteMsg::Slash { - fp_btc_pk_hex: fp_btc_pk_hex.to_string(), - }; - let staking_msg = WasmMsg::Execute { - contract_addr: staking_addr.to_string(), - msg: to_json_binary(&msg)?, - funds: vec![], - }; - wasm_msgs.push(staking_msg); - - // Extract BTC SK using the evidence +) -> Result<(WasmMsg, Event), ContractError> { let pk = eots::PublicKey::from_hex(fp_btc_pk_hex)?; let btc_sk = pk .extract_secret_key( @@ -332,12 +312,11 @@ fn slash_finality_provider( let babylon_addr = CONFIG.load(deps.storage)?.babylon; - let babylon_msg = WasmMsg::Execute { + let wasm_msg = WasmMsg::Execute { contract_addr: babylon_addr.to_string(), msg: to_json_binary(&msg)?, funds: vec![], }; - wasm_msgs.push(babylon_msg); let ev = Event::new("slashed_finality_provider") .add_attribute("module", "finality") @@ -357,7 +336,7 @@ fn slash_finality_provider( hex::encode(&evidence.fork_finality_sig), ) .add_attribute("secret_key", hex::encode(btc_sk.to_bytes())); - Ok((wasm_msgs, ev)) + Ok((wasm_msg, ev)) } /// Verifies the finality signature message w.r.t. the public randomness commitment: From a145f7e5a41f79218360c352336891f8dcf775e7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 12:46:13 +0200 Subject: [PATCH 17/51] Fix: proper routing / auth of slashing message --- contracts/babylon/src/contract.rs | 35 ++++++++++++++++++++-------- contracts/babylon/src/error.rs | 2 ++ packages/apis/src/btc_staking_api.rs | 6 ++--- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index c3833715..48582c2f 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,4 +1,3 @@ -use crate::error::ContractError; use cosmwasm_std::{ to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, @@ -6,12 +5,15 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_utils::ParseReplyError; +use babylon_apis::btc_staking_api; +use babylon_bindings::BabylonMsg; + +use crate::error::ContractError; use crate::ibc::{ibc_packet, IBC_CHANNEL}; use crate::msg::contract::{ContractMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; use crate::state::btc_light_client; use crate::state::config::{Config, CONFIG}; -use babylon_bindings::BabylonMsg; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -202,20 +204,33 @@ pub fn execute( Ok(Response::new()) } ExecuteMsg::Slashing { evidence } => { - // This is an internal routing message from the `btc-staking` contract + // This is an internal routing message from the `btc_finality` contract + let cfg = CONFIG.load(deps.storage)?; // Check sender - let btc_staking = CONFIG - .load(deps.storage)? - .btc_staking - .ok_or(ContractError::BtcStakingNotSet {})?; - if info.sender != btc_staking { + let btc_finality = cfg + .btc_finality + .ok_or(ContractError::BtcFinalityNotSet {})?; + if info.sender != btc_finality { return Err(ContractError::Unauthorized {}); } + // Send to the staking contract for processing + let btc_staking = cfg.btc_staking.ok_or(ContractError::BtcStakingNotSet {})?; + // Slashes this finality provider, i.e., sets its slashing height to the block height + // and its power to zero + let msg = btc_staking_api::ExecuteMsg::Slash { + fp_btc_pk_hex: hex::encode(evidence.fp_btc_pk.clone()), + }; + let wasm_msg = WasmMsg::Execute { + contract_addr: btc_staking.to_string(), + msg: to_json_binary(&msg)?, + funds: vec![], + }; + // Send over IBC to the Provider (Babylon) let channel = IBC_CHANNEL.load(deps.storage)?; - let msg = ibc_packet::slashing_msg(&env, &channel, &evidence)?; + let ibc_msg = ibc_packet::slashing_msg(&env, &channel, &evidence)?; // TODO: Add events - Ok(Response::new().add_message(msg)) + Ok(Response::new().add_message(wasm_msg).add_message(ibc_msg)) } } } diff --git a/contracts/babylon/src/error.rs b/contracts/babylon/src/error.rs index 7b9e9fee..19a3f84f 100644 --- a/contracts/babylon/src/error.rs +++ b/contracts/babylon/src/error.rs @@ -34,6 +34,8 @@ pub enum ContractError { Unauthorized {}, #[error("The BTC staking contract is not set")] BtcStakingNotSet {}, + #[error("The BTC finality contract is not set")] + BtcFinalityNotSet {}, #[error("Invalid configuration: {msg}")] InvalidConfig { msg: String }, } diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index eb6a7faf..6f7f8277 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -20,9 +20,9 @@ pub enum ExecuteMsg { unbonded_del: Vec, }, /// Slash finality provider staking power. - /// Used by the finality contract only. - /// The finality contract will call this message to slash the finality provider's staking power - /// when the finality provider is found to be malicious + /// Used by the babylon-contract only. + /// The Babylon contract will call this message to set the finality provider's staking power to + /// zero when the finality provider is found to be malicious by the finality contract. Slash { fp_btc_pk_hex: String }, } From 7d4c03077c1e87c9c167a8135ce724a5ae8dab47 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Sep 2024 16:52:19 +0200 Subject: [PATCH 18/51] Update / Set staking address during finality contract instantiation --- contracts/babylon/src/contract.rs | 23 +++++++++++--- contracts/babylon/src/ibc.rs | 2 ++ contracts/babylon/src/multitest/suite.rs | 2 ++ .../babylon/src/state/btc_light_client.rs | 1 + contracts/babylon/tests/integration.rs | 4 +++ contracts/btc-finality/src/contract.rs | 30 ++++++++++++++++--- packages/apis/src/finality_api.rs | 3 ++ 7 files changed, 57 insertions(+), 8 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 48582c2f..e08cacca 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_utils::ParseReplyError; -use babylon_apis::btc_staking_api; +use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use crate::error::ContractError; @@ -135,12 +135,25 @@ fn reply_init_finality_callback( reply: SubMsgResponse, ) -> Result, ContractError> { // Try to get contract address from events in reply - let addr = reply_init_get_contract_address(reply)?; + let finality_addr = reply_init_get_contract_address(reply)?; CONFIG.update(deps.storage, |mut cfg| { - cfg.btc_finality = Some(addr); + cfg.btc_finality = Some(finality_addr.clone()); Ok::<_, ContractError>(cfg) })?; - Ok(Response::new()) + // Set the BTC staking contract address to the BTC finality contract + let cfg = CONFIG.load(deps.storage)?; + let msg = finality_api::ExecuteMsg::UpdateStaking { + staking: cfg + .btc_staking + .ok_or(ContractError::BtcStakingNotSet {})? + .to_string(), + }; + let wasm_msg = WasmMsg::Execute { + contract_addr: finality_addr.to_string(), + msg: to_json_binary(&msg)?, + funds: vec![], + }; + Ok(Response::new().add_message(wasm_msg)) } pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { @@ -263,6 +276,8 @@ mod tests { notify_cosmos_zone: false, btc_staking_code_id: None, btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: None, consumer_name: None, consumer_description: None, diff --git a/contracts/babylon/src/ibc.rs b/contracts/babylon/src/ibc.rs index deb8fbfe..c2951632 100644 --- a/contracts/babylon/src/ibc.rs +++ b/contracts/babylon/src/ibc.rs @@ -434,6 +434,8 @@ mod tests { notify_cosmos_zone: false, btc_staking_code_id: None, btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: None, consumer_name: None, consumer_description: None, diff --git a/contracts/babylon/src/multitest/suite.rs b/contracts/babylon/src/multitest/suite.rs index b99dae9e..f4cdcba8 100644 --- a/contracts/babylon/src/multitest/suite.rs +++ b/contracts/babylon/src/multitest/suite.rs @@ -66,6 +66,8 @@ impl SuiteBuilder { notify_cosmos_zone: false, btc_staking_code_id: Some(btc_staking_code_id), btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: Some(owner.to_string()), consumer_name: Some("TestConsumer".to_string()), consumer_description: Some("Test Consumer Description".to_string()), diff --git a/contracts/babylon/src/state/btc_light_client.rs b/contracts/babylon/src/state/btc_light_client.rs index db7e0e95..7526fb3c 100644 --- a/contracts/babylon/src/state/btc_light_client.rs +++ b/contracts/babylon/src/state/btc_light_client.rs @@ -357,6 +357,7 @@ pub(crate) mod tests { checkpoint_finalization_timeout: w as u64, notify_cosmos_zone: false, btc_staking: None, + btc_finality: None, consumer_name: None, consumer_description: None, }; diff --git a/contracts/babylon/tests/integration.rs b/contracts/babylon/tests/integration.rs index 56c71f07..dbe78dde 100644 --- a/contracts/babylon/tests/integration.rs +++ b/contracts/babylon/tests/integration.rs @@ -49,6 +49,8 @@ fn setup() -> Instance { notify_cosmos_zone: false, btc_staking_code_id: None, btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: None, }; let info = message_info(&Addr::unchecked(CREATOR), &[]); @@ -101,6 +103,8 @@ fn instantiate_works() { notify_cosmos_zone: false, btc_staking_code_id: None, btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: None, }; let info = message_info(&Addr::unchecked(CREATOR), &[]); diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 55674139..c831a242 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -1,8 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, QuerierWrapper, - QueryRequest, QueryResponse, Reply, Response, StdResult, WasmQuery, + attr, to_json_binary, Addr, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, + QuerierWrapper, QueryRequest, QueryResponse, Reply, Response, StdResult, WasmQuery, }; use cw2::set_contract_version; use cw_utils::{maybe_addr, nonpayable}; @@ -10,6 +10,8 @@ use cw_utils::{maybe_addr, nonpayable}; use babylon_apis::btc_staking_api::SudoMsg; use babylon_bindings::BabylonMsg; +use btc_staking::msg::ActivatedHeightResponse; + use crate::error::ContractError; use crate::finality::{ compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit, @@ -17,7 +19,6 @@ use crate::finality::{ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; use crate::{finality, queries, state}; -use btc_staking::msg::ActivatedHeightResponse; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -34,7 +35,7 @@ pub fn instantiate( let config = Config { denom, babylon: info.sender, - staking: Addr::unchecked("staking"), // TODO: instantiate staking contract and set address in reply + staking: Addr::unchecked("UNSET"), // To be set later, through `UpdateStaking` }; CONFIG.save(deps.storage, &config)?; @@ -120,6 +121,7 @@ pub fn execute( ExecuteMsg::UpdateAdmin { admin } => ADMIN .execute_update_admin(deps, info, maybe_addr(api, admin)?) .map_err(Into::into), + ExecuteMsg::UpdateStaking { staking } => handle_update_staking(deps, info, staking), ExecuteMsg::SubmitFinalitySignature { fp_pubkey_hex, height, @@ -169,6 +171,26 @@ pub fn sudo( } } +fn handle_update_staking( + deps: DepsMut, + info: MessageInfo, + staking_addr: String, +) -> Result, ContractError> { + let mut cfg = CONFIG.load(deps.storage)?; + if info.sender != cfg.babylon && !ADMIN.is_admin(deps.as_ref(), &info.sender)? { + return Err(ContractError::Unauthorized {}); + } + cfg.staking = deps.api.addr_validate(&staking_addr)?; + CONFIG.save(deps.storage, &cfg)?; + + let attributes = vec![ + attr("action", "update_btc_staking"), + attr("staking", staking_addr), + attr("sender", info.sender), + ]; + Ok(Response::new().add_attributes(attributes)) +} + fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result, ContractError> { // Compute active finality provider set let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize; diff --git a/packages/apis/src/finality_api.rs b/packages/apis/src/finality_api.rs index a0d6b569..c29ac211 100644 --- a/packages/apis/src/finality_api.rs +++ b/packages/apis/src/finality_api.rs @@ -13,6 +13,9 @@ use crate::Bytes; pub enum ExecuteMsg { /// Change the admin UpdateAdmin { admin: Option }, + /// Set the BTC staking addr. + /// Only admin or the babylon contract can set this + UpdateStaking { staking: String }, /// Committing a sequence of public randomness for EOTS CommitPublicRandomness { /// `fp_pubkey_hex` is the BTC PK of the finality provider that commits the public randomness From 16e94f569ab81e7cdfdf70a83f7f4d5cd124f33f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 09:38:52 +0200 Subject: [PATCH 19/51] Fix / adapt benches --- contracts/babylon/benches/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/babylon/benches/main.rs b/contracts/babylon/benches/main.rs index 32675f05..644b2f36 100644 --- a/contracts/babylon/benches/main.rs +++ b/contracts/babylon/benches/main.rs @@ -51,6 +51,8 @@ pub fn setup_instance() -> Instance { notify_cosmos_zone: false, btc_staking_code_id: None, btc_staking_msg: None, + btc_finality_code_id: None, + btc_finality_msg: None, admin: None, }; let info = mock_info(CREATOR, &[]); From 066ea79a74a19af3932be14e44fbb7737e586a32 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 09:48:25 +0200 Subject: [PATCH 20/51] Improve / adjust size checks --- contracts/btc-staking/tests/integration.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/btc-staking/tests/integration.rs b/contracts/btc-staking/tests/integration.rs index bbdbfb8a..bc990493 100644 --- a/contracts/btc-staking/tests/integration.rs +++ b/contracts/btc-staking/tests/integration.rs @@ -9,9 +9,7 @@ static WASM: &[u8] = include_bytes!("../../../artifacts/btc_staking.wasm"); const MAX_WASM_SIZE: usize = 800 * 1024; // 800 KB // wasm binary with full validation -// TODO: optimise to 800 KB static WASM_FULL: &[u8] = include_bytes!("../../../artifacts/btc_staking-full-validation.wasm"); -const MAX_WASM_SIZE_FULL: usize = 1024 * 1024; // 1 MB const CREATOR: &str = "creator"; @@ -24,10 +22,10 @@ fn wasm_size_limit_check() { MAX_WASM_SIZE ); assert!( - WASM_FULL.len() < MAX_WASM_SIZE_FULL, + WASM_FULL.len() < MAX_WASM_SIZE, "BTC staking contract (with full validation) wasm binary is too large: {} (target: {})", WASM_FULL.len(), - MAX_WASM_SIZE_FULL + MAX_WASM_SIZE ); } From 49b6ec88e5963ef1dd27a6a7e6bc2c4256648bd8 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 09:53:28 +0200 Subject: [PATCH 21/51] Fix / adjust staking tests --- contracts/btc-staking/src/contract.rs | 5 ++++- contracts/btc-staking/src/queries.rs | 2 +- contracts/btc-staking/src/staking.rs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index a9abe58c..15fc49a1 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -148,7 +148,10 @@ pub(crate) mod tests { }; use cw_controllers::AdminResponse; use hex::ToHex; - use test_utils::{get_btc_delegation, get_finality_provider}; + use k256::schnorr::{Signature, SigningKey}; + use test_utils::{ + get_btc_del_unbonding_sig_bytes, get_btc_delegation, get_finality_provider, get_fp_sk_bytes, + }; pub(crate) const CREATOR: &str = "creator"; pub(crate) const INIT_ADMIN: &str = "initial_admin"; diff --git a/contracts/btc-staking/src/queries.rs b/contracts/btc-staking/src/queries.rs index 1160ee6f..279cd2b9 100644 --- a/contracts/btc-staking/src/queries.rs +++ b/contracts/btc-staking/src/queries.rs @@ -183,7 +183,7 @@ mod tests { use cosmwasm_std::testing::message_info; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::StdError::NotFound; - use cosmwasm_std::{from_json, Binary, Env, Storage}; + use cosmwasm_std::{from_json, Env, Storage}; use babylon_apis::btc_staking_api::{FinalityProvider, UnbondedBtcDelegation}; diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index a22d6ad7..0a6267aa 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -411,7 +411,8 @@ pub(crate) mod tests { use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; use crate::contract::tests::{ - create_new_finality_provider, get_active_btc_delegation, get_params, CREATOR, INIT_ADMIN, + create_new_finality_provider, create_new_fp_sk, get_active_btc_delegation, + get_btc_del_unbonding_sig, get_derived_btc_delegation, get_params, CREATOR, INIT_ADMIN, }; use crate::contract::{execute, instantiate}; use crate::msg::{ExecuteMsg, InstantiateMsg}; From 3562f8b9e09640c07f4242f1406bde1414164271 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:21:22 +0200 Subject: [PATCH 22/51] Remove unused structs Update schema --- contracts/btc-staking/schema/btc-staking.json | 740 +++--------------- contracts/btc-staking/schema/raw/execute.json | 119 +-- .../btc-staking/schema/raw/instantiate.json | 45 +- contracts/btc-staking/schema/raw/query.json | 212 ----- .../schema/raw/response_to_params.json | 49 +- contracts/btc-staking/src/msg.rs | 16 - 6 files changed, 186 insertions(+), 995 deletions(-) diff --git a/contracts/btc-staking/schema/btc-staking.json b/contracts/btc-staking/schema/btc-staking.json index 724e2ccf..19fe5d5f 100644 --- a/contracts/btc-staking/schema/btc-staking.json +++ b/contracts/btc-staking/schema/btc-staking.json @@ -26,14 +26,43 @@ }, "additionalProperties": false, "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + }, "Params": { "description": "Params define Consumer-selectable BTC staking parameters", "type": "object", "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", "max_active_finality_providers", - "min_pub_rand" + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" ], "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "max_active_finality_providers": { "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", "type": "integer", @@ -45,6 +74,20 @@ "type": "integer", "format": "uint64", "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" } }, "additionalProperties": false @@ -125,97 +168,20 @@ "additionalProperties": false }, { - "description": "Committing a sequence of public randomness for EOTS", + "description": "Slash finality provider staking power. Used by the babylon-contract only. The Babylon contract will call this message to set the finality provider's staking power to zero when the finality provider is found to be malicious by the finality contract.", "type": "object", "required": [ - "commit_public_randomness" + "slash" ], "properties": { - "commit_public_randomness": { + "slash": { "type": "object", "required": [ - "commitment", - "fp_pubkey_hex", - "num_pub_rand", - "signature", - "start_height" + "fp_btc_pk_hex" ], "properties": { - "commitment": { - "description": "`commitment` is the commitment of these public randomness values. Currently, it's the root of the Merkle tree that includes the public randomness", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "fp_pubkey_hex": { - "description": "`fp_pubkey_hex` is the BTC PK of the finality provider that commits the public randomness", + "fp_btc_pk_hex": { "type": "string" - }, - "num_pub_rand": { - "description": "`num_pub_rand` is the amount of public randomness committed", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "signature": { - "description": "`signature` is the signature on (start_height || num_pub_rand || commitment) signed by the SK corresponding to `fp_pubkey_hex`. This prevents others committing public randomness on behalf of `fp_pubkey_hex`", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "start_height": { - "description": "`start_height` is the start block height of the list of public randomness", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Submit Finality Signature.\n\nThis is a message that can be called by a finality provider to submit their finality signature to the Consumer chain. The signature is verified by the Consumer chain using the finality provider's public key\n\nThis message is equivalent to the `MsgAddFinalitySig` message in the Babylon finality protobuf defs.", - "type": "object", - "required": [ - "submit_finality_signature" - ], - "properties": { - "submit_finality_signature": { - "type": "object", - "required": [ - "block_hash", - "fp_pubkey_hex", - "height", - "proof", - "pub_rand", - "signature" - ], - "properties": { - "block_hash": { - "$ref": "#/definitions/Binary" - }, - "fp_pubkey_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proof": { - "$ref": "#/definitions/Proof" - }, - "pub_rand": { - "$ref": "#/definitions/Binary" - }, - "signature": { - "$ref": "#/definitions/Binary" } }, "additionalProperties": false @@ -520,38 +486,6 @@ }, "additionalProperties": false }, - "Proof": { - "description": "A `Proof` is a proof of a leaf's existence in a Merkle tree.\n\nThe convention for proofs is to include leaf hashes, but to exclude the root hash. This convention is implemented across IAVL range proofs as well. Keep this consistent unless there's a very good reason to change everything. This affects the generalized proof system as well.\n\nEquivalent to / adapted from cometbft/crypto/merkle/proof.go.", - "type": "object", - "required": [ - "aunts", - "index", - "leaf_hash", - "total" - ], - "properties": { - "aunts": { - "type": "array", - "items": { - "$ref": "#/definitions/Binary" - } - }, - "index": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "leaf_hash": { - "$ref": "#/definitions/Binary" - }, - "total": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, "ProofOfPossessionBtc": { "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", "type": "object", @@ -879,122 +813,6 @@ }, "additionalProperties": false }, - { - "description": "`FinalitySignature` returns the signature of the finality provider for a given block height", - "type": "object", - "required": [ - "finality_signature" - ], - "properties": { - "finality_signature": { - "type": "object", - "required": [ - "btc_pk_hex", - "height" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`PubRandCommit` returns the public random commitments for a given FP.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.\n\n`start_after` is the height of to start after (before, if `reverse` is `true`), or `None` to start from the beginning (end, if `reverse` is `true`). `limit` is the maximum number of commitments to return. `reverse` is an optional flag to return the commitments in reverse order", - "type": "object", - "required": [ - "pub_rand_commit" - ], - "properties": { - "pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "reverse": { - "type": [ - "boolean", - "null" - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`FirstPubRandCommit` returns the first public random commitment (if any) for a given FP.\n\nIt's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to false.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.", - "type": "object", - "required": [ - "first_pub_rand_commit" - ], - "properties": { - "first_pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`LastPubRandCommit` returns the last public random commitment (if any) for a given FP.\n\nIt's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to true.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.", - "type": "object", - "required": [ - "last_pub_rand_commit" - ], - "properties": { - "last_pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "`ActivatedHeight` returns the height at which the contract gets its first delegation, if any", "type": "object", @@ -1008,102 +826,6 @@ } }, "additionalProperties": false - }, - { - "description": "`Block` returns the indexed block information at height", - "type": "object", - "required": [ - "block" - ], - "properties": { - "block": { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`Blocks` return the list of indexed blocks.\n\n`start_after` is the height of the block to start after (before, if `reverse` is `true`), or `None` to start from the beginning (end, if `reverse` is `true`). `limit` is the maximum number of blocks to return. `finalised` is an optional filter to return only finalised blocks. `reverse` is an optional flag to return the blocks in reverse order", - "type": "object", - "required": [ - "blocks" - ], - "properties": { - "blocks": { - "type": "object", - "properties": { - "finalised": { - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "reverse": { - "type": [ - "boolean", - "null" - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`Evidence` returns the evidence for a given FP and block height", - "type": "object", - "required": [ - "evidence" - ], - "properties": { - "evidence": { - "type": "object", - "required": [ - "btc_pk_hex", - "height" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } ], "definitions": { @@ -1168,89 +890,6 @@ }, "additionalProperties": false }, - "block": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IndexedBlock", - "description": "`IndexedBlock` is the necessary metadata and finalization status of a block", - "type": "object", - "required": [ - "app_hash", - "finalized", - "height" - ], - "properties": { - "app_hash": { - "description": "`app_hash` is the AppHash of the block", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "finalized": { - "description": "`finalized` indicates whether the IndexedBlock is finalised by 2/3 of the finality providers or not", - "type": "boolean" - }, - "height": { - "description": "`height` is the height of the block", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "blocks": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BlocksResponse", - "type": "object", - "required": [ - "blocks" - ], - "properties": { - "blocks": { - "type": "array", - "items": { - "$ref": "#/definitions/IndexedBlock" - } - } - }, - "additionalProperties": false, - "definitions": { - "IndexedBlock": { - "description": "`IndexedBlock` is the necessary metadata and finalization status of a block", - "type": "object", - "required": [ - "app_hash", - "finalized", - "height" - ], - "properties": { - "app_hash": { - "description": "`app_hash` is the AppHash of the block", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "finalized": { - "description": "`finalized` indicates whether the IndexedBlock is finalised by 2/3 of the finality providers or not", - "type": "boolean" - }, - "height": { - "description": "`height` is the height of the block", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - } - }, "config": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Config", @@ -1778,102 +1417,6 @@ }, "additionalProperties": false }, - "evidence": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "EvidenceResponse", - "type": "object", - "properties": { - "evidence": { - "anyOf": [ - { - "$ref": "#/definitions/Evidence" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Evidence": { - "description": "Evidence is the evidence that a finality provider has signed finality signatures with correct public randomness on two conflicting Babylon headers", - "type": "object", - "required": [ - "block_height", - "canonical_app_hash", - "canonical_finality_sig", - "fork_app_hash", - "fork_finality_sig", - "fp_btc_pk", - "pub_rand" - ], - "properties": { - "block_height": { - "description": "`block_height` is the height of the conflicting blocks", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "canonical_app_hash": { - "description": "`canonical_app_hash` is the AppHash of the canonical block", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "canonical_finality_sig": { - "description": "`canonical_finality_sig` is the finality signature to the canonical block, where finality signature is an EOTS signature, i.e., the `s` in a Schnorr signature `(r, s)`. `r` is the public randomness already committed by the finality provider. Deserializes to `SchnorrEOTSSig`", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "fork_app_hash": { - "description": "`fork_app_hash` is the AppHash of the fork block", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "fork_finality_sig": { - "description": "`fork_finality_sig` is the finality signature to the fork block, where finality signature is an EOTS signature. Deserializes to `SchnorrEOTSSig`", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "fp_btc_pk": { - "description": "`fp_btc_pk` is the BTC PK of the finality provider that casts this vote", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "pub_rand": { - "description": "`pub_rand is` the public randomness the finality provider has committed to. Deserializes to `SchnorrPubRand`", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - } - }, - "additionalProperties": false - } - } - }, "finality_provider": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "FinalityProvider", @@ -2224,129 +1767,36 @@ } } }, - "finality_signature": { + "params": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "FinalitySignatureResponse", + "title": "Params", + "description": "Params define Consumer-selectable BTC staking parameters", "type": "object", "required": [ - "signature" + "btc_network", + "covenant_pks", + "covenant_quorum", + "max_active_finality_providers", + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" ], "properties": { - "signature": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { "type": "array", "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "type": "string" } - } - }, - "additionalProperties": false - }, - "first_pub_rand_commit": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_PubRandCommit", - "anyOf": [ - { - "$ref": "#/definitions/PubRandCommit" }, - { - "type": "null" - } - ], - "definitions": { - "PubRandCommit": { - "description": "`PubRandCommit` is a commitment to a series of public randomness. Currently, the commitment is a root of a Merkle tree that includes a series of public randomness values", - "type": "object", - "required": [ - "commitment", - "num_pub_rand", - "start_height" - ], - "properties": { - "commitment": { - "description": "`commitment` is the value of the commitment. Currently, it's the root of the Merkle tree constructed by the public randomness", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "num_pub_rand": { - "description": "`num_pub_rand` is the number of committed public randomness", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_height": { - "description": "`start_height` is the height of the first commitment", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - } - }, - "last_pub_rand_commit": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_PubRandCommit", - "anyOf": [ - { - "$ref": "#/definitions/PubRandCommit" + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 }, - { - "type": "null" - } - ], - "definitions": { - "PubRandCommit": { - "description": "`PubRandCommit` is a commitment to a series of public randomness. Currently, the commitment is a root of a Merkle tree that includes a series of public randomness values", - "type": "object", - "required": [ - "commitment", - "num_pub_rand", - "start_height" - ], - "properties": { - "commitment": { - "description": "`commitment` is the value of the commitment. Currently, it's the root of the Merkle tree constructed by the public randomness", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - "num_pub_rand": { - "description": "`num_pub_rand` is the number of committed public randomness", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "start_height": { - "description": "`start_height` is the height of the first commitment", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - } - }, - "params": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Params", - "description": "Params define Consumer-selectable BTC staking parameters", - "type": "object", - "required": [ - "max_active_finality_providers", - "min_pub_rand" - ], - "properties": { "max_active_finality_providers": { "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", "type": "integer", @@ -2358,44 +1808,34 @@ "type": "integer", "format": "uint64", "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "pub_rand_commit": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PubRandCommit", - "description": "`PubRandCommit` is a commitment to a series of public randomness. Currently, the commitment is a root of a Merkle tree that includes a series of public randomness values", - "type": "object", - "required": [ - "commitment", - "num_pub_rand", - "start_height" - ], - "properties": { - "commitment": { - "description": "`commitment` is the value of the commitment. Currently, it's the root of the Merkle tree constructed by the public randomness", - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } }, - "num_pub_rand": { - "description": "`num_pub_rand` is the number of committed public randomness", + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", "format": "uint64", "minimum": 0.0 }, - "start_height": { - "description": "`start_height` is the height of the first commitment", - "type": "integer", - "format": "uint64", - "minimum": 0.0 + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + } + } } } } diff --git a/contracts/btc-staking/schema/raw/execute.json b/contracts/btc-staking/schema/raw/execute.json index 67164ca2..11b583e7 100644 --- a/contracts/btc-staking/schema/raw/execute.json +++ b/contracts/btc-staking/schema/raw/execute.json @@ -72,97 +72,20 @@ "additionalProperties": false }, { - "description": "Committing a sequence of public randomness for EOTS", + "description": "Slash finality provider staking power. Used by the babylon-contract only. The Babylon contract will call this message to set the finality provider's staking power to zero when the finality provider is found to be malicious by the finality contract.", "type": "object", "required": [ - "commit_public_randomness" + "slash" ], "properties": { - "commit_public_randomness": { + "slash": { "type": "object", "required": [ - "commitment", - "fp_pubkey_hex", - "num_pub_rand", - "signature", - "start_height" + "fp_btc_pk_hex" ], "properties": { - "commitment": { - "description": "`commitment` is the commitment of these public randomness values. Currently, it's the root of the Merkle tree that includes the public randomness", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "fp_pubkey_hex": { - "description": "`fp_pubkey_hex` is the BTC PK of the finality provider that commits the public randomness", + "fp_btc_pk_hex": { "type": "string" - }, - "num_pub_rand": { - "description": "`num_pub_rand` is the amount of public randomness committed", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "signature": { - "description": "`signature` is the signature on (start_height || num_pub_rand || commitment) signed by the SK corresponding to `fp_pubkey_hex`. This prevents others committing public randomness on behalf of `fp_pubkey_hex`", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] - }, - "start_height": { - "description": "`start_height` is the start block height of the list of public randomness", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Submit Finality Signature.\n\nThis is a message that can be called by a finality provider to submit their finality signature to the Consumer chain. The signature is verified by the Consumer chain using the finality provider's public key\n\nThis message is equivalent to the `MsgAddFinalitySig` message in the Babylon finality protobuf defs.", - "type": "object", - "required": [ - "submit_finality_signature" - ], - "properties": { - "submit_finality_signature": { - "type": "object", - "required": [ - "block_hash", - "fp_pubkey_hex", - "height", - "proof", - "pub_rand", - "signature" - ], - "properties": { - "block_hash": { - "$ref": "#/definitions/Binary" - }, - "fp_pubkey_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "proof": { - "$ref": "#/definitions/Proof" - }, - "pub_rand": { - "$ref": "#/definitions/Binary" - }, - "signature": { - "$ref": "#/definitions/Binary" } }, "additionalProperties": false @@ -467,38 +390,6 @@ }, "additionalProperties": false }, - "Proof": { - "description": "A `Proof` is a proof of a leaf's existence in a Merkle tree.\n\nThe convention for proofs is to include leaf hashes, but to exclude the root hash. This convention is implemented across IAVL range proofs as well. Keep this consistent unless there's a very good reason to change everything. This affects the generalized proof system as well.\n\nEquivalent to / adapted from cometbft/crypto/merkle/proof.go.", - "type": "object", - "required": [ - "aunts", - "index", - "leaf_hash", - "total" - ], - "properties": { - "aunts": { - "type": "array", - "items": { - "$ref": "#/definitions/Binary" - } - }, - "index": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "leaf_hash": { - "$ref": "#/definitions/Binary" - }, - "total": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, "ProofOfPossessionBtc": { "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", "type": "object", diff --git a/contracts/btc-staking/schema/raw/instantiate.json b/contracts/btc-staking/schema/raw/instantiate.json index b2f3a6c3..3c553424 100644 --- a/contracts/btc-staking/schema/raw/instantiate.json +++ b/contracts/btc-staking/schema/raw/instantiate.json @@ -22,14 +22,43 @@ }, "additionalProperties": false, "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + }, "Params": { "description": "Params define Consumer-selectable BTC staking parameters", "type": "object", "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", "max_active_finality_providers", - "min_pub_rand" + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" ], "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "max_active_finality_providers": { "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", "type": "integer", @@ -41,6 +70,20 @@ "type": "integer", "format": "uint64", "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" } }, "additionalProperties": false diff --git a/contracts/btc-staking/schema/raw/query.json b/contracts/btc-staking/schema/raw/query.json index c6d174f1..891e0773 100644 --- a/contracts/btc-staking/schema/raw/query.json +++ b/contracts/btc-staking/schema/raw/query.json @@ -240,122 +240,6 @@ }, "additionalProperties": false }, - { - "description": "`FinalitySignature` returns the signature of the finality provider for a given block height", - "type": "object", - "required": [ - "finality_signature" - ], - "properties": { - "finality_signature": { - "type": "object", - "required": [ - "btc_pk_hex", - "height" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`PubRandCommit` returns the public random commitments for a given FP.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.\n\n`start_after` is the height of to start after (before, if `reverse` is `true`), or `None` to start from the beginning (end, if `reverse` is `true`). `limit` is the maximum number of commitments to return. `reverse` is an optional flag to return the commitments in reverse order", - "type": "object", - "required": [ - "pub_rand_commit" - ], - "properties": { - "pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "reverse": { - "type": [ - "boolean", - "null" - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`FirstPubRandCommit` returns the first public random commitment (if any) for a given FP.\n\nIt's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to false.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.", - "type": "object", - "required": [ - "first_pub_rand_commit" - ], - "properties": { - "first_pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`LastPubRandCommit` returns the last public random commitment (if any) for a given FP.\n\nIt's a convenience shortcut of `PubRandCommit` with a `limit` of 1, and `reverse` set to true.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format.", - "type": "object", - "required": [ - "last_pub_rand_commit" - ], - "properties": { - "last_pub_rand_commit": { - "type": "object", - "required": [ - "btc_pk_hex" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "`ActivatedHeight` returns the height at which the contract gets its first delegation, if any", "type": "object", @@ -369,102 +253,6 @@ } }, "additionalProperties": false - }, - { - "description": "`Block` returns the indexed block information at height", - "type": "object", - "required": [ - "block" - ], - "properties": { - "block": { - "type": "object", - "required": [ - "height" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`Blocks` return the list of indexed blocks.\n\n`start_after` is the height of the block to start after (before, if `reverse` is `true`), or `None` to start from the beginning (end, if `reverse` is `true`). `limit` is the maximum number of blocks to return. `finalised` is an optional filter to return only finalised blocks. `reverse` is an optional flag to return the blocks in reverse order", - "type": "object", - "required": [ - "blocks" - ], - "properties": { - "blocks": { - "type": "object", - "properties": { - "finalised": { - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "reverse": { - "type": [ - "boolean", - "null" - ] - }, - "start_after": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "`Evidence` returns the evidence for a given FP and block height", - "type": "object", - "required": [ - "evidence" - ], - "properties": { - "evidence": { - "type": "object", - "required": [ - "btc_pk_hex", - "height" - ], - "properties": { - "btc_pk_hex": { - "type": "string" - }, - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } ], "definitions": { diff --git a/contracts/btc-staking/schema/raw/response_to_params.json b/contracts/btc-staking/schema/raw/response_to_params.json index b29d6558..614e0fd4 100644 --- a/contracts/btc-staking/schema/raw/response_to_params.json +++ b/contracts/btc-staking/schema/raw/response_to_params.json @@ -4,10 +4,30 @@ "description": "Params define Consumer-selectable BTC staking parameters", "type": "object", "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", "max_active_finality_providers", - "min_pub_rand" + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" ], "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, "max_active_finality_providers": { "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", "type": "integer", @@ -19,7 +39,32 @@ "type": "integer", "format": "uint64", "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + } + } } diff --git a/contracts/btc-staking/src/msg.rs b/contracts/btc-staking/src/msg.rs index 6ab8a241..5b9dd2e1 100644 --- a/contracts/btc-staking/src/msg.rs +++ b/contracts/btc-staking/src/msg.rs @@ -2,7 +2,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cw_controllers::AdminResponse; use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; -use babylon_apis::finality_api::{Evidence, IndexedBlock}; use crate::state::config::{Config, Params}; use crate::state::staking::BtcDelegation; @@ -118,22 +117,7 @@ pub struct FinalityProviderInfo { pub power: u64, } -#[cw_serde] -pub struct FinalitySignatureResponse { - pub signature: Vec, -} - #[cw_serde] pub struct ActivatedHeightResponse { pub height: u64, } - -#[cw_serde] -pub struct BlocksResponse { - pub blocks: Vec, -} - -#[cw_serde] -pub struct EvidenceResponse { - pub evidence: Option, -} From 95ff295daeb8be4f866e2d3f56a8e372aef37105 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:21:41 +0200 Subject: [PATCH 23/51] Make test helpers public --- contracts/btc-staking/src/contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 15fc49a1..2ee70cb5 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -127,7 +127,7 @@ pub fn execute( } #[cfg(test)] -pub(crate) mod tests { +pub mod tests { use std::str::FromStr; use super::*; @@ -252,7 +252,7 @@ pub(crate) mod tests { } // Build a derived active BTC delegation from the base (from testdata) BTC delegation - pub(crate) fn get_derived_btc_delegation(del_id: i32, fp_ids: &[i32]) -> ActiveBtcDelegation { + pub fn get_derived_btc_delegation(del_id: i32, fp_ids: &[i32]) -> ActiveBtcDelegation { let del = get_btc_delegation(del_id, fp_ids.to_vec()); new_active_btc_delegation(del) } From 4e43626ede24b4248d8e1c0b99a193cc0379ae0b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:22:26 +0200 Subject: [PATCH 24/51] Update schema --- .../babylon/schema/babylon-contract.json | 37 ++++++++++++++++++- contracts/babylon/schema/raw/instantiate.json | 25 ++++++++++++- .../schema/raw/response_to_config.json | 12 ++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/contracts/babylon/schema/babylon-contract.json b/contracts/babylon/schema/babylon-contract.json index 56b3ad6c..b0914132 100644 --- a/contracts/babylon/schema/babylon-contract.json +++ b/contracts/babylon/schema/babylon-contract.json @@ -15,7 +15,7 @@ ], "properties": { "admin": { - "description": "If set, this will be the Wasm migration / upgrade admin of the BTC staking contract", + "description": "If set, this will be the Wasm migration / upgrade admin of the BTC staking contract and the BTC finality contract", "type": [ "string", "null" @@ -30,6 +30,26 @@ "format": "uint64", "minimum": 0.0 }, + "btc_finality_code_id": { + "description": "If set, this will instantiate a BTC finality contract", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "btc_finality_msg": { + "description": "If set, this will define the instantiation message for the BTC finality contract. This message is opaque to the Babylon contract, and depends on the specific finality contract being instantiated", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, "btc_staking_code_id": { "description": "If set, this will instantiate a BTC staking contract for BTC re-staking", "type": [ @@ -40,7 +60,7 @@ "minimum": 0.0 }, "btc_staking_msg": { - "description": "If set, this will define the instantiate message for the BTC staking contract. This message is opaque to the Babylon contract, and depends on the specific staking contract being instantiated", + "description": "If set, this will define the instantiation message for the BTC staking contract. This message is opaque to the Babylon contract, and depends on the specific staking contract being instantiated", "anyOf": [ { "$ref": "#/definitions/Binary" @@ -73,6 +93,7 @@ "$ref": "#/definitions/Network" }, "notify_cosmos_zone": { + "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: If set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" } }, @@ -1281,6 +1302,17 @@ "format": "uint64", "minimum": 0.0 }, + "btc_finality": { + "description": "If set, this stores a BTC finality contract used for BTC finality on the Consumer", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "btc_staking": { "description": "If set, this stores a BTC staking contract used for BTC re-staking", "anyOf": [ @@ -1315,6 +1347,7 @@ "$ref": "#/definitions/Network" }, "notify_cosmos_zone": { + "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: if set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" } }, diff --git a/contracts/babylon/schema/raw/instantiate.json b/contracts/babylon/schema/raw/instantiate.json index 108bee01..89440deb 100644 --- a/contracts/babylon/schema/raw/instantiate.json +++ b/contracts/babylon/schema/raw/instantiate.json @@ -11,7 +11,7 @@ ], "properties": { "admin": { - "description": "If set, this will be the Wasm migration / upgrade admin of the BTC staking contract", + "description": "If set, this will be the Wasm migration / upgrade admin of the BTC staking contract and the BTC finality contract", "type": [ "string", "null" @@ -26,6 +26,26 @@ "format": "uint64", "minimum": 0.0 }, + "btc_finality_code_id": { + "description": "If set, this will instantiate a BTC finality contract", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "btc_finality_msg": { + "description": "If set, this will define the instantiation message for the BTC finality contract. This message is opaque to the Babylon contract, and depends on the specific finality contract being instantiated", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, "btc_staking_code_id": { "description": "If set, this will instantiate a BTC staking contract for BTC re-staking", "type": [ @@ -36,7 +56,7 @@ "minimum": 0.0 }, "btc_staking_msg": { - "description": "If set, this will define the instantiate message for the BTC staking contract. This message is opaque to the Babylon contract, and depends on the specific staking contract being instantiated", + "description": "If set, this will define the instantiation message for the BTC staking contract. This message is opaque to the Babylon contract, and depends on the specific staking contract being instantiated", "anyOf": [ { "$ref": "#/definitions/Binary" @@ -69,6 +89,7 @@ "$ref": "#/definitions/Network" }, "notify_cosmos_zone": { + "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: If set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" } }, diff --git a/contracts/babylon/schema/raw/response_to_config.json b/contracts/babylon/schema/raw/response_to_config.json index e5c34497..f097e13b 100644 --- a/contracts/babylon/schema/raw/response_to_config.json +++ b/contracts/babylon/schema/raw/response_to_config.json @@ -23,6 +23,17 @@ "format": "uint64", "minimum": 0.0 }, + "btc_finality": { + "description": "If set, this stores a BTC finality contract used for BTC finality on the Consumer", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "btc_staking": { "description": "If set, this stores a BTC staking contract used for BTC re-staking", "anyOf": [ @@ -57,6 +68,7 @@ "$ref": "#/definitions/Network" }, "notify_cosmos_zone": { + "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: if set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" } }, From 0938d3b0fe3e2421e40692dd62bdaac8abb19492 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:42:28 +0200 Subject: [PATCH 25/51] Fix: tests compilation / imports --- contracts/btc-finality/src/contract.rs | 15 +- contracts/btc-finality/src/finality.rs | 94 ++-- contracts/btc-finality/src/queries.rs | 656 ------------------------- 3 files changed, 54 insertions(+), 711 deletions(-) diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index c831a242..d0e2f696 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -254,7 +254,6 @@ pub(crate) mod tests { FinalityProviderDescription, NewFinalityProvider, ProofOfPossessionBtc, }; use babylon_apis::finality_api::PubRandCommit; - use babylon_bitcoin::chain_params::Network; use babylon_proto::babylon::btcstaking::v1::{ BtcDelegation, FinalityProvider, Params as ProtoParams, }; @@ -273,14 +272,8 @@ pub(crate) mod tests { fn new_params(params: ProtoParams) -> Params { Params { - covenant_pks: params.covenant_pks.iter().map(hex::encode).collect(), - covenant_quorum: params.covenant_quorum, - btc_network: Network::Regtest, // TODO: fix this max_active_finality_providers: params.max_active_finality_providers, min_pub_rand: 10, // TODO: fix this - slashing_address: params.slashing_address, - min_slashing_tx_fee_sat: params.min_slashing_tx_fee_sat as u64, - slashing_rate: "0.01".to_string(), // TODO: fix this } } @@ -359,13 +352,7 @@ pub(crate) mod tests { } } - /// Build an active BTC delegation from a BTC delegation - pub(crate) fn get_active_btc_delegation() -> ActiveBtcDelegation { - let del = get_btc_delegation(1, vec![1]); - new_active_btc_delegation(del) - } - - // Build a derived active BTC delegation from the base (from testdata) BTC delegation + /// Build a derived active BTC delegation from the base (from testdata) BTC delegation pub(crate) fn get_derived_btc_delegation(del_id: i32, fp_ids: &[i32]) -> ActiveBtcDelegation { let del = get_btc_delegation(del_id, fp_ids.to_vec()); new_active_btc_delegation(del) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 7b3ae629..454a1759 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -584,12 +584,17 @@ pub fn list_fps_by_power( #[cfg(test)] mod tests { - use crate::contract::instantiate; - use crate::contract::tests::{create_new_finality_provider, get_public_randomness_commitment}; + use crate::contract::tests::{ + create_new_finality_provider, get_derived_btc_delegation, get_params, + get_public_randomness_commitment, + }; + use crate::contract::{execute, instantiate}; use crate::error::ContractError; - use crate::msg::InstantiateMsg; + use crate::msg::{FinalitySignatureResponse, InstantiateMsg}; + use crate::queries::{block, evidence, finality_signature}; + use babylon_apis::btc_staking_api; use babylon_apis::btc_staking_api::SudoMsg; - use babylon_apis::finality_api::IndexedBlock; + use babylon_apis::finality_api::{ExecuteMsg, IndexedBlock}; use babylon_bindings::BabylonMsg; use cosmwasm_std::testing::{ message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, @@ -599,8 +604,6 @@ mod tests { use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; const CREATOR: &str = "creator"; - const INIT_ADMIN: &str = "initial_admin"; - const NEW_ADMIN: &str = "new_admin"; fn mock_env_height(height: u64) -> Env { let mut env = mock_env(); @@ -676,14 +679,15 @@ mod tests { let new_fp = create_new_finality_provider(1); assert_eq!(new_fp.btc_pk_hex, pk_hex); - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![new_fp.clone()], active_del: vec![], slashed_del: vec![], unbonded_del: vec![], }; - let res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + let res = + btc_staking::contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); assert_eq!(0, res.messages.len()); // Now commit the public randomness for it @@ -732,31 +736,33 @@ mod tests { let new_fp = create_new_finality_provider(1); assert_eq!(new_fp.btc_pk_hex, pk_hex); - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![new_fp.clone()], active_del: vec![], slashed_del: vec![], unbonded_del: vec![], }; - let _res = execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); + let _res = + btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) + .unwrap(); // Activated height is not set let res = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); assert_eq!(res.height, 0); // Add a delegation, so that the finality provider has some power - let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![del1.clone()], slashed_del: vec![], unbonded_del: vec![], }; - execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); + btc_staking::contract::execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); // Activated height is now set let activated_height = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); @@ -803,14 +809,14 @@ mod tests { ); // Submit a finality signature from that finality provider at height initial_height + 1 - let finality_signature = add_finality_signature.finality_sig.to_vec(); + let finality_sig = add_finality_signature.finality_sig.to_vec(); let msg = ExecuteMsg::SubmitFinalitySignature { fp_pubkey_hex: pk_hex.clone(), height: initial_height + 1, pub_rand: pub_rand_one.into(), proof: proof.into(), block_hash: add_finality_signature.block_app_hash.to_vec().into(), - signature: Binary::new(finality_signature.clone()), + signature: Binary::new(finality_sig.clone()), }; // Execute the message at a higher height, so that: @@ -825,16 +831,12 @@ mod tests { .unwrap(); // Query finality signature for that exact height - let sig = btc_staking::queries::finality_signature( - deps.as_ref(), - pk_hex.to_string(), - initial_height + 1, - ) - .unwrap(); + let sig = + finality_signature(deps.as_ref(), pk_hex.to_string(), initial_height + 1).unwrap(); assert_eq!( sig, FinalitySignatureResponse { - signature: finality_signature + signature: finality_sig } ); } @@ -872,31 +874,35 @@ mod tests { let new_fp = create_new_finality_provider(1); assert_eq!(new_fp.btc_pk_hex, pk_hex); - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![new_fp.clone()], active_del: vec![], slashed_del: vec![], unbonded_del: vec![], }; - execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); + btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) + .unwrap(); // Add a delegation, so that the finality provider has some power - let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![del1.clone()], slashed_del: vec![], unbonded_del: vec![], }; - execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); + btc_staking::contract::execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); // Check that the finality provider power has been updated - let fp_info = - queries::finality_provider_info(deps.as_ref(), new_fp.btc_pk_hex.clone(), None) - .unwrap(); + let fp_info = btc_staking::queries::finality_provider_info( + deps.as_ref(), + new_fp.btc_pk_hex.clone(), + None, + ) + .unwrap(); assert_eq!(fp_info.power, del1.total_sat); // Submit public randomness commitment for the FP and the involved heights @@ -992,7 +998,7 @@ mod tests { assert_eq!(0, res.messages.len()); // Assert the submitted block has been indexed and finalised - let indexed_block = btc_staking::queries::block(deps.as_ref(), submit_height).unwrap(); + let indexed_block = block(deps.as_ref(), submit_height).unwrap(); assert_eq!( indexed_block, IndexedBlock { @@ -1035,32 +1041,38 @@ mod tests { let new_fp = create_new_finality_provider(1); assert_eq!(new_fp.btc_pk_hex, pk_hex); - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![new_fp.clone()], active_del: vec![], slashed_del: vec![], unbonded_del: vec![], }; - let _res = execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); + let _res = + btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) + .unwrap(); // Add a delegation, so that the finality provider has some power - let mut del1 = btc_staking::contract::tests::get_derived_btc_delegation(1, &[1]); + let mut del1 = get_derived_btc_delegation(1, &[1]); del1.fp_btc_pk_list = vec![pk_hex.clone()]; - let msg = ExecuteMsg::BtcStaking { + let msg = btc_staking_api::ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![del1.clone()], slashed_del: vec![], unbonded_del: vec![], }; - execute(deps.as_mut(), initial_env.clone(), info.clone(), msg).unwrap(); + btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) + .unwrap(); // Check that the finality provider power has been updated - let fp_info = - queries::finality_provider_info(deps.as_ref(), new_fp.btc_pk_hex.clone(), None) - .unwrap(); + let fp_info = btc_staking::queries::finality_provider_info( + deps.as_ref(), + new_fp.btc_pk_hex.clone(), + None, + ) + .unwrap(); assert_eq!(fp_info.power, del1.total_sat); // Submit public randomness commitment for the FP and the involved heights @@ -1133,7 +1145,7 @@ mod tests { // Assert the double signing evidence is proper let btc_pk = hex::decode(pk_hex.clone()).unwrap(); - let evidence = btc_staking::queries::evidence(deps.as_ref(), pk_hex.clone(), submit_height) + let evidence = evidence(deps.as_ref(), pk_hex.clone(), submit_height) .unwrap() .evidence .unwrap(); @@ -1168,7 +1180,7 @@ mod tests { call_end_block(&mut deps, "deadbeef02".as_bytes(), final_height).unwrap(); // Assert the canonical block has been indexed (and finalised) - let indexed_block = btc_staking::queries::block(deps.as_ref(), submit_height).unwrap(); + let indexed_block = block(deps.as_ref(), submit_height).unwrap(); assert_eq!( indexed_block, IndexedBlock { diff --git a/contracts/btc-finality/src/queries.rs b/contracts/btc-finality/src/queries.rs index dbcedd69..f6774f8e 100644 --- a/contracts/btc-finality/src/queries.rs +++ b/contracts/btc-finality/src/queries.rs @@ -77,659 +77,3 @@ pub fn evidence(deps: Deps, btc_pk_hex: String, height: u64) -> StdResult Vec { - let mut dels = dels.to_vec(); - dels.sort_by_key(staking_tx_hash); - dels - } - - #[test] - fn test_finality_providers() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a couple finality providers - let new_fp1 = create_new_finality_provider(1); - let new_fp2 = create_new_finality_provider(2); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone(), new_fp2.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query finality providers - let fps = crate::queries::finality_providers(deps.as_ref(), None, None) - .unwrap() - .fps; - - let fp1 = FinalityProvider::from(&new_fp1); - let fp2 = FinalityProvider::from(&new_fp2); - let expected_fps = vec![fp1.clone(), fp2.clone()]; - assert_eq!(fps.len(), expected_fps.len()); - for fp in fps { - assert!(expected_fps.contains(&fp)); - } - - // Query finality providers with limit - let fps = crate::queries::finality_providers(deps.as_ref(), None, Some(1)) - .unwrap() - .fps; - assert_eq!(fps.len(), 1); - assert!(fps[0] == fp1 || fps[0] == fp2); - - // Query finality providers with start_after - let fp_pk = fps[0].btc_pk_hex.clone(); - let fps = crate::queries::finality_providers(deps.as_ref(), Some(fp_pk), None) - .unwrap() - .fps; - assert_eq!(fps.len(), 1); - assert!(fps[0] == fp1 || fps[0] == fp2); - } - - #[test] - fn test_delegations() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a couple finality providers - let new_fp1 = create_new_finality_provider(1); - let new_fp2 = create_new_finality_provider(2); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone(), new_fp2.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add a couple delegations - let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query delegations - let dels = crate::queries::delegations(deps.as_ref(), None, None, None) - .unwrap() - .delegations; - - assert_eq!(dels.len(), 2); - // Sort original delegations by staking tx hash (to compare with the query result) - let sorted_dels = sort_delegations(&[del1.into(), del2.into()]); - assert_eq!(dels[0], sorted_dels[0]); - assert_eq!(dels[1], sorted_dels[1]); - - // Query delegations with limit - let dels = crate::queries::delegations(deps.as_ref(), None, Some(1), None) - .unwrap() - .delegations; - - assert_eq!(dels.len(), 1); - assert_eq!(dels[0], sorted_dels[0]); - - // Query delegations with start_after - let staking_tx_hash_hex = staking_tx_hash(&sorted_dels[0]).to_string(); - let dels = - crate::queries::delegations(deps.as_ref(), Some(staking_tx_hash_hex), None, None) - .unwrap() - .delegations; - - assert_eq!(dels.len(), 1); - assert_eq!(dels[0], sorted_dels[1]); - } - - #[test] - fn test_active_delegations() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a finality provider - let new_fp1 = create_new_finality_provider(1); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add a couple delegations - let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query only active delegations - let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(true)) - .unwrap() - .delegations; - assert_eq!(dels.len(), 2); - // Sort original delegations by staking tx hash (to compare with the query result) - let sorted_dels = sort_delegations(&[del1.clone().into(), del2.clone().into()]); - assert_eq!(dels[0], sorted_dels[0]); - assert_eq!(dels[1], sorted_dels[1]); - - // Unbond the second delegation - // Compute staking tx hash - let staking_tx_hash_hex = staking_tx_hash(&del2.into()).to_string(); - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![UnbondedBtcDelegation { - staking_tx_hash: staking_tx_hash_hex, - unbonding_tx_sig: Binary::new(vec![0x01, 0x02, 0x03]), - }], - }; - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query only active delegations - let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(true)) - .unwrap() - .delegations; - assert_eq!(dels.len(), 1); - assert_eq!(dels[0], del1.into()); - - // Query all delegations (with active set to false) - let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(false)) - .unwrap() - .delegations; - assert_eq!(dels.len(), 2); - - // Query all delegations (without active set) - let dels = crate::queries::delegations(deps.as_ref(), None, None, None) - .unwrap() - .delegations; - assert_eq!(dels.len(), 2); - } - - #[test] - fn test_delegations_by_fp() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a couple finality providers - let new_fp1 = create_new_finality_provider(1); - let fp1_pk = new_fp1.btc_pk_hex.clone(); - let new_fp2 = create_new_finality_provider(2); - let fp2_pk = new_fp2.btc_pk_hex.clone(); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone(), new_fp2.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add a couple delegations - let del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - let del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query delegations by finality provider - let dels1 = crate::queries::delegations_by_fp(deps.as_ref(), fp1_pk.clone()) - .unwrap() - .hashes; - assert_eq!(dels1.len(), 1); - let dels2 = crate::queries::delegations_by_fp(deps.as_ref(), fp2_pk.clone()) - .unwrap() - .hashes; - assert_eq!(dels2.len(), 1); - assert_ne!(dels1[0], dels2[0]); - let err = crate::queries::delegations_by_fp(deps.as_ref(), "f3".to_string()).unwrap_err(); - assert!(matches!(err, ContractError::Std(NotFound { .. }))); - } - - #[test] - fn test_active_delegations_by_fp() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a finality provider - let new_fp1 = create_new_finality_provider(1); - let fp1_pk = new_fp1.btc_pk_hex.clone(); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add a couple delegations - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); - - // Adjust staking amounts - del1.total_sat = 100; - del2.total_sat = 200; - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query all delegations by finality provider - let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), false) - .unwrap() - .delegations; - assert_eq!(dels1.len(), 2); - - // Query active delegations by finality provider - let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), true) - .unwrap() - .delegations; - assert_eq!(dels1.len(), 2); - - // Unbond the first delegation - // Compute staking tx hash - let staking_tx_hash_hex = staking_tx_hash(&del1.into()).to_string(); - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![UnbondedBtcDelegation { - staking_tx_hash: staking_tx_hash_hex, - unbonding_tx_sig: Binary::new(vec![0x01, 0x02, 0x03]), - }], - }; - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query all delegations by finality provider - let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), false) - .unwrap() - .delegations; - assert_eq!(dels1.len(), 2); - - // Query active delegations by finality provider - let dels1 = crate::queries::active_delegations_by_fp(deps.as_ref(), fp1_pk.clone(), true) - .unwrap() - .delegations; - assert_eq!(dels1.len(), 1); - let err = crate::queries::active_delegations_by_fp(deps.as_ref(), "f2".to_string(), false) - .unwrap_err(); - assert!(matches!(err, ContractError::Std(NotFound { .. }))); - } - - #[test] - fn test_fp_info() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - let initial_env = crate::finality::tests::mock_env_height(10); - - instantiate( - deps.as_mut(), - initial_env.clone(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a finality provider - let new_fp1 = create_new_finality_provider(1); - let fp1_pk = new_fp1.btc_pk_hex.clone(); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); - - // Add a couple delegations - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[1]); - - // Adjust staking amounts - del1.total_sat = 100; - del2.total_sat = 150; - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env_height(11), info.clone(), msg).unwrap(); - - // Query finality provider info - let fp = - crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), None).unwrap(); - assert_eq!( - fp, - FinalityProviderInfo { - btc_pk_hex: fp1_pk.clone(), - power: 250, - } - ); - - // Query finality provider info with same height as execute call - let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(11)) - .unwrap(); - assert_eq!( - fp, - FinalityProviderInfo { - btc_pk_hex: fp1_pk.clone(), - power: 0, // Historical data is not checkpoint yet - } - ); - - // Query finality provider info with past height as execute call - let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(12)) - .unwrap(); - assert_eq!( - fp, - FinalityProviderInfo { - btc_pk_hex: fp1_pk.clone(), - power: 250, - } - ); - - // Query finality provider info with some larger height - let fp = crate::queries::finality_provider_info(deps.as_ref(), fp1_pk.clone(), Some(1000)) - .unwrap(); - assert_eq!( - fp, - FinalityProviderInfo { - btc_pk_hex: fp1_pk.clone(), - power: 250, - } - ); - } - - #[test] - fn test_fp_info_raw_query() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a finality provider - let new_fp1 = create_new_finality_provider(1); - let fp1_pk = new_fp1.btc_pk_hex.clone(); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add a delegation - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1]); - // Adjust staking amount - del1.total_sat = 100; - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Build raw key - let prefixed_key = namespace_with_key(&[FP_STATE_KEY.as_bytes()], fp1_pk.as_bytes()); - // Read directly from storage - let fp_state_raw = deps.storage.get(&prefixed_key).unwrap(); - // Deserialize result - let fp_state: FinalityProviderState = from_json(fp_state_raw).unwrap(); - - assert_eq!(fp_state, FinalityProviderState { power: 100 }); - } - - #[test] - fn test_fps_by_power() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - let params = get_params(); - PARAMS.save(deps.as_mut().storage, ¶ms).unwrap(); - - // Add a couple finality providers - let new_fp1 = create_new_finality_provider(1); - let fp1_pk = new_fp1.btc_pk_hex.clone(); - let new_fp2 = create_new_finality_provider(2); - let fp2_pk = new_fp2.btc_pk_hex.clone(); - let new_fp3 = create_new_finality_provider(3); - let fp3_pk = new_fp3.btc_pk_hex.clone(); - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![new_fp1.clone(), new_fp2.clone(), new_fp3.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Add some delegations - let mut del1 = crate::contract::tests::get_derived_btc_delegation(1, &[1, 3]); - let mut del2 = crate::contract::tests::get_derived_btc_delegation(2, &[2]); - let mut del3 = crate::contract::tests::get_derived_btc_delegation(3, &[2]); - - // Adjust staking amounts - del1.total_sat = 100; - del2.total_sat = 150; - del3.total_sat = 75; - - let msg = ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone(), del2.clone(), del3], - slashed_del: vec![], - unbonded_del: vec![], - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Query finality providers by power - let fps = crate::queries::finality_providers_by_power(deps.as_ref(), None, None) - .unwrap() - .fps; - assert_eq!(fps.len(), 3); - assert_eq!(fps[0], { - FinalityProviderInfo { - btc_pk_hex: fp2_pk.clone(), - power: 225, - } - }); - // fp1 and fp3 can be in arbitrary order - let fp1_info = FinalityProviderInfo { - btc_pk_hex: fp1_pk.clone(), - power: 100, - }; - let fp3_info = FinalityProviderInfo { - btc_pk_hex: fp3_pk.clone(), - power: 100, - }; - assert!( - (fps[1] == fp1_info && fps[2] == fp3_info) - || (fps[1] == fp3_info && fps[2] == fp1_info) - ); - - // Query finality providers power with limit - let fps = crate::queries::finality_providers_by_power(deps.as_ref(), None, Some(2)) - .unwrap() - .fps; - assert_eq!(fps.len(), 2); - assert_eq!(fps[0], { - FinalityProviderInfo { - btc_pk_hex: fp2_pk.clone(), - power: 225, - } - }); - assert!(fps[1] == fp1_info || fps[1] == fp3_info); - - // Query finality providers power with start_after - let fps = - crate::queries::finality_providers_by_power(deps.as_ref(), Some(fps[1].clone()), None) - .unwrap() - .fps; - assert_eq!(fps.len(), 1); - assert!(fps[0] == fp1_info || fps[0] == fp3_info); - } -} From 00efde32aa5d7f4a9d87b9d7ed89d9b806976793 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:49:47 +0200 Subject: [PATCH 26/51] Ignore broken (no multi-test) tests --- contracts/btc-finality/src/finality.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 454a1759..d0874c0f 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -655,6 +655,7 @@ mod tests { } #[test] + #[ignore] fn commit_public_randomness_works() { let mut deps = mock_dependencies(); let info = message_info(&deps.api.addr_make(CREATOR), &[]); @@ -704,6 +705,7 @@ mod tests { } #[test] + #[ignore] fn finality_signature_happy_path() { let mut deps = mock_dependencies(); let info = message_info(&deps.api.addr_make(CREATOR), &[]); @@ -842,6 +844,7 @@ mod tests { } #[test] + #[ignore] fn finality_round_works() { let mut deps = mock_dependencies(); let info = message_info(&deps.api.addr_make(CREATOR), &[]); @@ -1010,6 +1013,7 @@ mod tests { } #[test] + #[ignore] fn slashing_works() { let mut deps = mock_dependencies(); let info = message_info(&deps.api.addr_make(CREATOR), &[]); From d3aece78199f3951a8576532c6426b75e78c1a68 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:58:03 +0200 Subject: [PATCH 27/51] Fix / unify schema bin names --- contracts/btc-finality/.cargo/config.toml | 4 ++++ contracts/btc-finality/Cargo.toml | 2 +- contracts/btc-staking/.cargo/config.toml | 2 +- contracts/btc-staking/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 contracts/btc-finality/.cargo/config.toml diff --git a/contracts/btc-finality/.cargo/config.toml b/contracts/btc-finality/.cargo/config.toml new file mode 100644 index 00000000..3d69bbce --- /dev/null +++ b/contracts/btc-finality/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --bin btc-finality-schema" diff --git a/contracts/btc-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml index f7c107d8..52c5b5f2 100644 --- a/contracts/btc-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -14,7 +14,7 @@ crate-type = ["cdylib", "rlib"] doctest = false [[bin]] -name = "finality-schema" +name = "btc-finality-schema" path = "src/bin/schema.rs" test = false diff --git a/contracts/btc-staking/.cargo/config.toml b/contracts/btc-staking/.cargo/config.toml index 6cd8153d..06557618 100644 --- a/contracts/btc-staking/.cargo/config.toml +++ b/contracts/btc-staking/.cargo/config.toml @@ -1,4 +1,4 @@ [alias] unit-test = "test --lib" integration-test = "test --test integration" -schema = "run --bin btcstaking-schema" +schema = "run --bin btc-staking-schema" diff --git a/contracts/btc-staking/Cargo.toml b/contracts/btc-staking/Cargo.toml index 5cd447e0..6a2c4178 100644 --- a/contracts/btc-staking/Cargo.toml +++ b/contracts/btc-staking/Cargo.toml @@ -22,7 +22,7 @@ crate-type = ["cdylib", "rlib"] doctest = false [[bin]] -name = "btcstaking-schema" +name = "btc-staking-schema" path = "src/bin/schema.rs" test = false From 6124b939a1b360a5ed83300af24f328d81b09a93 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:58:23 +0200 Subject: [PATCH 28/51] Update btc-finality schemas --- .../btc-finality/schema/btc-finality.json | 1841 +++++++++++++++++ .../btc-finality/schema/raw/execute.json | 477 +++++ .../btc-finality/schema/raw/instantiate.json | 92 + .../btc-finality/schema/raw/migrate.json | 6 + contracts/btc-finality/schema/raw/query.json | 280 +++ .../raw/response_to_activated_height.json | 16 + .../schema/raw/response_to_admin.json | 15 + .../schema/raw/response_to_config.json | 25 + .../schema/raw/response_to_delegation.json | 225 ++ .../schema/raw/response_to_delegations.json | 260 +++ .../raw/response_to_delegations_by_f_p.json | 17 + .../raw/response_to_finality_provider.json | 137 ++ .../response_to_finality_provider_info.json | 22 + .../raw/response_to_finality_providers.json | 152 ++ ...sponse_to_finality_providers_by_power.json | 39 + .../schema/raw/response_to_params.json | 70 + 16 files changed, 3674 insertions(+) create mode 100644 contracts/btc-finality/schema/btc-finality.json create mode 100644 contracts/btc-finality/schema/raw/execute.json create mode 100644 contracts/btc-finality/schema/raw/instantiate.json create mode 100644 contracts/btc-finality/schema/raw/migrate.json create mode 100644 contracts/btc-finality/schema/raw/query.json create mode 100644 contracts/btc-finality/schema/raw/response_to_activated_height.json create mode 100644 contracts/btc-finality/schema/raw/response_to_admin.json create mode 100644 contracts/btc-finality/schema/raw/response_to_config.json create mode 100644 contracts/btc-finality/schema/raw/response_to_delegation.json create mode 100644 contracts/btc-finality/schema/raw/response_to_delegations.json create mode 100644 contracts/btc-finality/schema/raw/response_to_delegations_by_f_p.json create mode 100644 contracts/btc-finality/schema/raw/response_to_finality_provider.json create mode 100644 contracts/btc-finality/schema/raw/response_to_finality_provider_info.json create mode 100644 contracts/btc-finality/schema/raw/response_to_finality_providers.json create mode 100644 contracts/btc-finality/schema/raw/response_to_finality_providers_by_power.json create mode 100644 contracts/btc-finality/schema/raw/response_to_params.json diff --git a/contracts/btc-finality/schema/btc-finality.json b/contracts/btc-finality/schema/btc-finality.json new file mode 100644 index 00000000..9bc2b7bc --- /dev/null +++ b/contracts/btc-finality/schema/btc-finality.json @@ -0,0 +1,1841 @@ +{ + "contract_name": "btc-finality", + "contract_version": "0.9.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "params": { + "anyOf": [ + { + "$ref": "#/definitions/Params" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + }, + "Params": { + "description": "Params define Consumer-selectable BTC staking parameters", + "type": "object", + "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", + "max_active_finality_providers", + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" + ], + "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_active_finality_providers": { + "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_pub_rand": { + "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "btc_staking execution handlers", + "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "BTC Staking operations", + "type": "object", + "required": [ + "btc_staking" + ], + "properties": { + "btc_staking": { + "type": "object", + "required": [ + "active_del", + "new_fp", + "slashed_del", + "unbonded_del" + ], + "properties": { + "active_del": { + "type": "array", + "items": { + "$ref": "#/definitions/ActiveBtcDelegation" + } + }, + "new_fp": { + "type": "array", + "items": { + "$ref": "#/definitions/NewFinalityProvider" + } + }, + "slashed_del": { + "type": "array", + "items": { + "$ref": "#/definitions/SlashedBtcDelegation" + } + }, + "unbonded_del": { + "type": "array", + "items": { + "$ref": "#/definitions/UnbondedBtcDelegation" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Slash finality provider staking power. Used by the babylon-contract only. The Babylon contract will call this message to set the finality provider's staking power to zero when the finality provider is found to be malicious by the finality contract.", + "type": "object", + "required": [ + "slash" + ], + "properties": { + "slash": { + "type": "object", + "required": [ + "fp_btc_pk_hex" + ], + "properties": { + "fp_btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "ActiveBtcDelegation": { + "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "BtcUndelegationInfo": { + "description": "BTCUndelegationInfo provides all necessary info about the undelegation", + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "description": "CovenantAdaptorSignatures is a list adaptor signatures signed by the covenant with different finality provider's public keys as encryption keys", + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "NewFinalityProvider": { + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "$ref": "#/definitions/Binary" + }, + "sig": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "SlashedBtcDelegation": { + "description": "SlashedBTCDelegation is a packet sent from Babylon to the Consumer chain about a slashed BTC delegation re-staked to >=1 of the Consumer chain's finality providers", + "type": "object", + "required": [ + "recovered_fp_btc_sk", + "staking_tx_hash" + ], + "properties": { + "recovered_fp_btc_sk": { + "description": "recovered_fp_btc_sk is the extracted BTC SK of the finality provider on this Consumer chain", + "type": "string" + }, + "staking_tx_hash": { + "description": "staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation", + "type": "string" + } + }, + "additionalProperties": false + }, + "UnbondedBtcDelegation": { + "description": "UnbondedBTCDelegation is sent from Babylon to the Consumer chain upon an early unbonded BTC delegation", + "type": "object", + "required": [ + "staking_tx_hash", + "unbonding_tx_sig" + ], + "properties": { + "staking_tx_hash": { + "description": "staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation", + "type": "string" + }, + "unbonding_tx_sig": { + "description": "unbonding_tx_sig is the signature on the unbonding tx signed by the BTC delegator It proves that the BTC delegator wants to unbond", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "`Config` returns the current configuration of the btc-staking contract", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Params` returns the current Consumer-specific parameters of the btc-staking contract", + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Admin` returns the current admin of the contract", + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProvider` returns the finality provider by its BTC public key, in hex format", + "type": "object", + "required": [ + "finality_provider" + ], + "properties": { + "finality_provider": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProviders` returns the list of registered finality providers\n\n`start_after` is the BTC public key of the FP to start after, or `None` to start from the beginning", + "type": "object", + "required": [ + "finality_providers" + ], + "properties": { + "finality_providers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Delegation` returns delegation information by its staking tx hash, in hex format", + "type": "object", + "required": [ + "delegation" + ], + "properties": { + "delegation": { + "type": "object", + "required": [ + "staking_tx_hash_hex" + ], + "properties": { + "staking_tx_hash_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Delegations` return the list of delegations\n\n`start_after` is the staking tx hash (in hex format) of the delegation to start after, or `None` to start from the beginning. `limit` is the maximum number of delegations to return. `active` is an optional filter to return only active delegations", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "type": "object", + "properties": { + "active": { + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`DelegationsByFP` returns the list of staking tx hashes (in hex format) corresponding to delegations, for a given finality provider.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format. The hashes are returned in hex format", + "type": "object", + "required": [ + "delegations_by_f_p" + ], + "properties": { + "delegations_by_f_p": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProviderInfo` returns the finality provider information by its BTC public key, in hex format The information includes the aggregated power of the finality provider.\n\n`height` is the optional block height at which the power is being aggregated. If `height` is not provided, the latest aggregated power is returned", + "type": "object", + "required": [ + "finality_provider_info" + ], + "properties": { + "finality_provider_info": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProvidersByPower` returns the list of finality provider infos sorted by their aggregated power, in descending order.\n\n`start_after` is the BTC public key of the FP to start after, or `None` to start from the top", + "type": "object", + "required": [ + "finality_providers_by_power" + ], + "properties": { + "finality_providers_by_power": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`ActivatedHeight` returns the height at which the contract gets its first delegation, if any", + "type": "object", + "required": [ + "activated_height" + ], + "properties": { + "activated_height": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "FinalityProviderInfo": { + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "sudo": null, + "responses": { + "activated_height": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActivatedHeightResponse", + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "admin": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "Config are Babylon-selectable BTC staking configuration", + "type": "object", + "required": [ + "babylon", + "denom" + ], + "properties": { + "babylon": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } + }, + "delegation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveBtcDelegation", + "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "BtcUndelegationInfo": { + "description": "BTCUndelegationInfo provides all necessary info about the undelegation", + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "description": "CovenantAdaptorSignatures is a list adaptor signatures signed by the covenant with different finality provider's public keys as encryption keys", + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "$ref": "#/definitions/Binary" + }, + "sig": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + } + }, + "delegations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BtcDelegationsResponse", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "type": "array", + "items": { + "$ref": "#/definitions/BtcDelegation" + } + } + }, + "additionalProperties": false, + "definitions": { + "BtcDelegation": { + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashed", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashed": { + "description": "slashed is used to indicate whether a given delegation is related to a slashed FP", + "type": "boolean" + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false + }, + "BtcUndelegationInfo": { + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "sig": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + } + }, + "delegations_by_f_p": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DelegationsByFPResponse", + "type": "object", + "required": [ + "hashes" + ], + "properties": { + "hashes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "finality_provider": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvider", + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id", + "slashed_btc_height", + "slashed_height" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + }, + "slashed_btc_height": { + "description": "slashed_btc_height is the BTC height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashed_height": { + "description": "slashed_height is the height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + } + }, + "finality_provider_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProviderInfo", + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "finality_providers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvidersResponse", + "type": "object", + "required": [ + "fps" + ], + "properties": { + "fps": { + "type": "array", + "items": { + "$ref": "#/definitions/FinalityProvider" + } + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProvider": { + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id", + "slashed_btc_height", + "slashed_height" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + }, + "slashed_btc_height": { + "description": "slashed_btc_height is the BTC height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashed_height": { + "description": "slashed_height is the height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + } + }, + "finality_providers_by_power": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvidersByPowerResponse", + "type": "object", + "required": [ + "fps" + ], + "properties": { + "fps": { + "type": "array", + "items": { + "$ref": "#/definitions/FinalityProviderInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "FinalityProviderInfo": { + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "params": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Params", + "description": "Params define Consumer-selectable BTC staking parameters", + "type": "object", + "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", + "max_active_finality_providers", + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" + ], + "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_active_finality_providers": { + "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_pub_rand": { + "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + } + } + } + } +} diff --git a/contracts/btc-finality/schema/raw/execute.json b/contracts/btc-finality/schema/raw/execute.json new file mode 100644 index 00000000..11b583e7 --- /dev/null +++ b/contracts/btc-finality/schema/raw/execute.json @@ -0,0 +1,477 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "btc_staking execution handlers", + "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "BTC Staking operations", + "type": "object", + "required": [ + "btc_staking" + ], + "properties": { + "btc_staking": { + "type": "object", + "required": [ + "active_del", + "new_fp", + "slashed_del", + "unbonded_del" + ], + "properties": { + "active_del": { + "type": "array", + "items": { + "$ref": "#/definitions/ActiveBtcDelegation" + } + }, + "new_fp": { + "type": "array", + "items": { + "$ref": "#/definitions/NewFinalityProvider" + } + }, + "slashed_del": { + "type": "array", + "items": { + "$ref": "#/definitions/SlashedBtcDelegation" + } + }, + "unbonded_del": { + "type": "array", + "items": { + "$ref": "#/definitions/UnbondedBtcDelegation" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Slash finality provider staking power. Used by the babylon-contract only. The Babylon contract will call this message to set the finality provider's staking power to zero when the finality provider is found to be malicious by the finality contract.", + "type": "object", + "required": [ + "slash" + ], + "properties": { + "slash": { + "type": "object", + "required": [ + "fp_btc_pk_hex" + ], + "properties": { + "fp_btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "ActiveBtcDelegation": { + "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "BtcUndelegationInfo": { + "description": "BTCUndelegationInfo provides all necessary info about the undelegation", + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "description": "CovenantAdaptorSignatures is a list adaptor signatures signed by the covenant with different finality provider's public keys as encryption keys", + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "NewFinalityProvider": { + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "$ref": "#/definitions/Binary" + }, + "sig": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + "SlashedBtcDelegation": { + "description": "SlashedBTCDelegation is a packet sent from Babylon to the Consumer chain about a slashed BTC delegation re-staked to >=1 of the Consumer chain's finality providers", + "type": "object", + "required": [ + "recovered_fp_btc_sk", + "staking_tx_hash" + ], + "properties": { + "recovered_fp_btc_sk": { + "description": "recovered_fp_btc_sk is the extracted BTC SK of the finality provider on this Consumer chain", + "type": "string" + }, + "staking_tx_hash": { + "description": "staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation", + "type": "string" + } + }, + "additionalProperties": false + }, + "UnbondedBtcDelegation": { + "description": "UnbondedBTCDelegation is sent from Babylon to the Consumer chain upon an early unbonded BTC delegation", + "type": "object", + "required": [ + "staking_tx_hash", + "unbonding_tx_sig" + ], + "properties": { + "staking_tx_hash": { + "description": "staking tx hash of the BTC delegation. It uniquely identifies a BTC delegation", + "type": "string" + }, + "unbonding_tx_sig": { + "description": "unbonding_tx_sig is the signature on the unbonding tx signed by the BTC delegator It proves that the BTC delegator wants to unbond", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/instantiate.json b/contracts/btc-finality/schema/raw/instantiate.json new file mode 100644 index 00000000..3c553424 --- /dev/null +++ b/contracts/btc-finality/schema/raw/instantiate.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "params": { + "anyOf": [ + { + "$ref": "#/definitions/Params" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + }, + "Params": { + "description": "Params define Consumer-selectable BTC staking parameters", + "type": "object", + "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", + "max_active_finality_providers", + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" + ], + "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_active_finality_providers": { + "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_pub_rand": { + "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/migrate.json b/contracts/btc-finality/schema/raw/migrate.json new file mode 100644 index 00000000..2bba9582 --- /dev/null +++ b/contracts/btc-finality/schema/raw/migrate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" +} diff --git a/contracts/btc-finality/schema/raw/query.json b/contracts/btc-finality/schema/raw/query.json new file mode 100644 index 00000000..891e0773 --- /dev/null +++ b/contracts/btc-finality/schema/raw/query.json @@ -0,0 +1,280 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "`Config` returns the current configuration of the btc-staking contract", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Params` returns the current Consumer-specific parameters of the btc-staking contract", + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Admin` returns the current admin of the contract", + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProvider` returns the finality provider by its BTC public key, in hex format", + "type": "object", + "required": [ + "finality_provider" + ], + "properties": { + "finality_provider": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProviders` returns the list of registered finality providers\n\n`start_after` is the BTC public key of the FP to start after, or `None` to start from the beginning", + "type": "object", + "required": [ + "finality_providers" + ], + "properties": { + "finality_providers": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Delegation` returns delegation information by its staking tx hash, in hex format", + "type": "object", + "required": [ + "delegation" + ], + "properties": { + "delegation": { + "type": "object", + "required": [ + "staking_tx_hash_hex" + ], + "properties": { + "staking_tx_hash_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`Delegations` return the list of delegations\n\n`start_after` is the staking tx hash (in hex format) of the delegation to start after, or `None` to start from the beginning. `limit` is the maximum number of delegations to return. `active` is an optional filter to return only active delegations", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "type": "object", + "properties": { + "active": { + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`DelegationsByFP` returns the list of staking tx hashes (in hex format) corresponding to delegations, for a given finality provider.\n\n`btc_pk_hex` is the BTC public key of the finality provider, in hex format. The hashes are returned in hex format", + "type": "object", + "required": [ + "delegations_by_f_p" + ], + "properties": { + "delegations_by_f_p": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProviderInfo` returns the finality provider information by its BTC public key, in hex format The information includes the aggregated power of the finality provider.\n\n`height` is the optional block height at which the power is being aggregated. If `height` is not provided, the latest aggregated power is returned", + "type": "object", + "required": [ + "finality_provider_info" + ], + "properties": { + "finality_provider_info": { + "type": "object", + "required": [ + "btc_pk_hex" + ], + "properties": { + "btc_pk_hex": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`FinalityProvidersByPower` returns the list of finality provider infos sorted by their aggregated power, in descending order.\n\n`start_after` is the BTC public key of the FP to start after, or `None` to start from the top", + "type": "object", + "required": [ + "finality_providers_by_power" + ], + "properties": { + "finality_providers_by_power": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "`ActivatedHeight` returns the height at which the contract gets its first delegation, if any", + "type": "object", + "required": [ + "activated_height" + ], + "properties": { + "activated_height": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "FinalityProviderInfo": { + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_activated_height.json b/contracts/btc-finality/schema/raw/response_to_activated_height.json new file mode 100644 index 00000000..2854be08 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_activated_height.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActivatedHeightResponse", + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/btc-finality/schema/raw/response_to_admin.json b/contracts/btc-finality/schema/raw/response_to_admin.json new file mode 100644 index 00000000..c73969ab --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_admin.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/contracts/btc-finality/schema/raw/response_to_config.json b/contracts/btc-finality/schema/raw/response_to_config.json new file mode 100644 index 00000000..96e6cbe3 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_config.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "Config are Babylon-selectable BTC staking configuration", + "type": "object", + "required": [ + "babylon", + "denom" + ], + "properties": { + "babylon": { + "$ref": "#/definitions/Addr" + }, + "denom": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_delegation.json b/contracts/btc-finality/schema/raw/response_to_delegation.json new file mode 100644 index 00000000..f20b76c1 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_delegation.json @@ -0,0 +1,225 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveBtcDelegation", + "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "BtcUndelegationInfo": { + "description": "BTCUndelegationInfo provides all necessary info about the undelegation", + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "description": "CovenantAdaptorSignatures is a list adaptor signatures signed by the covenant with different finality provider's public keys as encryption keys", + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "$ref": "#/definitions/Binary" + }, + "sig": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_delegations.json b/contracts/btc-finality/schema/raw/response_to_delegations.json new file mode 100644 index 00000000..9cb87334 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_delegations.json @@ -0,0 +1,260 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BtcDelegationsResponse", + "type": "object", + "required": [ + "delegations" + ], + "properties": { + "delegations": { + "type": "array", + "items": { + "$ref": "#/definitions/BtcDelegation" + } + } + }, + "additionalProperties": false, + "definitions": { + "BtcDelegation": { + "type": "object", + "required": [ + "btc_pk_hex", + "covenant_sigs", + "delegator_slashing_sig", + "end_height", + "fp_btc_pk_list", + "params_version", + "slashed", + "slashing_tx", + "staker_addr", + "staking_output_idx", + "staking_tx", + "start_height", + "total_sat", + "unbonding_time", + "undelegation_info" + ], + "properties": { + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "covenant_sigs": { + "description": "covenant_sigs is a list of adaptor signatures on the slashing tx by each covenant member. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk) as string hex. It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "end_height": { + "description": "end_height is the end height of the BTC delegation it is the end BTC height of the time-lock - w", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fp_btc_pk_list": { + "description": "fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that this BTC delegation delegates to", + "type": "array", + "items": { + "type": "string" + } + }, + "params_version": { + "description": "params version used to validate the delegation", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "slashed": { + "description": "slashed is used to indicate whether a given delegation is related to a slashed FP", + "type": "boolean" + }, + "slashing_tx": { + "description": "slashing_tx is the slashing tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "staker_addr": { + "description": "staker_addr is the address to receive rewards from BTC delegation", + "type": "string" + }, + "staking_output_idx": { + "description": "staking_output_idx is the index of the staking output in the staking tx", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "staking_tx": { + "description": "staking_tx is the staking tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "start_height": { + "description": "start_height is the start BTC height of the BTC delegation. It is the start BTC height of the time-lock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_sat": { + "description": "total_sat is the total BTC stakes in this delegation, quantified in satoshi", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "unbonding_time": { + "description": "unbonding_time is used in unbonding output time-lock path and in slashing transactions change outputs", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "undelegation_info": { + "description": "undelegation_info is the undelegation info of this delegation.", + "allOf": [ + { + "$ref": "#/definitions/BtcUndelegationInfo" + } + ] + } + }, + "additionalProperties": false + }, + "BtcUndelegationInfo": { + "type": "object", + "required": [ + "covenant_slashing_sigs", + "covenant_unbonding_sig_list", + "delegator_slashing_sig", + "delegator_unbonding_sig", + "slashing_tx", + "unbonding_tx" + ], + "properties": { + "covenant_slashing_sigs": { + "description": "covenant_slashing_sigs is a list of adaptor signatures on the unbonding slashing tx by each covenant member It will be a part of the witness for the staking tx output.", + "type": "array", + "items": { + "$ref": "#/definitions/CovenantAdaptorSignatures" + } + }, + "covenant_unbonding_sig_list": { + "description": "covenant_unbonding_sig_list is the list of signatures on the unbonding tx by covenant members", + "type": "array", + "items": { + "$ref": "#/definitions/SignatureInfo" + } + }, + "delegator_slashing_sig": { + "description": "delegator_slashing_sig is the signature on the slashing tx by the delegator (i.e. SK corresponding to btc_pk). It will be a part of the witness for the unbonding tx output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "delegator_unbonding_sig": { + "description": "delegator_unbonding_sig is the signature on the unbonding tx by the delegator (i.e. SK corresponding to btc_pk). It effectively proves that the delegator wants to unbond and thus Babylon will consider this BTC delegation unbonded. Delegator's BTC on Bitcoin will be unbonded after time-lock.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "slashing_tx": { + "description": "slashing_tx is the unbonding slashing tx", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "unbonding_tx": { + "description": "unbonding_tx is the transaction which will transfer the funds from staking output to unbonding output. Unbonding output will usually have lower timelock than staking output.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, + "CovenantAdaptorSignatures": { + "type": "object", + "required": [ + "adaptor_sigs", + "cov_pk" + ], + "properties": { + "adaptor_sigs": { + "description": "adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "cov_pk": { + "description": "cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, + "SignatureInfo": { + "type": "object", + "required": [ + "pk", + "sig" + ], + "properties": { + "pk": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "sig": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_delegations_by_f_p.json b/contracts/btc-finality/schema/raw/response_to_delegations_by_f_p.json new file mode 100644 index 00000000..c91c2ccd --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_delegations_by_f_p.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DelegationsByFPResponse", + "type": "object", + "required": [ + "hashes" + ], + "properties": { + "hashes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/contracts/btc-finality/schema/raw/response_to_finality_provider.json b/contracts/btc-finality/schema/raw/response_to_finality_provider.json new file mode 100644 index 00000000..72127516 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_finality_provider.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvider", + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id", + "slashed_btc_height", + "slashed_height" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + }, + "slashed_btc_height": { + "description": "slashed_btc_height is the BTC height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashed_height": { + "description": "slashed_height is the height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_finality_provider_info.json b/contracts/btc-finality/schema/raw/response_to_finality_provider_info.json new file mode 100644 index 00000000..cc0d3cbc --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_finality_provider_info.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProviderInfo", + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false +} diff --git a/contracts/btc-finality/schema/raw/response_to_finality_providers.json b/contracts/btc-finality/schema/raw/response_to_finality_providers.json new file mode 100644 index 00000000..e0f3177f --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_finality_providers.json @@ -0,0 +1,152 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvidersResponse", + "type": "object", + "required": [ + "fps" + ], + "properties": { + "fps": { + "type": "array", + "items": { + "$ref": "#/definitions/FinalityProvider" + } + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "FinalityProvider": { + "type": "object", + "required": [ + "addr", + "btc_pk_hex", + "commission", + "consumer_id", + "slashed_btc_height", + "slashed_height" + ], + "properties": { + "addr": { + "description": "addr is the bech32 address identifier of the finality provider", + "type": "string" + }, + "btc_pk_hex": { + "description": "btc_pk_hex is the Bitcoin secp256k1 PK of this finality provider the PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "commission": { + "description": "commission defines the commission rate of the finality provider.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "consumer_id": { + "description": "consumer_id is the ID of the consumer that the finality provider is operating on.", + "type": "string" + }, + "description": { + "description": "description defines the description terms for the finality provider", + "anyOf": [ + { + "$ref": "#/definitions/FinalityProviderDescription" + }, + { + "type": "null" + } + ] + }, + "pop": { + "description": "pop is the proof of possession of the babylon_pk and btc_pk", + "anyOf": [ + { + "$ref": "#/definitions/ProofOfPossessionBtc" + }, + { + "type": "null" + } + ] + }, + "slashed_btc_height": { + "description": "slashed_btc_height is the BTC height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashed_height": { + "description": "slashed_height is the height on which the finality provider is slashed", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "FinalityProviderDescription": { + "type": "object", + "required": [ + "details", + "identity", + "moniker", + "security_contact", + "website" + ], + "properties": { + "details": { + "description": "details is the details of the finality provider", + "type": "string" + }, + "identity": { + "description": "identity is the identity of the finality provider", + "type": "string" + }, + "moniker": { + "description": "moniker is the name of the finality provider", + "type": "string" + }, + "security_contact": { + "description": "security_contact is the security contact of the finality provider", + "type": "string" + }, + "website": { + "description": "website is the website of the finality provider", + "type": "string" + } + }, + "additionalProperties": false + }, + "ProofOfPossessionBtc": { + "description": "ProofOfPossessionBtc is the proof of possession that a Babylon secp256k1 secret key and a Bitcoin secp256k1 secret key are held by the same person", + "type": "object", + "required": [ + "btc_sig", + "btc_sig_type" + ], + "properties": { + "btc_sig": { + "description": "btc_sig is the signature generated via sign(sk_btc, babylon_sig) the signature follows encoding in either BIP-340 spec or BIP-322 spec", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "btc_sig_type": { + "description": "btc_sig_type indicates the type of btc_sig in the pop", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_finality_providers_by_power.json b/contracts/btc-finality/schema/raw/response_to_finality_providers_by_power.json new file mode 100644 index 00000000..e36afe41 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_finality_providers_by_power.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FinalityProvidersByPowerResponse", + "type": "object", + "required": [ + "fps" + ], + "properties": { + "fps": { + "type": "array", + "items": { + "$ref": "#/definitions/FinalityProviderInfo" + } + } + }, + "additionalProperties": false, + "definitions": { + "FinalityProviderInfo": { + "type": "object", + "required": [ + "btc_pk_hex", + "power" + ], + "properties": { + "btc_pk_hex": { + "description": "`btc_pk_hex` is the Bitcoin secp256k1 PK of this finality provider. The PK follows encoding in BIP-340 spec in hex format", + "type": "string" + }, + "power": { + "description": "`power` is the aggregated power of this finality provider. The power is calculated based on the amount of BTC delegated to this finality provider", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/btc-finality/schema/raw/response_to_params.json b/contracts/btc-finality/schema/raw/response_to_params.json new file mode 100644 index 00000000..614e0fd4 --- /dev/null +++ b/contracts/btc-finality/schema/raw/response_to_params.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Params", + "description": "Params define Consumer-selectable BTC staking parameters", + "type": "object", + "required": [ + "btc_network", + "covenant_pks", + "covenant_quorum", + "max_active_finality_providers", + "min_pub_rand", + "min_slashing_tx_fee_sat", + "slashing_address", + "slashing_rate" + ], + "properties": { + "btc_network": { + "$ref": "#/definitions/Network" + }, + "covenant_pks": { + "type": "array", + "items": { + "type": "string" + } + }, + "covenant_quorum": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "max_active_finality_providers": { + "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_pub_rand": { + "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "min_slashing_tx_fee_sat": { + "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "slashing_address": { + "description": "`slashing_address` is the address that the slashed BTC goes to. The address is in string format on Bitcoin.", + "type": "string" + }, + "slashing_rate": { + "description": "`slashing_rate` determines the portion of the staked amount to be slashed, expressed as a decimal (e.g. 0.5 for 50%).", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Network": { + "type": "string", + "enum": [ + "mainnet", + "testnet", + "signet", + "regtest" + ] + } + } +} From 9af350c4faca7add86af5fbcec67ce0d7349608b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 11:58:53 +0200 Subject: [PATCH 29/51] Add btc-finality to CI releases --- .github/workflows/deploy.yml | 15 ++++++++++++++- .github/workflows/wasm-tests.yml | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ee4628d5..80510de9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,6 +26,7 @@ jobs: path: |- artifacts/babylon_contract.wasm artifacts/btc_staking.wasm + artifacts/btc_finality.wasm artifacts/checksums.txt - name: Show data run: |- @@ -35,6 +36,7 @@ jobs: run: |- zip artifacts/babylon_contract.wasm.zip artifacts/babylon_contract.wasm zip artifacts/btc_staking.wasm.zip artifacts/btc_staking.wasm + zip artifacts/btc_finality.wasm.zip artifacts/btc_finality.wasm zip artifacts/op_finality_gadget.wasm.zip artifacts/op_finality_gadget.wasm - name: Create a Release id: create-release @@ -68,6 +70,16 @@ jobs: asset_path: ./artifacts/btc_staking.wasm.zip asset_name: btc_staking.wasm.zip asset_content_type: application/zip + - name: Upload btc_finality + id: upload-btc_finality + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: ./artifacts/btc_finality.wasm.zip + asset_name: btc_finality.wasm.zip + asset_content_type: application/zip - name: Upload op_finality_gadget id: upload-op_finality_gadget uses: actions/upload-release-asset@v1 @@ -91,7 +103,8 @@ jobs: - name: Build and run schema generator run: |- (cd ./contracts/babylon && cargo run --bin schema) - (cd ./contracts/btc-staking && cargo run --bin btcstaking-schema) + (cd ./contracts/btc-staking && cargo run --bin btc-staking-schema) + (cd ./contracts/btc-finality && cargo run --bin btc-finality-schema) - name: Consolidate schemas run: |- mkdir -p ./schemas diff --git a/.github/workflows/wasm-tests.yml b/.github/workflows/wasm-tests.yml index 5f8a7a56..b14644e1 100644 --- a/.github/workflows/wasm-tests.yml +++ b/.github/workflows/wasm-tests.yml @@ -25,3 +25,4 @@ jobs: path: |- artifacts/babylon_contract.wasm artifacts/btc_staking.wasm + artifacts/btc_finality.wasm From a7a03ad67dadd67e0512abc5841b0f40beb2c3fa Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Sep 2024 12:02:11 +0200 Subject: [PATCH 30/51] Add btc-finality integration tests --- contracts/btc-finality/tests/integration.rs | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 contracts/btc-finality/tests/integration.rs diff --git a/contracts/btc-finality/tests/integration.rs b/contracts/btc-finality/tests/integration.rs new file mode 100644 index 00000000..08477acf --- /dev/null +++ b/contracts/btc-finality/tests/integration.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::{ContractResult, Response}; +use cosmwasm_vm::testing::{instantiate, mock_env, mock_info, mock_instance}; + +use btc_finality::msg::InstantiateMsg; + +// wasm binary lite version +static WASM: &[u8] = include_bytes!("../../../artifacts/btc_finality.wasm"); +/// Wasm size limit: https://github.com/CosmWasm/wasmd/blob/main/x/wasm/types/validation.go#L24-L25 +const MAX_WASM_SIZE: usize = 800 * 1024; // 800 KB + +const CREATOR: &str = "creator"; + +#[test] +fn wasm_size_limit_check() { + assert!( + WASM.len() < MAX_WASM_SIZE, + "BTC finality contract wasm binary is too large: {} (target: {})", + WASM.len(), + MAX_WASM_SIZE + ); +} + +#[test] +fn instantiate_works() { + let mut deps = mock_instance(WASM, &[]); + + let msg = InstantiateMsg { + params: None, + admin: None, + }; + let info = mock_info(CREATOR, &[]); + let res: ContractResult = instantiate(&mut deps, mock_env(), info, msg); + let msgs = res.unwrap().messages; + assert_eq!(0, msgs.len()); +} From 21a72d99a88dc165423a2589b710f6f2cf16765a Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 20 Sep 2024 09:25:59 +0200 Subject: [PATCH 31/51] Add btc finality to init tests --- Cargo.lock | 1 + contracts/babylon/Cargo.toml | 1 + contracts/babylon/src/multitest.rs | 15 ++++++++----- contracts/babylon/src/multitest/suite.rs | 28 ++++++++++++++++++++---- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 531e6981..c76edfe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "babylon-bitcoin", "babylon-proto", "blst", + "btc-finality", "btc-staking", "cosmos-sdk-proto", "cosmwasm-schema", diff --git a/contracts/babylon/Cargo.toml b/contracts/babylon/Cargo.toml index d499205c..7564e5d9 100644 --- a/contracts/babylon/Cargo.toml +++ b/contracts/babylon/Cargo.toml @@ -50,6 +50,7 @@ ics23 = { workspace = true } [dev-dependencies] babylon-bindings-test = { path = "../../packages/bindings-test" } btc-staking = { path = "../btc-staking", features = [ "library" ] } +btc-finality = { path = "../btc-finality", features = [ "library" ] } test-utils = { path = "../../packages/test-utils" } cosmwasm-vm = { workspace = true } diff --git a/contracts/babylon/src/multitest.rs b/contracts/babylon/src/multitest.rs index 51bdc8cd..9ddefd5f 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -5,8 +5,9 @@ use suite::SuiteBuilder; // Some multi-test default settings // TODO: Replace these with their address generators -const CONTRACT0_ADDR: &str = "cosmwasm1uzyszmsnca8euusre35wuqj4el3hyj8jty84kwln7du5stwwxyns2z5hxp"; +const CONTRACT0_ADDR: &str = "cosmwasm19mfs8tl4s396u7vqw9rrnsmrrtca5r66p7v8jvwdxvjn3shcmllqupdgxu"; const CONTRACT1_ADDR: &str = "cosmwasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s8jef58"; +const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqt8utkp"; const TOKEN: &str = "TOKEN"; #[test] @@ -24,11 +25,17 @@ fn initialization() { assert_eq!(config.checkpoint_finalization_timeout, 10); assert!(!config.notify_cosmos_zone); assert_eq!(config.btc_staking, Some(Addr::unchecked(CONTRACT1_ADDR))); + assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); // Check that the btc-staking contract was initialized correctly let btc_staking_config = suite.get_btc_staking_config(); assert_eq!(btc_staking_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); assert_eq!(btc_staking_config.denom, TOKEN); + + // Check that the btc-finality contract was initialized correctly + let btc_finality_config = suite.get_btc_finality_config(); + assert_eq!(btc_finality_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); + assert_eq!(btc_finality_config.denom, TOKEN); } mod instantiation { @@ -41,13 +48,11 @@ mod instantiation { // Confirm the btc-staking contract has been instantiated and set let config = suite.get_config(); assert_eq!(config.btc_staking, Some(Addr::unchecked(CONTRACT1_ADDR))); + // Confirm the btc-finality contract has been instantiated and set + assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); } } -mod btc_staking {} - -mod slashing {} - mod migration { use super::*; use cosmwasm_std::Empty; diff --git a/contracts/babylon/src/multitest/suite.rs b/contracts/babylon/src/multitest/suite.rs index f4cdcba8..0c9b9929 100644 --- a/contracts/babylon/src/multitest/suite.rs +++ b/contracts/babylon/src/multitest/suite.rs @@ -1,5 +1,5 @@ use crate::msg::contract::{InstantiateMsg, QueryMsg}; -use crate::multitest::CONTRACT1_ADDR; +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; use crate::state::config::Config; use anyhow::Result as AnyResult; use babylon_bindings::BabylonMsg; @@ -18,6 +18,15 @@ fn contract_btc_staking() -> Box> { Box::new(contract) } +fn contract_btc_finality() -> Box> { + let contract = ContractWrapper::new( + btc_finality::contract::execute, + btc_finality::contract::instantiate, + btc_finality::contract::query, + ); + Box::new(contract) +} + fn contract_babylon() -> Box> { let contract = ContractWrapper::new(crate::execute, crate::instantiate, crate::query) .with_reply(crate::reply) @@ -52,8 +61,11 @@ impl SuiteBuilder { app.init_modules(|_router, _api, _storage| -> AnyResult<()> { Ok(()) }) .unwrap(); - let btc_staking_code_id = app.store_code(contract_btc_staking()); - let contract_code_id = app.store_code(contract_babylon()); + let btc_staking_code_id = + app.store_code_with_creator(owner.clone(), contract_btc_staking()); + let btc_finality_code_id = + app.store_code_with_creator(owner.clone(), contract_btc_finality()); + let contract_code_id = app.store_code_with_creator(owner.clone(), contract_babylon()); let contract = app .instantiate_contract( contract_code_id, @@ -66,7 +78,7 @@ impl SuiteBuilder { notify_cosmos_zone: false, btc_staking_code_id: Some(btc_staking_code_id), btc_staking_msg: None, - btc_finality_code_id: None, + btc_finality_code_id: Some(btc_finality_code_id), btc_finality_msg: None, admin: Some(owner.to_string()), consumer_name: Some("TestConsumer".to_string()), @@ -121,6 +133,14 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_btc_finality_config(&self) -> btc_finality::state::config::Config { + self.app + .wrap() + .query_wasm_smart(CONTRACT2_ADDR, &btc_finality::msg::QueryMsg::Config {}) + .unwrap() + } + pub fn migrate(&mut self, addr: &str, msg: Empty) -> AnyResult { self.app.migrate_contract( Addr::unchecked(addr), From 4646f1eccab352bdb9f5847b66f3d6ca8b22eedb Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 22 Sep 2024 09:38:18 +0200 Subject: [PATCH 32/51] Add multitest to btc-finality --- Cargo.lock | 5 + contracts/btc-finality/Cargo.toml | 18 ++- contracts/btc-finality/src/lib.rs | 3 + contracts/btc-finality/src/multitest.rs | 32 ++++ contracts/btc-finality/src/multitest/suite.rs | 149 ++++++++++++++++++ 5 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 contracts/btc-finality/src/multitest.rs create mode 100644 contracts/btc-finality/src/multitest/suite.rs diff --git a/Cargo.lock b/Cargo.lock index c76edfe3..cd3016f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,8 +383,11 @@ dependencies = [ name = "btc-finality" version = "0.9.0" dependencies = [ + "anyhow", + "assert_matches", "babylon-apis", "babylon-bindings", + "babylon-bindings-test", "babylon-bitcoin", "babylon-btcstaking", "babylon-contract", @@ -396,6 +399,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-vm", "cw-controllers", + "cw-multi-test", "cw-storage-plus", "cw-utils", "cw2", @@ -403,6 +407,7 @@ dependencies = [ "eots", "hex", "k256", + "pbjson-types", "prost 0.11.9", "test-utils", "thiserror", diff --git a/contracts/btc-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml index 52c5b5f2..b67d707c 100644 --- a/contracts/btc-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -53,7 +53,17 @@ thiserror = { workspace = true } cw-controllers = { workspace = true } [dev-dependencies] -test-utils = { path = "../../packages/test-utils" } -babylon-proto = { path = "../../packages/proto" } -cosmwasm-vm = { workspace = true } -prost = { workspace = true } +babylon-bindings-test = { path = "../../packages/bindings-test" } +babylon-proto = { path = "../../packages/proto" } +babylon-contract = { path = "../babylon", features = [ "library" ] } +btc-staking = { path = "../btc-staking", features = [ "library" ] } +test-utils = { path = "../../packages/test-utils" } + +cosmwasm-vm = { workspace = true } +cw-multi-test = { workspace = true } + +anyhow = { workspace = true } +assert_matches = { workspace = true } +derivative = { workspace = true } +pbjson-types = { workspace = true } +prost = { workspace = true } \ No newline at end of file diff --git a/contracts/btc-finality/src/lib.rs b/contracts/btc-finality/src/lib.rs index 55a81eab..18690be3 100644 --- a/contracts/btc-finality/src/lib.rs +++ b/contracts/btc-finality/src/lib.rs @@ -5,3 +5,6 @@ pub mod error; pub mod msg; pub mod queries; pub mod state; + +#[cfg(test)] +mod multitest; diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs new file mode 100644 index 00000000..a884eac1 --- /dev/null +++ b/contracts/btc-finality/src/multitest.rs @@ -0,0 +1,32 @@ +mod suite; + +use cosmwasm_std::Addr; +use suite::SuiteBuilder; + +// Some multi-test default settings +// TODO: Replace these with their address generators +const CONTRACT0_ADDR: &str = "cosmwasm19mfs8tl4s396u7vqw9rrnsmrrtca5r66p7v8jvwdxvjn3shcmllqupdgxu"; +const CONTRACT1_ADDR: &str = "cosmwasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s8jef58"; +const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqt8utkp"; + +mod instantiation { + use super::*; + + #[test] + fn instantiate_works() { + let suite = SuiteBuilder::new().build(); + + // Confirm the btc-staking contract has been instantiated and set + let config = suite.get_babylon_config(); + assert_eq!(config.btc_staking, Some(Addr::unchecked(CONTRACT1_ADDR))); + // Confirm the btc-finality contract has been instantiated and set + assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); + // Check that the btc-staking contract was initialized correctly + let btc_staking_config = suite.get_btc_staking_config(); + assert_eq!(btc_staking_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); + + // Check that the btc-finality contract was initialized correctly + let btc_finality_config = suite.get_btc_finality_config(); + assert_eq!(btc_finality_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); + } +} diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs new file mode 100644 index 00000000..d7737fab --- /dev/null +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -0,0 +1,149 @@ +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; +use anyhow::Result as AnyResult; +use babylon_bindings::BabylonMsg; +use babylon_bindings_test::BabylonApp; +use babylon_bitcoin::chain_params::Network; +use cosmwasm_std::Addr; +use cw_multi_test::{Contract, ContractWrapper, Executor}; +use derivative::Derivative; + +fn contract_btc_staking() -> Box> { + let contract = ContractWrapper::new( + btc_staking::contract::execute, + btc_staking::contract::instantiate, + btc_staking::contract::query, + ); + Box::new(contract) +} + +fn contract_btc_finality() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} + +fn contract_babylon() -> Box> { + let contract = ContractWrapper::new( + babylon_contract::execute, + babylon_contract::instantiate, + babylon_contract::query, + ) + .with_reply(babylon_contract::reply) + .with_migrate(babylon_contract::migrate); + Box::new(contract) +} + +#[derive(Derivative)] +#[derivative(Default = "new")] +pub struct SuiteBuilder { + funds: Vec<(Addr, u128)>, +} + +impl SuiteBuilder { + /// Sets initial number of tokens on address + #[allow(dead_code)] + pub fn with_funds(mut self, addr: &str, amount: u128) -> Self { + self.funds.push((Addr::unchecked(addr), amount)); + self + } + + #[track_caller] + pub fn build(self) -> Suite { + let _funds = self.funds; + + let owner = Addr::unchecked("owner"); + + let mut app = BabylonApp::new(owner.as_str()); + + let _block_info = app.block_info(); + + app.init_modules(|_router, _api, _storage| -> AnyResult<()> { Ok(()) }) + .unwrap(); + + let btc_staking_code_id = + app.store_code_with_creator(owner.clone(), contract_btc_staking()); + let btc_finality_code_id = + app.store_code_with_creator(owner.clone(), contract_btc_finality()); + let contract_code_id = app.store_code_with_creator(owner.clone(), contract_babylon()); + let contract = app + .instantiate_contract( + contract_code_id, + owner.clone(), + &babylon_contract::msg::contract::InstantiateMsg { + network: Network::Testnet, + babylon_tag: "01020304".to_string(), + btc_confirmation_depth: 1, + checkpoint_finalization_timeout: 10, + notify_cosmos_zone: false, + btc_staking_code_id: Some(btc_staking_code_id), + btc_staking_msg: None, + btc_finality_code_id: Some(btc_finality_code_id), + btc_finality_msg: None, + admin: Some(owner.to_string()), + consumer_name: Some("TestConsumer".to_string()), + consumer_description: Some("Test Consumer Description".to_string()), + }, + &[], + "babylon", + Some(owner.to_string()), + ) + .unwrap(); + + Suite { + app, + code_id: contract_code_id, + contract, + owner, + } + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Suite { + #[derivative(Debug = "ignore")] + pub app: BabylonApp, + /// The code id of the babylon contract + code_id: u64, + /// Babylon contract address + pub contract: Addr, + /// Admin of babylon and btc-staking contracts + pub owner: Addr, +} + +impl Suite { + #[allow(dead_code)] + pub fn admin(&self) -> &str { + self.owner.as_str() + } + + #[track_caller] + pub fn get_babylon_config(&self) -> babylon_contract::state::config::Config { + self.app + .wrap() + .query_wasm_smart( + self.contract.clone(), + &babylon_contract::msg::contract::QueryMsg::Config {}, + ) + .unwrap() + } + + #[track_caller] + pub fn get_btc_staking_config(&self) -> btc_staking::state::config::Config { + self.app + .wrap() + .query_wasm_smart(CONTRACT1_ADDR, &btc_staking::msg::QueryMsg::Config {}) + .unwrap() + } + + #[track_caller] + pub fn get_btc_finality_config(&self) -> crate::state::config::Config { + self.app + .wrap() + .query_wasm_smart(CONTRACT2_ADDR, &crate::msg::QueryMsg::Config {}) + .unwrap() + } +} From 7a4c1e912cfb274ef2762df4cab620e8779dc302 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 07:38:00 +0200 Subject: [PATCH 33/51] Add convenience contracts fields to suite --- contracts/btc-finality/src/multitest/suite.rs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index d7737fab..97e28205 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -1,11 +1,15 @@ -use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; use anyhow::Result as AnyResult; +use derivative::Derivative; + +use cosmwasm_std::Addr; + +use cw_multi_test::{Contract, ContractWrapper, Executor}; + use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; -use cosmwasm_std::Addr; -use cw_multi_test::{Contract, ContractWrapper, Executor}; -use derivative::Derivative; + +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; fn contract_btc_staking() -> Box> { let contract = ContractWrapper::new( @@ -95,7 +99,9 @@ impl SuiteBuilder { Suite { app, code_id: contract_code_id, - contract, + babylon: contract, + staking: Addr::unchecked(CONTRACT1_ADDR), + finality: Addr::unchecked(CONTRACT2_ADDR), owner, } } @@ -109,7 +115,11 @@ pub struct Suite { /// The code id of the babylon contract code_id: u64, /// Babylon contract address - pub contract: Addr, + pub babylon: Addr, + /// Staking contract address + pub staking: Addr, + /// Finality contract address + pub finality: Addr, /// Admin of babylon and btc-staking contracts pub owner: Addr, } @@ -125,7 +135,7 @@ impl Suite { self.app .wrap() .query_wasm_smart( - self.contract.clone(), + self.babylon.clone(), &babylon_contract::msg::contract::QueryMsg::Config {}, ) .unwrap() From 2f6583e178cce63f3b64727e4811046d3612c6f3 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 08:12:31 +0200 Subject: [PATCH 34/51] Remove unused funds --- contracts/btc-finality/src/multitest/suite.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index 97e28205..ecaab006 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -43,21 +43,11 @@ fn contract_babylon() -> Box> { #[derive(Derivative)] #[derivative(Default = "new")] pub struct SuiteBuilder { - funds: Vec<(Addr, u128)>, } impl SuiteBuilder { - /// Sets initial number of tokens on address - #[allow(dead_code)] - pub fn with_funds(mut self, addr: &str, amount: u128) -> Self { - self.funds.push((Addr::unchecked(addr), amount)); - self - } - #[track_caller] pub fn build(self) -> Suite { - let _funds = self.funds; - let owner = Addr::unchecked("owner"); let mut app = BabylonApp::new(owner.as_str()); From dfc0589085b736b09890694b070e2c1c56c3b6b6 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 09:02:29 +0200 Subject: [PATCH 35/51] Add public randomness commitment mt --- contracts/btc-finality/src/multitest.rs | 23 +++++++++ contracts/btc-finality/src/multitest/suite.rs | 51 ++++++++++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index a884eac1..e3bfa0e5 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -12,6 +12,8 @@ const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv73 mod instantiation { use super::*; + use crate::contract::tests::{create_new_finality_provider, get_public_randomness_commitment}; + #[test] fn instantiate_works() { let suite = SuiteBuilder::new().build(); @@ -29,4 +31,25 @@ mod instantiation { let btc_finality_config = suite.get_btc_finality_config(); assert_eq!(btc_finality_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); } + + #[test] + fn commit_public_randomness_works() { + let mut suite = SuiteBuilder::new().build(); + + // Read public randomness commitment test data + let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); + + // Register one FP + // NOTE: the test data ensures that pub rand commit / finality sig are + // signed by the 1st FP + let new_fp = create_new_finality_provider(1); + assert_eq!(new_fp.btc_pk_hex, pk_hex); + + suite.register_finality_providers(&[new_fp]).unwrap(); + + // Now commit the public randomness for it + suite + .commit_public_randomness(&pk_hex, &pub_rand, &pubrand_signature) + .unwrap(); + } } diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index ecaab006..93354846 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -3,13 +3,14 @@ use derivative::Derivative; use cosmwasm_std::Addr; -use cw_multi_test::{Contract, ContractWrapper, Executor}; - +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; +use babylon_apis::btc_staking_api::NewFinalityProvider; +use babylon_apis::finality_api::PubRandCommit; +use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; - -use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; +use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; fn contract_btc_staking() -> Box> { let contract = ContractWrapper::new( @@ -42,8 +43,7 @@ fn contract_babylon() -> Box> { #[derive(Derivative)] #[derivative(Default = "new")] -pub struct SuiteBuilder { -} +pub struct SuiteBuilder {} impl SuiteBuilder { #[track_caller] @@ -146,4 +146,43 @@ impl Suite { .query_wasm_smart(CONTRACT2_ADDR, &crate::msg::QueryMsg::Config {}) .unwrap() } + + #[track_caller] + pub fn register_finality_providers( + &mut self, + fps: &[NewFinalityProvider], + ) -> anyhow::Result { + self.app.execute_contract( + self.babylon.clone(), + self.staking.clone(), + &btc_staking_api::ExecuteMsg::BtcStaking { + new_fp: fps.to_vec(), + active_del: vec![], + slashed_del: vec![], + unbonded_del: vec![], + }, + &[], + ) + } + + #[track_caller] + pub fn commit_public_randomness( + &mut self, + pk_hex: &str, + pub_rand: &PubRandCommit, + pubrand_signature: &[u8], + ) -> anyhow::Result { + self.app.execute_contract( + Addr::unchecked("anyone"), + self.finality.clone(), + &finality_api::ExecuteMsg::CommitPublicRandomness { + fp_pubkey_hex: pk_hex.to_string(), + start_height: pub_rand.start_height, + num_pub_rand: pub_rand.num_pub_rand, + commitment: pub_rand.commitment.clone().into(), + signature: pubrand_signature.into(), + }, + &[], + ) + } } From c6935e071b9a704fe90072519d3d98466f07152c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 09:02:53 +0200 Subject: [PATCH 36/51] Fix: FP smart query results processing --- contracts/btc-finality/src/finality.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index d0874c0f..aa35c5e8 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -38,16 +38,15 @@ pub fn handle_public_randomness_commit( // TODO: ensure log_2(num_pub_rand) is an integer? // Ensure the finality provider is registered - if !deps.querier.query_wasm_smart( - CONFIG.load(deps.storage)?.staking, - &btc_staking::msg::QueryMsg::FinalityProvider { - btc_pk_hex: fp_pubkey_hex.to_string(), - }, - )? { - return Err(ContractError::FinalityProviderNotFound( - fp_pubkey_hex.to_string(), - )); - } + let _fp: FinalityProvider = deps + .querier + .query_wasm_smart( + CONFIG.load(deps.storage)?.staking, + &btc_staking::msg::QueryMsg::FinalityProvider { + btc_pk_hex: fp_pubkey_hex.to_string(), + }, + ) + .map_err(|_| ContractError::FinalityProviderNotFound(fp_pubkey_hex.to_string()))?; // Verify signature over the list verify_commitment_signature( fp_pubkey_hex, From 8d7f3573c60ca11c4cd4e8f89d581b243ea59c1b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 09:03:52 +0200 Subject: [PATCH 37/51] Remove original test --- contracts/btc-finality/src/finality.rs | 50 -------------------------- 1 file changed, 50 deletions(-) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index aa35c5e8..e010e884 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -653,56 +653,6 @@ mod tests { ) } - #[test] - #[ignore] - fn commit_public_randomness_works() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - instantiate( - deps.as_mut(), - mock_env(), - info.clone(), - InstantiateMsg { - params: None, - admin: None, - }, - ) - .unwrap(); - - // Read public randomness commitment test data - let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); - - // Register one FP - // NOTE: the test data ensures that pub rand commit / finality sig are - // signed by the 1st FP - let new_fp = create_new_finality_provider(1); - assert_eq!(new_fp.btc_pk_hex, pk_hex); - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![new_fp.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let res = - btc_staking::contract::execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Now commit the public randomness for it - let msg = ExecuteMsg::CommitPublicRandomness { - fp_pubkey_hex: pk_hex, - start_height: pub_rand.start_height, - num_pub_rand: pub_rand.num_pub_rand, - commitment: pub_rand.commitment.into(), - signature: pubrand_signature.into(), - }; - - let res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - assert_eq!(0, res.messages.len()); - } - #[test] #[ignore] fn finality_signature_happy_path() { From 6c34f6475bcfe97963f4a92866dc6f604397acff Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 09:33:07 +0200 Subject: [PATCH 38/51] Add TODO --- contracts/btc-finality/src/finality.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index e010e884..d0a6c3ea 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -38,6 +38,7 @@ pub fn handle_public_randomness_commit( // TODO: ensure log_2(num_pub_rand) is an integer? // Ensure the finality provider is registered + // TODO: Use a raw query for performance and cost let _fp: FinalityProvider = deps .querier .query_wasm_smart( From 14cdb0df291c4032f893624b66b4455009c4c6dc Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 12:16:19 +0200 Subject: [PATCH 39/51] Move sudo msg from staking to finality --- contracts/btc-finality/src/contract.rs | 5 ++--- contracts/btc-finality/src/finality.rs | 22 ++++++++++++---------- packages/apis/src/btc_staking_api.rs | 17 ----------------- packages/apis/src/finality_api.rs | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index d0e2f696..017d58b8 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -1,3 +1,5 @@ +use babylon_apis::finality_api::SudoMsg; +use babylon_bindings::BabylonMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -7,9 +9,6 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw_utils::{maybe_addr, nonpayable}; -use babylon_apis::btc_staking_api::SudoMsg; -use babylon_bindings::BabylonMsg; - use btc_staking::msg::ActivatedHeightResponse; use crate::error::ContractError; diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index d0a6c3ea..41ccfb02 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -584,6 +584,18 @@ pub fn list_fps_by_power( #[cfg(test)] mod tests { + use hex::ToHex; + + use cosmwasm_std::testing::{ + message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{to_json_binary, Binary, Env, Event, OwnedDeps, Response, SubMsg, WasmMsg}; + + use babylon_apis::btc_staking_api; + use babylon_apis::finality_api::{ExecuteMsg, IndexedBlock, SudoMsg}; + use babylon_bindings::BabylonMsg; + use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; + use crate::contract::tests::{ create_new_finality_provider, get_derived_btc_delegation, get_params, get_public_randomness_commitment, @@ -592,16 +604,6 @@ mod tests { use crate::error::ContractError; use crate::msg::{FinalitySignatureResponse, InstantiateMsg}; use crate::queries::{block, evidence, finality_signature}; - use babylon_apis::btc_staking_api; - use babylon_apis::btc_staking_api::SudoMsg; - use babylon_apis::finality_api::{ExecuteMsg, IndexedBlock}; - use babylon_bindings::BabylonMsg; - use cosmwasm_std::testing::{ - message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, - }; - use cosmwasm_std::{to_json_binary, Binary, Env, Event, OwnedDeps, Response, SubMsg, WasmMsg}; - use hex::ToHex; - use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; const CREATOR: &str = "creator"; diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index 6f7f8277..b7663fa2 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -26,23 +26,6 @@ pub enum ExecuteMsg { Slash { fp_btc_pk_hex: String }, } -#[cw_serde] -pub enum SudoMsg { - /// The SDK should call SudoMsg::BeginBlock{} once per block (in BeginBlock). - /// It allows the staking module to index the BTC height, and update the power - /// distribution of the active Finality Providers. - BeginBlock { - hash_hex: String, - app_hash_hex: String, - }, - /// The SDK should call SudoMsg::EndBlock{} once per block (in EndBlock). - /// It allows the finality module to index blocks and tally the finality provider votes - EndBlock { - hash_hex: String, - app_hash_hex: String, - }, -} - #[cw_serde] pub struct NewFinalityProvider { /// description defines the description terms for the finality provider diff --git a/packages/apis/src/finality_api.rs b/packages/apis/src/finality_api.rs index c29ac211..38a3c6de 100644 --- a/packages/apis/src/finality_api.rs +++ b/packages/apis/src/finality_api.rs @@ -114,3 +114,20 @@ pub struct Evidence { /// Deserializes to `SchnorrEOTSSig` pub fork_finality_sig: Bytes, } + +#[cw_serde] +pub enum SudoMsg { + /// The SDK should call SudoMsg::BeginBlock{} once per block (in BeginBlock). + /// It allows the staking module to index the BTC height, and update the power + /// distribution of the active Finality Providers. + BeginBlock { + hash_hex: String, + app_hash_hex: String, + }, + /// The SDK should call SudoMsg::EndBlock{} once per block (in EndBlock). + /// It allows the finality module to index blocks and tally the finality provider votes + EndBlock { + hash_hex: String, + app_hash_hex: String, + }, +} From 3380a9d50863d271955229af9587c268a66d7024 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 12:20:16 +0200 Subject: [PATCH 40/51] Add new at height app ctor --- packages/bindings-test/src/multitest.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/bindings-test/src/multitest.rs b/packages/bindings-test/src/multitest.rs index ad5c0192..731cca46 100644 --- a/packages/bindings-test/src/multitest.rs +++ b/packages/bindings-test/src/multitest.rs @@ -165,9 +165,13 @@ impl BabylonApp { } pub fn new_genesis(owner: &str) -> Self { + BabylonApp::new_at_height(owner, 0) + } + + pub fn new_at_height(owner: &str, height: u64) -> Self { let owner = Addr::unchecked(owner); let block_info = BlockInfo { - height: 0, + height, time: Timestamp::from_seconds(1714119228), chain_id: "babylon-testnet-phase-3".to_owned(), }; @@ -208,7 +212,7 @@ impl BabylonApp { } /// This advances BlockInfo by given number of blocks. - /// It does not do any callbacks, but keeps the ratio of seconds/blokc + /// It does not do any callbacks, but keeps the ratio of seconds/block pub fn advance_blocks(&mut self, blocks: u64) { self.update_block(|block| { block.time = block.time.plus_seconds(BLOCK_TIME * blocks); From 1f9f05c8b57bc1b4f9232eaf5e36e67a274cc780 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 12:51:15 +0200 Subject: [PATCH 41/51] Add finality sig happy path mt Remove original (ignored) test --- Cargo.lock | 1 + contracts/btc-finality/Cargo.toml | 3 +- contracts/btc-finality/src/finality.rs | 143 +---------------- contracts/btc-finality/src/multitest.rs | 97 +++++++++++- contracts/btc-finality/src/multitest/suite.rs | 145 +++++++++++++++++- 5 files changed, 238 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd3016f3..8c15d6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,7 @@ dependencies = [ "k256", "pbjson-types", "prost 0.11.9", + "tendermint-proto", "test-utils", "thiserror", ] diff --git a/contracts/btc-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml index b67d707c..8c721216 100644 --- a/contracts/btc-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -66,4 +66,5 @@ anyhow = { workspace = true } assert_matches = { workspace = true } derivative = { workspace = true } pbjson-types = { workspace = true } -prost = { workspace = true } \ No newline at end of file +prost = { workspace = true } +tendermint-proto = { workspace = true } diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 41ccfb02..7d9e7255 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -602,8 +602,8 @@ mod tests { }; use crate::contract::{execute, instantiate}; use crate::error::ContractError; - use crate::msg::{FinalitySignatureResponse, InstantiateMsg}; - use crate::queries::{block, evidence, finality_signature}; + use crate::msg::InstantiateMsg; + use crate::queries::{block, evidence}; const CREATOR: &str = "creator"; @@ -656,145 +656,6 @@ mod tests { ) } - #[test] - #[ignore] - fn finality_signature_happy_path() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - // Read public randomness commitment test data - let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); - let pub_rand_one = get_pub_rand_value(); - // Read equivalent / consistent add finality signature test data - let add_finality_signature = get_add_finality_sig(); - let proof = add_finality_signature.proof.unwrap(); - - let initial_height = pub_rand.start_height; - - let initial_env = mock_env_height(initial_height); - - instantiate( - deps.as_mut(), - initial_env.clone(), - info.clone(), - InstantiateMsg { - params: Some(get_params()), - admin: None, - }, - ) - .unwrap(); - - // Register one FP - // NOTE: the test data ensures that pub rand commit / finality sig are - // signed by the 1st FP - let new_fp = create_new_finality_provider(1); - assert_eq!(new_fp.btc_pk_hex, pk_hex); - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![new_fp.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = - btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) - .unwrap(); - - // Activated height is not set - let res = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); - assert_eq!(res.height, 0); - - // Add a delegation, so that the finality provider has some power - let mut del1 = get_derived_btc_delegation(1, &[1]); - del1.fp_btc_pk_list = vec![pk_hex.clone()]; - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - btc_staking::contract::execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); - - // Activated height is now set - let activated_height = btc_staking::queries::activated_height(deps.as_ref()).unwrap(); - assert_eq!(activated_height.height, initial_height + 1); - - // Submit public randomness commitment for the FP and the involved heights - let msg = ExecuteMsg::CommitPublicRandomness { - fp_pubkey_hex: pk_hex.clone(), - start_height: pub_rand.start_height, - num_pub_rand: pub_rand.num_pub_rand, - commitment: pub_rand.commitment.into(), - signature: pubrand_signature.into(), - }; - - let res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Call the begin-block sudo handler, for completeness - let res = call_begin_block( - &mut deps, - &add_finality_signature.block_app_hash, - initial_height + 1, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(0, res.messages.len()); - assert_eq!(0, res.events.len()); - - // Call the end-block sudo handler, so that the block is indexed in the store - let res = call_end_block( - &mut deps, - &add_finality_signature.block_app_hash, - initial_height + 1, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(0, res.messages.len()); - assert_eq!(1, res.events.len()); - assert_eq!( - res.events[0], - Event::new("index_block") - .add_attribute("module", "finality") - .add_attribute("last_height", (initial_height + 1).to_string()) - ); - - // Submit a finality signature from that finality provider at height initial_height + 1 - let finality_sig = add_finality_signature.finality_sig.to_vec(); - let msg = ExecuteMsg::SubmitFinalitySignature { - fp_pubkey_hex: pk_hex.clone(), - height: initial_height + 1, - pub_rand: pub_rand_one.into(), - proof: proof.into(), - block_hash: add_finality_signature.block_app_hash.to_vec().into(), - signature: Binary::new(finality_sig.clone()), - }; - - // Execute the message at a higher height, so that: - // 1. It's not rejected because of height being too high. - // 2. The FP has consolidated power at such height - let _res = execute( - deps.as_mut(), - mock_env_height(initial_height + 2), - info.clone(), - msg, - ) - .unwrap(); - - // Query finality signature for that exact height - let sig = - finality_signature(deps.as_ref(), pk_hex.to_string(), initial_height + 1).unwrap(); - assert_eq!( - sig, - FinalitySignatureResponse { - signature: finality_sig - } - ); - } - #[test] #[ignore] fn finality_round_works() { diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index e3bfa0e5..ab80cdad 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -12,7 +12,14 @@ const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv73 mod instantiation { use super::*; - use crate::contract::tests::{create_new_finality_provider, get_public_randomness_commitment}; + use test_utils::{get_add_finality_sig, get_pub_rand_value}; + + use cosmwasm_std::Event; + + use crate::contract::tests::{ + create_new_finality_provider, get_derived_btc_delegation, get_public_randomness_commitment, + }; + use crate::msg::FinalitySignatureResponse; #[test] fn instantiate_works() { @@ -52,4 +59,92 @@ mod instantiation { .commit_public_randomness(&pk_hex, &pub_rand, &pubrand_signature) .unwrap(); } + + #[test] + fn finality_signature_happy_path() { + // Read public randomness commitment test data + let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); + let pub_rand_one = get_pub_rand_value(); + // Read equivalent / consistent add finality signature test data + let add_finality_signature = get_add_finality_sig(); + let proof = add_finality_signature.proof.unwrap(); + + let initial_height = pub_rand.start_height; + + let mut suite = SuiteBuilder::new().with_height(initial_height).build(); + + // Register one FP + // NOTE: the test data ensures that pub rand commit / finality sig are + // signed by the 1st FP + let new_fp = create_new_finality_provider(1); + + suite.register_finality_providers(&[new_fp]).unwrap(); + + // Activated height is not set + let res = suite.get_activated_height(); + assert_eq!(res.height, 0); + + // Add a delegation, so that the finality provider has some power + let mut del1 = get_derived_btc_delegation(1, &[1]); + del1.fp_btc_pk_list = vec![pk_hex.clone()]; + + suite.add_delegations(&[del1]).unwrap(); + + // Activated height is now set + let res = suite.get_activated_height(); + assert_eq!(res.height, initial_height + 1); + + suite + .commit_public_randomness(&pk_hex, &pub_rand, &pubrand_signature) + .unwrap(); + + // Call the begin-block sudo handler(s), for completeness + let res = suite + .call_begin_block(&add_finality_signature.block_app_hash, initial_height + 1) + .unwrap(); + assert_eq!(1, res.events.len()); + assert_eq!( + res.events[0], + Event::new("sudo").add_attribute("_contract_address", CONTRACT2_ADDR) + ); + + // Call the end-block sudo handler(s), so that the block is indexed in the store + let res = suite + .call_end_block(&add_finality_signature.block_app_hash, initial_height + 1) + .unwrap(); + assert_eq!(2, res.events.len()); + assert_eq!( + res.events[0], + Event::new("sudo").add_attribute("_contract_address", CONTRACT2_ADDR) + ); + assert_eq!( + res.events[1], + Event::new("wasm-index_block") + .add_attribute("_contract_address", CONTRACT2_ADDR) + .add_attribute("module", "finality") + .add_attribute("last_height", (initial_height + 1).to_string()) + ); + + // Submit a finality signature from that finality provider at height initial_height + 1 + let finality_sig = add_finality_signature.finality_sig.to_vec(); + suite + .submit_finality_signature( + &pk_hex, + initial_height + 1, + &pub_rand_one, + &proof, + &add_finality_signature.block_app_hash, + &finality_sig, + ) + .unwrap(); + + // Query finality signature for that exact height + let sig = suite.get_finality_signature(&pk_hex, initial_height + 1); + assert_eq!( + sig, + FinalitySignatureResponse { + signature: finality_sig + } + ); + } } diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index 93354846..ebad8798 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -1,16 +1,21 @@ use anyhow::Result as AnyResult; use derivative::Derivative; +use hex::ToHex; use cosmwasm_std::Addr; -use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; -use babylon_apis::btc_staking_api::NewFinalityProvider; +use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; + +use babylon_apis::btc_staking_api::{ActiveBtcDelegation, NewFinalityProvider}; use babylon_apis::finality_api::PubRandCommit; use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; -use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; +use btc_staking::msg::ActivatedHeightResponse; + +use crate::msg::FinalitySignatureResponse; +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; fn contract_btc_staking() -> Box> { let contract = ContractWrapper::new( @@ -26,7 +31,8 @@ fn contract_btc_finality() -> Box> { crate::contract::execute, crate::contract::instantiate, crate::contract::query, - ); + ) + .with_sudo(crate::contract::sudo); Box::new(contract) } @@ -43,14 +49,21 @@ fn contract_babylon() -> Box> { #[derive(Derivative)] #[derivative(Default = "new")] -pub struct SuiteBuilder {} +pub struct SuiteBuilder { + height: Option, +} impl SuiteBuilder { + pub fn with_height(mut self, height: u64) -> Self { + self.height = Some(height); + self + } + #[track_caller] pub fn build(self) -> Suite { let owner = Addr::unchecked("owner"); - let mut app = BabylonApp::new(owner.as_str()); + let mut app = BabylonApp::new_at_height(owner.as_str(), self.height.unwrap_or(1)); let _block_info = app.block_info(); @@ -135,7 +148,7 @@ impl Suite { pub fn get_btc_staking_config(&self) -> btc_staking::state::config::Config { self.app .wrap() - .query_wasm_smart(CONTRACT1_ADDR, &btc_staking::msg::QueryMsg::Config {}) + .query_wasm_smart(self.staking.clone(), &btc_staking::msg::QueryMsg::Config {}) .unwrap() } @@ -143,7 +156,32 @@ impl Suite { pub fn get_btc_finality_config(&self) -> crate::state::config::Config { self.app .wrap() - .query_wasm_smart(CONTRACT2_ADDR, &crate::msg::QueryMsg::Config {}) + .query_wasm_smart(self.finality.clone(), &crate::msg::QueryMsg::Config {}) + .unwrap() + } + + #[track_caller] + pub fn get_activated_height(&self) -> ActivatedHeightResponse { + self.app + .wrap() + .query_wasm_smart( + self.staking.clone(), + &btc_staking::msg::QueryMsg::ActivatedHeight {}, + ) + .unwrap() + } + + #[track_caller] + pub fn get_finality_signature(&self, pk_hex: &str, height: u64) -> FinalitySignatureResponse { + self.app + .wrap() + .query_wasm_smart( + self.finality.clone(), + &crate::msg::QueryMsg::FinalitySignature { + btc_pk_hex: pk_hex.to_string(), + height, + }, + ) .unwrap() } @@ -165,6 +203,21 @@ impl Suite { ) } + #[track_caller] + pub fn add_delegations(&mut self, dels: &[ActiveBtcDelegation]) -> anyhow::Result { + self.app.execute_contract( + self.babylon.clone(), + self.staking.clone(), + &btc_staking_api::ExecuteMsg::BtcStaking { + new_fp: vec![], + active_del: dels.to_vec(), + slashed_del: vec![], + unbonded_del: vec![], + }, + &[], + ) + } + #[track_caller] pub fn commit_public_randomness( &mut self, @@ -185,4 +238,80 @@ impl Suite { &[], ) } + + #[track_caller] + pub fn call_begin_block( + &mut self, + app_hash: &[u8], + height: u64, + ) -> anyhow::Result { + // Set the block height + let mut block = self.app.block_info(); + block.height = height; + self.app.set_block(block); + + // Hash is not used in the begin-block handler + let hash_hex = "deadbeef".to_string(); + let app_hash_hex: String = app_hash.encode_hex(); + + self.app.wasm_sudo( + self.finality.clone(), + &finality_api::SudoMsg::BeginBlock { + hash_hex: hash_hex.clone(), + app_hash_hex: app_hash_hex.clone(), + }, + ) + } + + #[track_caller] + pub fn call_end_block(&mut self, app_hash: &[u8], height: u64) -> anyhow::Result { + // Set the block height + let mut block = self.app.block_info(); + block.height = height; + self.app.set_block(block); + + // Hash is not used in the begin-block handler + let hash_hex = "deadbeef".to_string(); + let app_hash_hex: String = app_hash.encode_hex(); + + self.app.wasm_sudo( + self.finality.clone(), + &finality_api::SudoMsg::EndBlock { + hash_hex: hash_hex.clone(), + app_hash_hex: app_hash_hex.clone(), + }, + ) + } + + #[track_caller] + pub fn submit_finality_signature( + &mut self, + pk_hex: &str, + height: u64, + pub_rand: &[u8], + proof: &tendermint_proto::crypto::Proof, + block_hash: &[u8], + finality_sig: &[u8], + ) -> anyhow::Result { + // Execute the message at a higher height, so that: + // 1. It's not rejected because of height being too low. + // 2. The FP has consolidated power at such height + let mut block = self.app.block_info(); + block.height = height + 1; + self.app.set_block(block); + + self.app.execute_contract( + Addr::unchecked("anyone"), + self.finality.clone(), + &finality_api::ExecuteMsg::SubmitFinalitySignature { + fp_pubkey_hex: pk_hex.to_string(), + height, + pub_rand: pub_rand.into(), + proof: proof.into(), + block_hash: block_hash.into(), + signature: finality_sig.into(), + }, + &[], + ) + } } From af7c70580390a95a425c583551958d1cb9cb6b4d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 14:03:23 +0200 Subject: [PATCH 42/51] Add test modules for clarity --- contracts/btc-finality/src/multitest.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index ab80cdad..1aa8fa4e 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -12,15 +12,6 @@ const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv73 mod instantiation { use super::*; - use test_utils::{get_add_finality_sig, get_pub_rand_value}; - - use cosmwasm_std::Event; - - use crate::contract::tests::{ - create_new_finality_provider, get_derived_btc_delegation, get_public_randomness_commitment, - }; - use crate::msg::FinalitySignatureResponse; - #[test] fn instantiate_works() { let suite = SuiteBuilder::new().build(); @@ -38,6 +29,19 @@ mod instantiation { let btc_finality_config = suite.get_btc_finality_config(); assert_eq!(btc_finality_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); } +} + +mod finality { + use super::*; + + use cosmwasm_std::Event; + + use test_utils::{get_add_finality_sig, get_pub_rand_value}; + + use crate::contract::tests::{ + create_new_finality_provider, get_derived_btc_delegation, get_public_randomness_commitment, + }; + use crate::msg::FinalitySignatureResponse; #[test] fn commit_public_randomness_works() { From 5c339e362b71c00e6c827785072877aaf0ae9c0a Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Sep 2024 14:21:38 +0200 Subject: [PATCH 43/51] Add finality round works mt Remove original (ignored) test --- contracts/btc-finality/src/finality.rs | 171 +----------------- contracts/btc-finality/src/multitest.rs | 109 ++++++++++- contracts/btc-finality/src/multitest/suite.rs | 33 +++- 3 files changed, 137 insertions(+), 176 deletions(-) diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 7d9e7255..019718bd 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -589,7 +589,7 @@ mod tests { use cosmwasm_std::testing::{ message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{to_json_binary, Binary, Env, Event, OwnedDeps, Response, SubMsg, WasmMsg}; + use cosmwasm_std::{to_json_binary, Binary, Env, OwnedDeps, Response, SubMsg, WasmMsg}; use babylon_apis::btc_staking_api; use babylon_apis::finality_api::{ExecuteMsg, IndexedBlock, SudoMsg}; @@ -656,175 +656,6 @@ mod tests { ) } - #[test] - #[ignore] - fn finality_round_works() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - // Read public randomness commitment test data - let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); - let pub_rand_one = get_pub_rand_value(); - // Read equivalent / consistent add finality signature test data - let add_finality_signature = get_add_finality_sig(); - let proof = add_finality_signature.proof.unwrap(); - - let initial_height = pub_rand.start_height; - - let initial_env = mock_env_height(initial_height); - - instantiate( - deps.as_mut(), - initial_env.clone(), - info.clone(), - InstantiateMsg { - params: Some(get_params()), - admin: None, - }, - ) - .unwrap(); - - // Register one FP - // NOTE: the test data ensures that pub rand commit / finality sig are - // signed by the 1st FP - let new_fp = create_new_finality_provider(1); - assert_eq!(new_fp.btc_pk_hex, pk_hex); - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![new_fp.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) - .unwrap(); - - // Add a delegation, so that the finality provider has some power - let mut del1 = get_derived_btc_delegation(1, &[1]); - del1.fp_btc_pk_list = vec![pk_hex.clone()]; - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - btc_staking::contract::execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); - - // Check that the finality provider power has been updated - let fp_info = btc_staking::queries::finality_provider_info( - deps.as_ref(), - new_fp.btc_pk_hex.clone(), - None, - ) - .unwrap(); - assert_eq!(fp_info.power, del1.total_sat); - - // Submit public randomness commitment for the FP and the involved heights - let msg = ExecuteMsg::CommitPublicRandomness { - fp_pubkey_hex: pk_hex.clone(), - start_height: pub_rand.start_height, - num_pub_rand: pub_rand.num_pub_rand, - commitment: pub_rand.commitment.into(), - signature: pubrand_signature.into(), - }; - - execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // Call the begin-block sudo handler, for completeness - let res = call_begin_block( - &mut deps, - &add_finality_signature.block_app_hash, - initial_height + 1, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(0, res.messages.len()); - assert_eq!(0, res.events.len()); - - // Call the end-block sudo handler, so that the block is indexed in the store - let res = call_end_block( - &mut deps, - &add_finality_signature.block_app_hash, - initial_height + 1, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(0, res.messages.len()); - assert_eq!(1, res.events.len()); - assert_eq!( - res.events[0], - Event::new("index_block") - .add_attribute("module", "finality") - .add_attribute("last_height", (initial_height + 1).to_string()) - ); - - // Submit a finality signature from that finality provider at height initial_height + 1 - let submit_height = initial_height + 1; - let finality_signature = add_finality_signature.finality_sig.to_vec(); - let msg = ExecuteMsg::SubmitFinalitySignature { - fp_pubkey_hex: pk_hex.clone(), - height: submit_height, - pub_rand: pub_rand_one.into(), - proof: proof.into(), - block_hash: add_finality_signature.block_app_hash.to_vec().into(), - signature: Binary::new(finality_signature.clone()), - }; - - // Execute the message at the exact submit height, so that: - // 1. It's not rejected because of height being too high. - // 2. The FP has consolidated power at such height - // 3. There are no more pending / future blocks to process - let submit_env = mock_env_height(submit_height); - execute(deps.as_mut(), submit_env.clone(), info.clone(), msg).unwrap(); - - // Call the begin blocker, to compute the active FP set - let res = call_begin_block( - &mut deps, - &add_finality_signature.block_app_hash, - submit_height, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(0, res.events.len()); - assert_eq!(0, res.messages.len()); - - // Call the end blocker, to process the finality signatures - let res = call_end_block( - &mut deps, - &add_finality_signature.block_app_hash, - submit_height, - ) - .unwrap(); - assert_eq!(0, res.attributes.len()); - assert_eq!(2, res.events.len()); - assert_eq!( - res.events[0], - Event::new("index_block") - .add_attribute("module", "finality") - .add_attribute("last_height", submit_height.to_string()) - ); - assert_eq!( - res.events[1], - Event::new("finalize_block") - .add_attribute("module", "finality") - .add_attribute("finalized_height", submit_height.to_string()) - ); - assert_eq!(0, res.messages.len()); - - // Assert the submitted block has been indexed and finalised - let indexed_block = block(deps.as_ref(), submit_height).unwrap(); - assert_eq!( - indexed_block, - IndexedBlock { - height: submit_height, - app_hash: add_finality_signature.block_app_hash.to_vec(), - finalized: true, - } - ); - } - #[test] #[ignore] fn slashing_works() { diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index 1aa8fa4e..9ef38a8f 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -34,14 +34,14 @@ mod instantiation { mod finality { use super::*; - use cosmwasm_std::Event; - - use test_utils::{get_add_finality_sig, get_pub_rand_value}; - use crate::contract::tests::{ create_new_finality_provider, get_derived_btc_delegation, get_public_randomness_commitment, }; use crate::msg::FinalitySignatureResponse; + use babylon_apis::finality_api::IndexedBlock; + + use cosmwasm_std::Event; + use test_utils::{get_add_finality_sig, get_pub_rand_value}; #[test] fn commit_public_randomness_works() { @@ -151,4 +151,105 @@ mod finality { } ); } + + #[test] + fn finality_round_works() { + // Read public randomness commitment test data + let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); + let pub_rand_one = get_pub_rand_value(); + // Read equivalent / consistent add finality signature test data + let add_finality_signature = get_add_finality_sig(); + let proof = add_finality_signature.proof.unwrap(); + + let initial_height = pub_rand.start_height; + + let mut suite = SuiteBuilder::new().with_height(initial_height).build(); + + // signed by the 1st FP + let new_fp = create_new_finality_provider(1); + assert_eq!(new_fp.btc_pk_hex, pk_hex); + + suite + .register_finality_providers(&[new_fp.clone()]) + .unwrap(); + + // Add a delegation, so that the finality provider has some power + let mut del1 = get_derived_btc_delegation(1, &[1]); + del1.fp_btc_pk_list = vec![pk_hex.clone()]; + + suite.add_delegations(&[del1.clone()]).unwrap(); + + // Check that the finality provider power has been updated + let fp_info = suite.get_finality_provider_info(&new_fp.btc_pk_hex, None); + assert_eq!(fp_info.power, del1.total_sat); + + // Submit public randomness commitment for the FP and the involved heights + suite + .commit_public_randomness(&pk_hex, &pub_rand, &pubrand_signature) + .unwrap(); + + // Call the begin-block sudo handler, for completeness + suite + .call_begin_block(&add_finality_signature.block_app_hash, initial_height + 1) + .unwrap(); + + // Call the end-block sudo handler, so that the block is indexed in the store + suite + .call_end_block(&add_finality_signature.block_app_hash, initial_height + 1) + .unwrap(); + + // Submit a finality signature from that finality provider at height initial_height + 1 + let submit_height = initial_height + 1; + let finality_sig = add_finality_signature.finality_sig.to_vec(); + suite + .submit_finality_signature( + &pk_hex, + submit_height, + &pub_rand_one, + &proof, + &add_finality_signature.block_app_hash, + &finality_sig, + ) + .unwrap(); + + // Call the begin blocker, to compute the active FP set + suite + .call_begin_block(&add_finality_signature.block_app_hash, submit_height) + .unwrap(); + + // Call the end blocker, to process the finality signatures + let res = suite + .call_end_block(&add_finality_signature.block_app_hash, submit_height) + .unwrap(); + assert_eq!(3, res.events.len()); + assert_eq!( + res.events[0], + Event::new("sudo").add_attribute("_contract_address", CONTRACT2_ADDR) + ); + assert_eq!( + res.events[1], + Event::new("wasm-index_block") + .add_attribute("_contract_address", CONTRACT2_ADDR) + .add_attribute("module", "finality") + .add_attribute("last_height", submit_height.to_string()) + ); + assert_eq!( + res.events[2], + Event::new("wasm-finalize_block") + .add_attribute("_contract_address", CONTRACT2_ADDR) + .add_attribute("module", "finality") + .add_attribute("finalized_height", submit_height.to_string()) + ); + + // Assert the submitted block has been indexed and finalised + let indexed_block = suite.get_indexed_block(submit_height); + assert_eq!( + indexed_block, + IndexedBlock { + height: submit_height, + app_hash: add_finality_signature.block_app_hash.to_vec(), + finalized: true, + } + ); + } } diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index ebad8798..07a34848 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -7,12 +7,12 @@ use cosmwasm_std::Addr; use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; use babylon_apis::btc_staking_api::{ActiveBtcDelegation, NewFinalityProvider}; -use babylon_apis::finality_api::PubRandCommit; +use babylon_apis::finality_api::{IndexedBlock, PubRandCommit}; use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; -use btc_staking::msg::ActivatedHeightResponse; +use btc_staking::msg::{ActivatedHeightResponse, FinalityProviderInfo}; use crate::msg::FinalitySignatureResponse; use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; @@ -171,6 +171,24 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_finality_provider_info( + &self, + pk_hex: &str, + height: Option, + ) -> FinalityProviderInfo { + self.app + .wrap() + .query_wasm_smart( + self.staking.clone(), + &btc_staking::msg::QueryMsg::FinalityProviderInfo { + btc_pk_hex: pk_hex.to_string(), + height, + }, + ) + .unwrap() + } + #[track_caller] pub fn get_finality_signature(&self, pk_hex: &str, height: u64) -> FinalitySignatureResponse { self.app @@ -185,6 +203,17 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_indexed_block(&self, height: u64) -> IndexedBlock { + self.app + .wrap() + .query_wasm_smart( + self.finality.clone(), + &crate::msg::QueryMsg::Block { height }, + ) + .unwrap() + } + #[track_caller] pub fn register_finality_providers( &mut self, From bca20d1ba0e58d88bf808129c3cc6e583f7117b9 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 08:04:07 +0200 Subject: [PATCH 44/51] Add slashing works mt --- contracts/btc-finality/src/multitest.rs | 152 ++++++++++++++++++ contracts/btc-finality/src/multitest/suite.rs | 35 +++- 2 files changed, 183 insertions(+), 4 deletions(-) diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index 9ef38a8f..0a1f75bf 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -5,8 +5,11 @@ use suite::SuiteBuilder; // Some multi-test default settings // TODO: Replace these with their address generators +// Babylon contract const CONTRACT0_ADDR: &str = "cosmwasm19mfs8tl4s396u7vqw9rrnsmrrtca5r66p7v8jvwdxvjn3shcmllqupdgxu"; +// BTC Staking contract const CONTRACT1_ADDR: &str = "cosmwasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s8jef58"; +// BTC Finality contract const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqt8utkp"; mod instantiation { @@ -253,3 +256,152 @@ mod finality { ); } } + +mod slashing { + use babylon_apis::finality_api::IndexedBlock; + use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; + + use crate::contract::tests::{ + create_new_finality_provider, get_derived_btc_delegation, get_public_randomness_commitment, + }; + use crate::multitest::suite::SuiteBuilder; + + #[test] + fn slashing_works() { + // Read public randomness commitment test data + let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); + let pub_rand_one = get_pub_rand_value(); + // Read equivalent / consistent add finality signature test data + let add_finality_signature = get_add_finality_sig(); + let proof = add_finality_signature.proof.unwrap(); + + let initial_height = pub_rand.start_height; + + let mut suite = SuiteBuilder::new().with_height(initial_height).build(); + + // Register one FP + // NOTE: the test data ensures that pub rand commit / finality sig are + // signed by the 1st FP + let new_fp = create_new_finality_provider(1); + + suite + .register_finality_providers(&[new_fp.clone()]) + .unwrap(); + + // Add a delegation, so that the finality provider has some power + let mut del1 = get_derived_btc_delegation(1, &[1]); + del1.fp_btc_pk_list = vec![pk_hex.clone()]; + + suite.add_delegations(&[del1.clone()]).unwrap(); + + // Check that the finality provider power has been updated + let fp_info = suite.get_finality_provider_info(&new_fp.btc_pk_hex, None); + assert_eq!(fp_info.power, del1.total_sat); + + // Submit public randomness commitment for the FP and the involved heights + suite + .commit_public_randomness(&pk_hex, &pub_rand, &pubrand_signature) + .unwrap(); + + // Call the begin-block sudo handler at the next height, for completeness + let next_height = initial_height + 1; + suite + .call_begin_block(&add_finality_signature.block_app_hash, next_height) + .unwrap(); + + // Call the end-block sudo handler, so that the block is indexed in the store + suite + .call_end_block(&add_finality_signature.block_app_hash, next_height) + .unwrap(); + + // Submit a finality signature from that finality provider at next height (initial_height + 1) + let submit_height = next_height; + // Increase block height + let next_height = next_height + 1; + suite.app.advance_blocks(next_height - submit_height); + // Call the begin-block sudo handler at the next height, for completeness + suite + .call_begin_block(&add_finality_signature.block_app_hash, next_height) + .unwrap(); + + let finality_signature = add_finality_signature.finality_sig.to_vec(); + suite + .submit_finality_signature( + &pk_hex, + submit_height, + &pub_rand_one, + &proof, + &add_finality_signature.block_app_hash, + &finality_signature, + ) + .unwrap(); + + // Submitting the same signature twice is tolerated + suite + .submit_finality_signature( + &pk_hex, + submit_height, + &pub_rand_one, + &proof, + &add_finality_signature.block_app_hash, + &finality_signature, + ) + .unwrap(); + + // Submit another (different and valid) finality signature, from the same finality provider + // at the same height, and with the same proof + let add_finality_signature_2 = get_add_finality_sig_2(); + let res = suite + .submit_finality_signature( + &pk_hex, + submit_height, + &pub_rand_one, + &proof, + &add_finality_signature_2.block_app_hash, + &add_finality_signature_2.finality_sig, + ) + .unwrap(); + + // Assert the double signing evidence is proper + let btc_pk = hex::decode(pk_hex.clone()).unwrap(); + let evidence = suite.get_double_signing_evidence(&pk_hex, submit_height); + assert_eq!(evidence.block_height, submit_height); + assert_eq!(evidence.fp_btc_pk, btc_pk); + + // Assert the slashing event is there + assert_eq!(1, res.events.len()); + // Assert the slashing event is proper + assert_eq!(res.events[0].ty, "slashed_finality_provider".to_string()); + + // Call the end-block sudo handler for completeness / realism + suite + .call_end_block(&add_finality_signature_2.block_app_hash, next_height) + .unwrap(); + + // Call the next (final) block begin blocker, to compute the active FP set + let final_height = next_height + 1; + suite + .call_begin_block("deadbeef02".as_bytes(), final_height) + .unwrap(); + + // Call the next (final) block end blocker, to process the finality signatures + suite + .call_end_block("deadbeef02".as_bytes(), final_height) + .unwrap(); + + // Assert the canonical block has been indexed (and finalised) + let indexed_block = suite.get_indexed_block(submit_height); + assert_eq!( + indexed_block, + IndexedBlock { + height: submit_height, + app_hash: add_finality_signature.block_app_hash.to_vec(), + finalized: true, + } + ); + + // Assert the finality provider has been slashed + let fp = suite.get_finality_provider(&pk_hex); + assert_eq!(fp.slashed_height, submit_height); + } +} diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index 07a34848..f50f9741 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -6,8 +6,8 @@ use cosmwasm_std::Addr; use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; -use babylon_apis::btc_staking_api::{ActiveBtcDelegation, NewFinalityProvider}; -use babylon_apis::finality_api::{IndexedBlock, PubRandCommit}; +use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider, NewFinalityProvider}; +use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; @@ -171,6 +171,19 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_finality_provider(&self, pk_hex: &str) -> FinalityProvider { + self.app + .wrap() + .query_wasm_smart( + self.staking.clone(), + &btc_staking::msg::QueryMsg::FinalityProvider { + btc_pk_hex: pk_hex.to_string(), + }, + ) + .unwrap() + } + #[track_caller] pub fn get_finality_provider_info( &self, @@ -214,6 +227,20 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_double_signing_evidence(&self, pk_hex: &str, height: u64) -> Evidence { + self.app + .wrap() + .query_wasm_smart( + self.finality.clone(), + &crate::msg::QueryMsg::Evidence { + btc_pk_hex: pk_hex.to_string(), + height, + }, + ) + .unwrap() + } + #[track_caller] pub fn register_finality_providers( &mut self, @@ -280,7 +307,7 @@ impl Suite { self.app.set_block(block); // Hash is not used in the begin-block handler - let hash_hex = "deadbeef".to_string(); + let hash_hex = format!("deadbeef{}", height); let app_hash_hex: String = app_hash.encode_hex(); self.app.wasm_sudo( @@ -300,7 +327,7 @@ impl Suite { self.app.set_block(block); // Hash is not used in the begin-block handler - let hash_hex = "deadbeef".to_string(); + let hash_hex = format!("deadbeef{}", height); let app_hash_hex: String = app_hash.encode_hex(); self.app.wasm_sudo( From 543e639c0583a352375359141d3b3576a4aa3b25 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 08:07:53 +0200 Subject: [PATCH 45/51] Remove original finality (no mt) tests --- contracts/btc-finality/src/contract.rs | 17 +- contracts/btc-finality/src/finality.rs | 262 ------------------------- 2 files changed, 1 insertion(+), 278 deletions(-) diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 017d58b8..1734c0c5 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -247,15 +247,12 @@ pub(crate) mod tests { use super::*; - use crate::state::config::Params; use babylon_apis::btc_staking_api::{ ActiveBtcDelegation, BtcUndelegationInfo, CovenantAdaptorSignatures, FinalityProviderDescription, NewFinalityProvider, ProofOfPossessionBtc, }; use babylon_apis::finality_api::PubRandCommit; - use babylon_proto::babylon::btcstaking::v1::{ - BtcDelegation, FinalityProvider, Params as ProtoParams, - }; + use babylon_proto::babylon::btcstaking::v1::{BtcDelegation, FinalityProvider}; use cosmwasm_std::{ from_json, testing::{message_info, mock_dependencies, mock_env}, @@ -269,18 +266,6 @@ pub(crate) mod tests { pub(crate) const INIT_ADMIN: &str = "initial_admin"; const NEW_ADMIN: &str = "new_admin"; - fn new_params(params: ProtoParams) -> Params { - Params { - max_active_finality_providers: params.max_active_finality_providers, - min_pub_rand: 10, // TODO: fix this - } - } - - pub(crate) fn get_params() -> Params { - let proto_params = test_utils::get_params(); - new_params(proto_params) - } - fn new_finality_provider(fp: FinalityProvider) -> NewFinalityProvider { NewFinalityProvider { addr: fp.addr, diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 019718bd..41a64fdd 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -581,265 +581,3 @@ pub fn list_fps_by_power( let res: FinalityProvidersByPowerResponse = querier.query(&query)?; Ok(res.fps) } - -#[cfg(test)] -mod tests { - use hex::ToHex; - - use cosmwasm_std::testing::{ - message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, - }; - use cosmwasm_std::{to_json_binary, Binary, Env, OwnedDeps, Response, SubMsg, WasmMsg}; - - use babylon_apis::btc_staking_api; - use babylon_apis::finality_api::{ExecuteMsg, IndexedBlock, SudoMsg}; - use babylon_bindings::BabylonMsg; - use test_utils::{get_add_finality_sig, get_add_finality_sig_2, get_pub_rand_value}; - - use crate::contract::tests::{ - create_new_finality_provider, get_derived_btc_delegation, get_params, - get_public_randomness_commitment, - }; - use crate::contract::{execute, instantiate}; - use crate::error::ContractError; - use crate::msg::InstantiateMsg; - use crate::queries::{block, evidence}; - - const CREATOR: &str = "creator"; - - fn mock_env_height(height: u64) -> Env { - let mut env = mock_env(); - env.block.height = height; - - env - } - - #[track_caller] - pub fn call_begin_block( - deps: &mut OwnedDeps, - app_hash: &[u8], - height: u64, - ) -> Result, ContractError> { - let env = mock_env_height(height); - // Hash is not used in the begin-block handler - let hash_hex = "deadbeef".to_string(); - let app_hash_hex = app_hash.encode_hex(); - - crate::contract::sudo( - deps.as_mut(), - env.clone(), - SudoMsg::BeginBlock { - hash_hex, - app_hash_hex, - }, - ) - } - - #[track_caller] - pub fn call_end_block( - deps: &mut OwnedDeps, - app_hash: &[u8], - height: u64, - ) -> Result, ContractError> { - let env = mock_env_height(height); - // Hash is not used in the end-block handler - let hash_hex = "deadbeef".to_string(); - let app_hash_hex = app_hash.encode_hex(); - - crate::contract::sudo( - deps.as_mut(), - env.clone(), - SudoMsg::EndBlock { - hash_hex, - app_hash_hex, - }, - ) - } - - #[test] - #[ignore] - fn slashing_works() { - let mut deps = mock_dependencies(); - let info = message_info(&deps.api.addr_make(CREATOR), &[]); - - // Read public randomness commitment test data - let (pk_hex, pub_rand, pubrand_signature) = get_public_randomness_commitment(); - let pub_rand_one = get_pub_rand_value(); - // Read equivalent / consistent add finality signature test data - let add_finality_signature = get_add_finality_sig(); - let proof = add_finality_signature.proof.unwrap(); - - let initial_height = pub_rand.start_height; - let initial_env = mock_env_height(initial_height); - - instantiate( - deps.as_mut(), - initial_env.clone(), - info.clone(), - InstantiateMsg { - params: Some(get_params()), - admin: None, - }, - ) - .unwrap(); - - // Register one FP - // NOTE: the test data ensures that pub rand commit / finality sig are - // signed by the 1st FP - let new_fp = create_new_finality_provider(1); - assert_eq!(new_fp.btc_pk_hex, pk_hex); - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![new_fp.clone()], - active_del: vec![], - slashed_del: vec![], - unbonded_del: vec![], - }; - - let _res = - btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) - .unwrap(); - - // Add a delegation, so that the finality provider has some power - let mut del1 = get_derived_btc_delegation(1, &[1]); - del1.fp_btc_pk_list = vec![pk_hex.clone()]; - - let msg = btc_staking_api::ExecuteMsg::BtcStaking { - new_fp: vec![], - active_del: vec![del1.clone()], - slashed_del: vec![], - unbonded_del: vec![], - }; - - btc_staking::contract::execute(deps.as_mut(), initial_env.clone(), info.clone(), msg) - .unwrap(); - - // Check that the finality provider power has been updated - let fp_info = btc_staking::queries::finality_provider_info( - deps.as_ref(), - new_fp.btc_pk_hex.clone(), - None, - ) - .unwrap(); - assert_eq!(fp_info.power, del1.total_sat); - - // Submit public randomness commitment for the FP and the involved heights - let msg = ExecuteMsg::CommitPublicRandomness { - fp_pubkey_hex: pk_hex.clone(), - start_height: pub_rand.start_height, - num_pub_rand: pub_rand.num_pub_rand, - commitment: pub_rand.commitment.into(), - signature: pubrand_signature.into(), - }; - - execute(deps.as_mut(), initial_env, info.clone(), msg).unwrap(); - - // Call the begin-block sudo handler at the next height, for completeness - let next_height = initial_height + 1; - call_begin_block( - &mut deps, - &add_finality_signature.block_app_hash, - next_height, - ) - .unwrap(); - - // Call the end-block sudo handler, so that the block is indexed in the store - call_end_block( - &mut deps, - &add_finality_signature.block_app_hash, - next_height, - ) - .unwrap(); - - // Submit a finality signature from that finality provider at next height (initial_height + 1) - let submit_height = next_height; - // Increase block height - let next_height = next_height + 1; - let next_env = mock_env_height(next_height); - // Call the begin-block sudo handler at the next height, for completeness - call_begin_block(&mut deps, "deadbeef01".as_bytes(), next_height).unwrap(); - - let finality_signature = add_finality_signature.finality_sig.to_vec(); - let msg = ExecuteMsg::SubmitFinalitySignature { - fp_pubkey_hex: pk_hex.clone(), - height: submit_height, - pub_rand: pub_rand_one.clone().into(), - proof: proof.clone().into(), - block_hash: add_finality_signature.block_app_hash.to_vec().into(), - signature: Binary::new(finality_signature.clone()), - }; - - let res = execute(deps.as_mut(), next_env.clone(), info.clone(), msg.clone()).unwrap(); - assert_eq!(0, res.messages.len()); - assert_eq!(0, res.events.len()); - - // Submitting the same signature twice is tolerated - let res = execute(deps.as_mut(), next_env.clone(), info.clone(), msg).unwrap(); - assert_eq!(0, res.messages.len()); - assert_eq!(0, res.events.len()); - - // Submit another (different and valid) finality signature, from the same finality provider - // at the same height - let add_finality_signature_2 = get_add_finality_sig_2(); - let msg = ExecuteMsg::SubmitFinalitySignature { - fp_pubkey_hex: pk_hex.clone(), - height: submit_height, - pub_rand: pub_rand_one.clone().into(), - proof: proof.into(), - block_hash: add_finality_signature_2.block_app_hash.to_vec().into(), - signature: Binary::new(add_finality_signature_2.finality_sig.to_vec()), - }; - let res = execute(deps.as_mut(), next_env.clone(), info.clone(), msg).unwrap(); - - // Assert the double signing evidence is proper - let btc_pk = hex::decode(pk_hex.clone()).unwrap(); - let evidence = evidence(deps.as_ref(), pk_hex.clone(), submit_height) - .unwrap() - .evidence - .unwrap(); - assert_eq!(evidence.block_height, submit_height); - assert_eq!(evidence.fp_btc_pk, btc_pk); - - // Assert the slashing propagation msg is there - assert_eq!(1, res.messages.len()); - // Assert the slashing propagation msg is proper - let babylon_addr = btc_staking::queries::config(deps.as_ref()).unwrap().babylon; - // Assert the slashing event is there - assert_eq!(1, res.events.len()); - // Assert the slashing event is proper - assert_eq!(res.events[0].ty, "slashed_finality_provider".to_string()); - assert_eq!( - res.messages[0], - SubMsg::new(WasmMsg::Execute { - contract_addr: babylon_addr.to_string(), - msg: to_json_binary(&babylon_contract::ExecuteMsg::Slashing { evidence }).unwrap(), - funds: vec![] - }) - ); - - // Call the end-block sudo handler for completeness / realism - call_end_block(&mut deps, "deadbeef01".as_bytes(), next_height).unwrap(); - - // Call the next (final) block begin blocker, to compute the active FP set - let final_height = next_height + 1; - call_begin_block(&mut deps, "deadbeef02".as_bytes(), final_height).unwrap(); - - // Call the next (final) block end blocker, to process the finality signatures - call_end_block(&mut deps, "deadbeef02".as_bytes(), final_height).unwrap(); - - // Assert the canonical block has been indexed (and finalised) - let indexed_block = block(deps.as_ref(), submit_height).unwrap(); - assert_eq!( - indexed_block, - IndexedBlock { - height: submit_height, - app_hash: add_finality_signature.block_app_hash.to_vec(), - finalized: true, - } - ); - - // Assert the finality provider has been slashed - let fp = btc_staking::queries::finality_provider(deps.as_ref(), pk_hex).unwrap(); - assert_eq!(fp.slashed_height, submit_height); - } -} From 4bdc93a2a0a8b418c86708a63a5deef4b8300134 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 09:00:24 +0200 Subject: [PATCH 46/51] Fix: slashing test --- contracts/btc-finality/src/multitest.rs | 18 +++++++++++++----- contracts/btc-finality/src/multitest/suite.rs | 6 +++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/btc-finality/src/multitest.rs b/contracts/btc-finality/src/multitest.rs index 0a1f75bf..096c77e3 100644 --- a/contracts/btc-finality/src/multitest.rs +++ b/contracts/btc-finality/src/multitest.rs @@ -305,6 +305,7 @@ mod slashing { // Call the begin-block sudo handler at the next height, for completeness let next_height = initial_height + 1; + suite.app.advance_blocks(next_height - initial_height); suite .call_begin_block(&add_finality_signature.block_app_hash, next_height) .unwrap(); @@ -364,14 +365,20 @@ mod slashing { // Assert the double signing evidence is proper let btc_pk = hex::decode(pk_hex.clone()).unwrap(); - let evidence = suite.get_double_signing_evidence(&pk_hex, submit_height); + let evidence = suite + .get_double_signing_evidence(&pk_hex, submit_height) + .evidence + .unwrap(); assert_eq!(evidence.block_height, submit_height); assert_eq!(evidence.fp_btc_pk, btc_pk); // Assert the slashing event is there - assert_eq!(1, res.events.len()); - // Assert the slashing event is proper - assert_eq!(res.events[0].ty, "slashed_finality_provider".to_string()); + assert_eq!(4, res.events.len()); + // Assert the slashing event is proper (slashing is the 2nd event in the list) + assert_eq!( + res.events[1].ty, + "wasm-slashed_finality_provider".to_string() + ); // Call the end-block sudo handler for completeness / realism suite @@ -380,6 +387,7 @@ mod slashing { // Call the next (final) block begin blocker, to compute the active FP set let final_height = next_height + 1; + suite.app.advance_blocks(final_height - next_height); suite .call_begin_block("deadbeef02".as_bytes(), final_height) .unwrap(); @@ -402,6 +410,6 @@ mod slashing { // Assert the finality provider has been slashed let fp = suite.get_finality_provider(&pk_hex); - assert_eq!(fp.slashed_height, submit_height); + assert_eq!(fp.slashed_height, next_height); } } diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index f50f9741..9c174333 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -7,14 +7,14 @@ use cosmwasm_std::Addr; use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider, NewFinalityProvider}; -use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; +use babylon_apis::finality_api::{IndexedBlock, PubRandCommit}; use babylon_apis::{btc_staking_api, finality_api}; use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; use btc_staking::msg::{ActivatedHeightResponse, FinalityProviderInfo}; -use crate::msg::FinalitySignatureResponse; +use crate::msg::{EvidenceResponse, FinalitySignatureResponse}; use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; fn contract_btc_staking() -> Box> { @@ -228,7 +228,7 @@ impl Suite { } #[track_caller] - pub fn get_double_signing_evidence(&self, pk_hex: &str, height: u64) -> Evidence { + pub fn get_double_signing_evidence(&self, pk_hex: &str, height: u64) -> EvidenceResponse { self.app .wrap() .query_wasm_smart( From 538076577a28089cd0be31f7aa7537e5f816e96d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 09:08:19 +0200 Subject: [PATCH 47/51] Feature gate / disable IBC messages for (multi-)tests --- contracts/babylon/src/contract.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index e08cacca..c5bc8830 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -63,6 +63,17 @@ pub fn instantiate( }; let init_msg = SubMsg::reply_on_success(init_msg, REPLY_ID_INSTANTIATE_STAKING); + // Test code sets a channel, so that we can better approximate IBC in test code + #[cfg(any(test, feature = "library"))] + { + let channel = cosmwasm_std::testing::mock_ibc_channel( + "channel-123", + cosmwasm_std::IbcOrder::Ordered, + "babylon", + ); + IBC_CHANNEL.save(deps.storage, &channel)?; + } + res = res.add_submessage(init_msg); } @@ -227,6 +238,7 @@ pub fn execute( return Err(ContractError::Unauthorized {}); } // Send to the staking contract for processing + let mut res = Response::new(); let btc_staking = cfg.btc_staking.ok_or(ContractError::BtcStakingNotSet {})?; // Slashes this finality provider, i.e., sets its slashing height to the block height // and its power to zero @@ -238,12 +250,24 @@ pub fn execute( msg: to_json_binary(&msg)?, funds: vec![], }; + res = res.add_message(wasm_msg); // Send over IBC to the Provider (Babylon) let channel = IBC_CHANNEL.load(deps.storage)?; let ibc_msg = ibc_packet::slashing_msg(&env, &channel, &evidence)?; + // Send packet only if we are IBC enabled + // TODO: send in test code when multi-test can handle it + #[cfg(not(any(test, feature = "library")))] + { + res = res.add_message(ibc_msg); + } + #[cfg(any(test, feature = "library"))] + { + let _ = ibc_msg; + } + // TODO: Add events - Ok(Response::new().add_message(wasm_msg).add_message(ibc_msg)) + Ok(res) } } } From 8b1ccf5803a00384b9867986e72dc07f01e9fc88 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 11:40:00 +0200 Subject: [PATCH 48/51] Fix: Avoid full-validation failure / bug in CI --- .github/workflows/local-tests.yml | 12 ++++++++++-- contracts/btc-finality/Cargo.toml | 2 -- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/local-tests.yml b/.github/workflows/local-tests.yml index 7e159659..12342b6a 100644 --- a/.github/workflows/local-tests.yml +++ b/.github/workflows/local-tests.yml @@ -13,5 +13,13 @@ jobs: image: rust:1.78.0 steps: - uses: actions/checkout@v4.1.0 - - name: Build contracts, check formats, and run unit tests (with full validation) - run: cargo test --lib --features full-validation + - name: Build contracts, check formats, and run unit tests + run: cargo test --lib + local-build-test-full-validation: + runs-on: ubuntu-latest + container: + image: rust:1.78.0 + steps: + - uses: actions/checkout@v4.1.0 + - name: Build contracts, check formats, and run unit tests (full validation) + run: cargo test --package btc-staking --lib --features full-validation diff --git a/contracts/btc-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml index 8c721216..d213ed0b 100644 --- a/contracts/btc-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -25,8 +25,6 @@ default = [] cranelift = ["cosmwasm-vm/cranelift"] # for quicker tests, cargo test --lib library = [] -# feature for enabling the full validation -full-validation = [] [dependencies] babylon-apis = { path = "../../packages/apis" } From be727aea545517501ab4108529289dfa0cc065b5 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 11:52:53 +0200 Subject: [PATCH 49/51] Remove (unused) denom from config --- contracts/babylon/src/multitest.rs | 3 --- contracts/btc-finality/src/contract.rs | 2 -- contracts/btc-finality/src/state/config.rs | 1 - contracts/btc-staking/src/contract.rs | 2 -- contracts/btc-staking/src/state/config.rs | 1 - 5 files changed, 9 deletions(-) diff --git a/contracts/babylon/src/multitest.rs b/contracts/babylon/src/multitest.rs index 9ddefd5f..44fc23ee 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -8,7 +8,6 @@ use suite::SuiteBuilder; const CONTRACT0_ADDR: &str = "cosmwasm19mfs8tl4s396u7vqw9rrnsmrrtca5r66p7v8jvwdxvjn3shcmllqupdgxu"; const CONTRACT1_ADDR: &str = "cosmwasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s8jef58"; const CONTRACT2_ADDR: &str = "cosmwasm1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqt8utkp"; -const TOKEN: &str = "TOKEN"; #[test] fn initialization() { @@ -30,12 +29,10 @@ fn initialization() { // Check that the btc-staking contract was initialized correctly let btc_staking_config = suite.get_btc_staking_config(); assert_eq!(btc_staking_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); - assert_eq!(btc_staking_config.denom, TOKEN); // Check that the btc-finality contract was initialized correctly let btc_finality_config = suite.get_btc_finality_config(); assert_eq!(btc_finality_config.babylon, Addr::unchecked(CONTRACT0_ADDR)); - assert_eq!(btc_finality_config.denom, TOKEN); } mod instantiation { diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 1734c0c5..5ab224b6 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -30,9 +30,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result, ContractError> { nonpayable(&info)?; - let denom = deps.querier.query_bonded_denom()?; let config = Config { - denom, babylon: info.sender, staking: Addr::unchecked("UNSET"), // To be set later, through `UpdateStaking` }; diff --git a/contracts/btc-finality/src/state/config.rs b/contracts/btc-finality/src/state/config.rs index 6755a032..77c18b6b 100644 --- a/contracts/btc-finality/src/state/config.rs +++ b/contracts/btc-finality/src/state/config.rs @@ -15,7 +15,6 @@ pub(crate) const ADMIN: Admin = Admin::new("admin"); // TODO: Add / enable config entries as needed #[cw_serde] pub struct Config { - pub denom: String, pub babylon: Addr, pub staking: Addr, } diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 2ee70cb5..3dd2f89b 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -26,9 +26,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result, ContractError> { nonpayable(&info)?; - let denom = deps.querier.query_bonded_denom()?; let config = Config { - denom, babylon: info.sender, }; CONFIG.save(deps.storage, &config)?; diff --git a/contracts/btc-staking/src/state/config.rs b/contracts/btc-staking/src/state/config.rs index 29912e2b..2ea5671e 100644 --- a/contracts/btc-staking/src/state/config.rs +++ b/contracts/btc-staking/src/state/config.rs @@ -15,7 +15,6 @@ pub(crate) const ADMIN: Admin = Admin::new("admin"); // TODO: Add / enable config entries as needed #[cw_serde] pub struct Config { - pub denom: String, pub babylon: Addr, } From e0af2d938a392acf100d341044e0113f5042d085 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 12:00:34 +0200 Subject: [PATCH 50/51] Remove (unused / redundant) params from staking params --- contracts/btc-staking/src/contract.rs | 2 -- contracts/btc-staking/src/state/config.rs | 8 -------- 2 files changed, 10 deletions(-) diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 3dd2f89b..1fca33b3 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -160,8 +160,6 @@ pub mod tests { covenant_pks: params.covenant_pks.iter().map(hex::encode).collect(), covenant_quorum: params.covenant_quorum, btc_network: Network::Regtest, // TODO: fix this - max_active_finality_providers: params.max_active_finality_providers, - min_pub_rand: 10, // TODO: fix this slashing_address: params.slashing_address, min_slashing_tx_fee_sat: params.min_slashing_tx_fee_sat as u64, slashing_rate: "0.01".to_string(), // TODO: fix this diff --git a/contracts/btc-staking/src/state/config.rs b/contracts/btc-staking/src/state/config.rs index 2ea5671e..af413fa0 100644 --- a/contracts/btc-staking/src/state/config.rs +++ b/contracts/btc-staking/src/state/config.rs @@ -35,14 +35,6 @@ pub struct Params { // `min_commission_rate` is the chain-wide minimum commission rate that a finality provider // can charge their delegators // pub min_commission_rate: Decimal, - /// `max_active_finality_providers` is the maximum number of active finality providers in the - /// BTC staking protocol - #[derivative(Default(value = "100"))] - pub max_active_finality_providers: u32, - /// `min_pub_rand` is the minimum amount of public randomness each public randomness commitment - /// should commit - #[derivative(Default(value = "1"))] - pub min_pub_rand: u64, /// `slashing_address` is the address that the slashed BTC goes to. /// The address is in string format on Bitcoin. #[derivative(Default(value = "String::from(\"n4cV57jePmAAue2WTTBQzH3k3R2rgWBQwY\")"))] From 05438bbc044f32eab78f03ad76acfaf60a5cb1f2 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Sep 2024 12:01:13 +0200 Subject: [PATCH 51/51] Update schema --- .../btc-finality/schema/btc-finality.json | 34 +------------------ .../btc-finality/schema/raw/instantiate.json | 14 -------- .../schema/raw/response_to_config.json | 6 +--- .../schema/raw/response_to_params.json | 14 -------- contracts/btc-staking/schema/btc-staking.json | 34 +------------------ .../btc-staking/schema/raw/instantiate.json | 14 -------- .../schema/raw/response_to_config.json | 6 +--- .../schema/raw/response_to_params.json | 14 -------- 8 files changed, 4 insertions(+), 132 deletions(-) diff --git a/contracts/btc-finality/schema/btc-finality.json b/contracts/btc-finality/schema/btc-finality.json index 9bc2b7bc..d9c8c88c 100644 --- a/contracts/btc-finality/schema/btc-finality.json +++ b/contracts/btc-finality/schema/btc-finality.json @@ -42,8 +42,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -63,18 +61,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", @@ -896,15 +882,11 @@ "description": "Config are Babylon-selectable BTC staking configuration", "type": "object", "required": [ - "babylon", - "denom" + "babylon" ], "properties": { "babylon": { "$ref": "#/definitions/Addr" - }, - "denom": { - "type": "string" } }, "additionalProperties": false, @@ -1776,8 +1758,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -1797,18 +1777,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", diff --git a/contracts/btc-finality/schema/raw/instantiate.json b/contracts/btc-finality/schema/raw/instantiate.json index 3c553424..a771d00c 100644 --- a/contracts/btc-finality/schema/raw/instantiate.json +++ b/contracts/btc-finality/schema/raw/instantiate.json @@ -38,8 +38,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -59,18 +57,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", diff --git a/contracts/btc-finality/schema/raw/response_to_config.json b/contracts/btc-finality/schema/raw/response_to_config.json index 96e6cbe3..6b405689 100644 --- a/contracts/btc-finality/schema/raw/response_to_config.json +++ b/contracts/btc-finality/schema/raw/response_to_config.json @@ -4,15 +4,11 @@ "description": "Config are Babylon-selectable BTC staking configuration", "type": "object", "required": [ - "babylon", - "denom" + "babylon" ], "properties": { "babylon": { "$ref": "#/definitions/Addr" - }, - "denom": { - "type": "string" } }, "additionalProperties": false, diff --git a/contracts/btc-finality/schema/raw/response_to_params.json b/contracts/btc-finality/schema/raw/response_to_params.json index 614e0fd4..89d6b26a 100644 --- a/contracts/btc-finality/schema/raw/response_to_params.json +++ b/contracts/btc-finality/schema/raw/response_to_params.json @@ -7,8 +7,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -28,18 +26,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", diff --git a/contracts/btc-staking/schema/btc-staking.json b/contracts/btc-staking/schema/btc-staking.json index 19fe5d5f..b8835592 100644 --- a/contracts/btc-staking/schema/btc-staking.json +++ b/contracts/btc-staking/schema/btc-staking.json @@ -42,8 +42,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -63,18 +61,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", @@ -896,15 +882,11 @@ "description": "Config are Babylon-selectable BTC staking configuration", "type": "object", "required": [ - "babylon", - "denom" + "babylon" ], "properties": { "babylon": { "$ref": "#/definitions/Addr" - }, - "denom": { - "type": "string" } }, "additionalProperties": false, @@ -1776,8 +1758,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -1797,18 +1777,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", diff --git a/contracts/btc-staking/schema/raw/instantiate.json b/contracts/btc-staking/schema/raw/instantiate.json index 3c553424..a771d00c 100644 --- a/contracts/btc-staking/schema/raw/instantiate.json +++ b/contracts/btc-staking/schema/raw/instantiate.json @@ -38,8 +38,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -59,18 +57,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer", diff --git a/contracts/btc-staking/schema/raw/response_to_config.json b/contracts/btc-staking/schema/raw/response_to_config.json index 96e6cbe3..6b405689 100644 --- a/contracts/btc-staking/schema/raw/response_to_config.json +++ b/contracts/btc-staking/schema/raw/response_to_config.json @@ -4,15 +4,11 @@ "description": "Config are Babylon-selectable BTC staking configuration", "type": "object", "required": [ - "babylon", - "denom" + "babylon" ], "properties": { "babylon": { "$ref": "#/definitions/Addr" - }, - "denom": { - "type": "string" } }, "additionalProperties": false, diff --git a/contracts/btc-staking/schema/raw/response_to_params.json b/contracts/btc-staking/schema/raw/response_to_params.json index 614e0fd4..89d6b26a 100644 --- a/contracts/btc-staking/schema/raw/response_to_params.json +++ b/contracts/btc-staking/schema/raw/response_to_params.json @@ -7,8 +7,6 @@ "btc_network", "covenant_pks", "covenant_quorum", - "max_active_finality_providers", - "min_pub_rand", "min_slashing_tx_fee_sat", "slashing_address", "slashing_rate" @@ -28,18 +26,6 @@ "format": "uint32", "minimum": 0.0 }, - "max_active_finality_providers": { - "description": "`max_active_finality_providers` is the maximum number of active finality providers in the BTC staking protocol", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_pub_rand": { - "description": "`min_pub_rand` is the minimum amount of public randomness each public randomness commitment should commit", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "min_slashing_tx_fee_sat": { "description": "`min_slashing_tx_fee_sat` is the minimum amount of tx fee (quantified in Satoshi) needed for the pre-signed slashing tx", "type": "integer",