From 333f2387ba2675b3c66f8520325b347cd3f09364 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 9 Aug 2024 16:33:25 +0200 Subject: [PATCH 1/7] Introduce BtcDelegation for state --- contracts/btc-staking/src/msg.rs | 3 +- contracts/btc-staking/src/queries.rs | 32 +++--- contracts/btc-staking/src/staking.rs | 48 ++++----- contracts/btc-staking/src/state/staking.rs | 113 ++++++++++++++++++++- packages/apis/src/btc_staking_api.rs | 33 ------ 5 files changed, 149 insertions(+), 80 deletions(-) diff --git a/contracts/btc-staking/src/msg.rs b/contracts/btc-staking/src/msg.rs index 23b28acd..781d123f 100644 --- a/contracts/btc-staking/src/msg.rs +++ b/contracts/btc-staking/src/msg.rs @@ -5,6 +5,7 @@ use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; use babylon_apis::finality_api::{Evidence, IndexedBlock, PubRandCommit}; use crate::state::config::{Config, Params}; +use crate::state::staking::BtcDelegation; #[cw_serde] #[derive(Default)] @@ -150,7 +151,7 @@ pub struct FinalityProvidersResponse { #[cw_serde] pub struct BtcDelegationsResponse { - pub delegations: Vec, + pub delegations: Vec, } #[cw_serde] diff --git a/contracts/btc-staking/src/queries.rs b/contracts/btc-staking/src/queries.rs index 52d7e152..1703915b 100644 --- a/contracts/btc-staking/src/queries.rs +++ b/contracts/btc-staking/src/queries.rs @@ -7,7 +7,7 @@ use cosmwasm_std::Order::{Ascending, Descending}; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; -use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider}; +use babylon_apis::btc_staking_api::FinalityProvider; use babylon_apis::finality_api::IndexedBlock; use crate::error::ContractError; @@ -20,7 +20,7 @@ use crate::state::config::{Config, Params}; use crate::state::config::{CONFIG, PARAMS}; use crate::state::finality::{BLOCKS, EVIDENCES}; use crate::state::staking::{ - fps, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, FPS, FP_DELEGATIONS, + fps, BtcDelegation, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, FPS, FP_DELEGATIONS, }; pub fn config(deps: Deps) -> StdResult { @@ -56,10 +56,7 @@ pub fn finality_providers( /// 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 { +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())?) } @@ -92,7 +89,7 @@ pub fn delegations( }) .take(limit) .map(|item| item.map(|(_, v)| v)) - .collect::, _>>()?; + .collect::, _>>()?; Ok(BtcDelegationsResponse { delegations }) } @@ -247,9 +244,7 @@ mod tests { use cosmwasm_std::StdError::NotFound; use cosmwasm_std::{from_json, Binary, Storage}; - use babylon_apis::btc_staking_api::{ - ActiveBtcDelegation, FinalityProvider, UnbondedBtcDelegation, - }; + use babylon_apis::btc_staking_api::{FinalityProvider, UnbondedBtcDelegation}; use crate::contract::tests::create_new_finality_provider; use crate::contract::{execute, instantiate}; @@ -257,12 +252,12 @@ mod tests { use crate::finality::tests::mock_env_height; use crate::msg::{ExecuteMsg, FinalityProviderInfo, InstantiateMsg}; use crate::staking::tests::staking_tx_hash; - use crate::state::staking::{FinalityProviderState, FP_STATE_KEY}; + use crate::state::staking::{BtcDelegation, FinalityProviderState, FP_STATE_KEY}; const CREATOR: &str = "creator"; // Sort delegations by staking tx hash - fn sort_delegations(dels: &[ActiveBtcDelegation]) -> Vec { + fn sort_delegations(dels: &[BtcDelegation]) -> Vec { let mut dels = dels.to_vec(); dels.sort_by_key(staking_tx_hash); dels @@ -371,7 +366,7 @@ mod tests { 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(), del2.clone()]); + let sorted_dels = sort_delegations(&[del1.try_into().unwrap(), del2.try_into().unwrap()]); assert_eq!(dels[0], sorted_dels[0]); assert_eq!(dels[1], sorted_dels[1]); @@ -441,13 +436,16 @@ mod tests { .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(), del2.clone()]); + let sorted_dels = sort_delegations(&[ + del1.clone().try_into().unwrap(), + del2.clone().try_into().unwrap(), + ]); 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).to_string(); + let staking_tx_hash_hex = staking_tx_hash(&del2.try_into().unwrap()).to_string(); let msg = ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![], @@ -464,7 +462,7 @@ mod tests { .unwrap() .delegations; assert_eq!(dels.len(), 1); - assert_eq!(dels[0], del1); + assert_eq!(dels[0], del1.try_into().unwrap()); // Query all delegations (with active set to false) let dels = crate::queries::delegations(deps.as_ref(), None, None, Some(false)) @@ -595,7 +593,7 @@ mod tests { // Unbond the first delegation // Compute staking tx hash - let staking_tx_hash_hex = staking_tx_hash(&del1).to_string(); + let staking_tx_hash_hex = staking_tx_hash(&del1.try_into().unwrap()).to_string(); let msg = ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![], diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index f3db3f49..6e5d58ed 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -12,8 +12,8 @@ use crate::error::ContractError; use crate::msg::FinalityProviderInfo; use crate::state::config::{ADMIN, CONFIG}; use crate::state::staking::{ - fps, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, DELEGATION_FPS, FPS, FP_DELEGATIONS, - FP_SET, TOTAL_POWER, + fps, BtcDelegation, FinalityProviderState, ACTIVATED_HEIGHT, DELEGATIONS, DELEGATION_FPS, FPS, + FP_DELEGATIONS, FP_SET, TOTAL_POWER, }; use crate::state::BTC_HEIGHT; use babylon_apis::btc_staking_api::{ @@ -91,10 +91,10 @@ pub fn handle_new_fp( pub fn handle_active_delegation( storage: &mut dyn Storage, height: u64, - delegation: &ActiveBtcDelegation, + active_delegation: &ActiveBtcDelegation, ) -> Result<(), ContractError> { // Basic stateless checks - delegation.validate()?; + active_delegation.validate()?; // Get params // btc_confirmation_depth @@ -113,8 +113,8 @@ pub fn handle_active_delegation( // TODO: Verify proof of possession // Parse staking tx - let staking_tx: Transaction = deserialize(&delegation.staking_tx) - .map_err(|_| ContractError::InvalidBtcTx(delegation.staking_tx.encode_hex()))?; + let staking_tx: Transaction = deserialize(&active_delegation.staking_tx) + .map_err(|_| ContractError::InvalidBtcTx(active_delegation.staking_tx.encode_hex()))?; // Check staking time is at most uint16 match staking_tx.lock_time { LockTime::Blocks(b) if b.to_consensus_u32() > u16::MAX as u32 => { @@ -181,7 +181,7 @@ pub fn handle_active_delegation( // All good, check initial BTC undelegation information is present // TODO: Check that the sent undelegation info is valid - match delegation.undelegation_info { + match active_delegation.undelegation_info { Some(ref undelegation_info) => { // Check that the unbonding tx is there if undelegation_info.unbonding_tx.is_empty() { @@ -213,7 +213,7 @@ pub fn handle_active_delegation( // Update delegations by registered finality provider let fps = fps(); let mut registered_fp = false; - for fp_btc_pk_hex in &delegation.fp_btc_pk_list { + for fp_btc_pk_hex in &active_delegation.fp_btc_pk_list { // Skip if finality provider is not registered, as it can belong to another Consumer, or Babylon if !FPS.has(storage, fp_btc_pk_hex) { continue; @@ -244,7 +244,7 @@ pub fn handle_active_delegation( // Update aggregated voting power by FP fps.update(storage, fp_btc_pk_hex, height, |fp_state| { let mut fp_state = fp_state.unwrap_or_default(); - fp_state.power = fp_state.power.saturating_add(delegation.total_sat); + fp_state.power = fp_state.power.saturating_add(active_delegation.total_sat); Ok::<_, ContractError>(fp_state) })?; @@ -255,7 +255,8 @@ pub fn handle_active_delegation( return Err(ContractError::FinalityProviderNotRegistered); } // Add this BTC delegation - DELEGATIONS.save(storage, staking_tx_hash.as_ref(), delegation)?; + let delegation = BtcDelegation::try_from(active_delegation)?; + DELEGATIONS.save(storage, staking_tx_hash.as_ref(), &delegation)?; // Store activated height, if first delegation if ACTIVATED_HEIGHT.may_load(storage)?.is_none() { @@ -315,17 +316,10 @@ fn handle_undelegation( fn btc_undelegate( storage: &mut dyn Storage, staking_tx_hash: &Txid, - btc_del: &mut ActiveBtcDelegation, - unbondind_tx_sig: &[u8], + btc_del: &mut BtcDelegation, + unbonding_tx_sig: &[u8], ) -> Result<(), ContractError> { - match &mut btc_del.undelegation_info { - Some(undelegation_info) => { - undelegation_info.delegator_unbonding_sig = unbondind_tx_sig.to_vec().into(); - } - None => { - return Err(ContractError::MissingUnbondingInfo); - } - } + btc_del.undelegation_info.delegator_unbonding_sig = unbonding_tx_sig.to_vec().into(); // Set BTC delegation back to KV store DELEGATIONS.save(storage, staking_tx_hash.as_ref(), btc_del)?; @@ -439,7 +433,7 @@ pub(crate) mod tests { use crate::queries; // Compute staking tx hash of an active delegation - pub(crate) fn staking_tx_hash(del: &ActiveBtcDelegation) -> Txid { + pub(crate) fn staking_tx_hash(del: &BtcDelegation) -> Txid { let staking_tx: Transaction = bitcoin::consensus::deserialize(&del.staking_tx).unwrap(); staking_tx.txid() } @@ -573,9 +567,10 @@ pub(crate) mod tests { assert_eq!(0, res.messages.len()); // Check the active delegation is being stored - let staking_tx_hash_hex = staking_tx_hash(&active_delegation).to_string(); + let delegation = BtcDelegation::try_from(&active_delegation).unwrap(); + let staking_tx_hash_hex = staking_tx_hash(&delegation).to_string(); let query_res = queries::delegation(deps.as_ref(), staking_tx_hash_hex).unwrap(); - assert_eq!(query_res, active_delegation); + assert_eq!(query_res, delegation); // Check that the finality provider power has been updated let fp = queries::finality_provider_info(deps.as_ref(), new_fp.btc_pk_hex.clone(), None) @@ -621,10 +616,11 @@ pub(crate) mod tests { // Check the delegation is active (it has no unbonding or slashing tx signature) let active_delegation_undelegation = active_delegation.undelegation_info.clone().unwrap(); // Compute the staking tx hash - let staking_tx_hash_hex = staking_tx_hash(&active_delegation).to_string(); + let delegation = BtcDelegation::try_from(&active_delegation).unwrap(); + let staking_tx_hash_hex = staking_tx_hash(&delegation).to_string(); let btc_del = queries::delegation(deps.as_ref(), staking_tx_hash_hex.clone()).unwrap(); - let btc_undelegation = btc_del.undelegation_info.unwrap(); + let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, BtcUndelegationInfo { @@ -656,7 +652,7 @@ pub(crate) mod tests { // Check the delegation is not active any more (updated with the unbonding tx signature) let active_delegation_undelegation = active_delegation.undelegation_info.unwrap(); let btc_del = queries::delegation(deps.as_ref(), staking_tx_hash_hex).unwrap(); - let btc_undelegation = btc_del.undelegation_info.unwrap(); + let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, BtcUndelegationInfo { diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index ccd503df..952fcd45 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -1,17 +1,124 @@ use cosmwasm_schema::cw_serde; use cw_storage_plus::{IndexedSnapshotMap, Item, Map, MultiIndex, Strategy}; +use crate::error::ContractError; use crate::msg::FinalityProviderInfo; -use babylon_apis::btc_staking_api::{ActiveBtcDelegation, FinalityProvider, HASH_SIZE}; - use crate::state::fp_index::FinalityProviderIndexes; +use babylon_apis::btc_staking_api::{ + ActiveBtcDelegation, BTCDelegationStatus, BtcUndelegationInfo, CovenantAdaptorSignatures, + FinalityProvider, HASH_SIZE, +}; +use babylon_apis::Bytes; + +#[cw_serde] +pub struct BtcDelegation { + /// staker_addr is the address to receive rewards from BTC delegation + pub staker_addr: String, + /// btc_pk_hex is the Bitcoin secp256k1 PK of the BTC delegator. + /// The PK follows encoding in BIP-340 spec in hex format + pub btc_pk_hex: String, + /// fp_btc_pk_list is the list of BIP-340 PKs of the finality providers that + /// this BTC delegation delegates to + pub fp_btc_pk_list: Vec, + /// start_height is the start BTC height of the BTC delegation. + /// It is the start BTC height of the time-lock + pub start_height: u64, + /// end_height is the end height of the BTC delegation + /// it is the end BTC height of the time-lock - w + pub end_height: u64, + /// total_sat is the total BTC stakes in this delegation, quantified in satoshi + pub total_sat: u64, + /// staking_tx is the staking tx + pub staking_tx: Bytes, + /// slashing_tx is the slashing tx + pub slashing_tx: Bytes, + /// 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. + pub delegator_slashing_sig: Bytes, + /// 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. + pub covenant_sigs: Vec, + /// staking_output_idx is the index of the staking output in the staking tx + pub staking_output_idx: u32, + /// unbonding_time is used in unbonding output time-lock path and in slashing transactions + /// change outputs + pub unbonding_time: u32, + /// undelegation_info is the undelegation info of this delegation. + pub undelegation_info: BtcUndelegationInfo, + /// params version used to validate the delegation + pub params_version: u32, +} + +impl BtcDelegation { + pub fn is_active(&self) -> bool { + // TODO: Implement full delegation status checks (needs BTC height) + // self.get_status(btc_height, w) == BTCDelegationStatus::ACTIVE + !self.is_unbonded_early() + } + + fn is_unbonded_early(&self) -> bool { + !self.undelegation_info.delegator_unbonding_sig.is_empty() + } + + pub fn get_status(&self, btc_height: u64, w: u64) -> BTCDelegationStatus { + // Manually unbonded, staking tx time-lock has not begun, is less than w BTC blocks left, or + // has expired + if self.is_unbonded_early() + || btc_height < self.start_height + || btc_height + w > self.end_height + { + BTCDelegationStatus::UNBONDED + } else { + // At this point, the BTC delegation has an active time-lock, and Babylon is not aware of + // an unbonding tx with the delegator's signature + BTCDelegationStatus::ACTIVE + } + } +} + +impl TryFrom for BtcDelegation { + type Error = ContractError; + + fn try_from(delegation: ActiveBtcDelegation) -> Result { + let undelegation_info = match delegation.undelegation_info { + Some(info) => info, + None => return Err(ContractError::MissingUnbondingInfo {}), + }; + Ok(BtcDelegation { + staker_addr: delegation.staker_addr, + btc_pk_hex: delegation.btc_pk_hex, + fp_btc_pk_list: delegation.fp_btc_pk_list, + start_height: delegation.start_height, + end_height: delegation.end_height, + total_sat: delegation.total_sat, + staking_tx: delegation.staking_tx.to_vec(), + slashing_tx: delegation.slashing_tx.to_vec(), + delegator_slashing_sig: delegation.delegator_slashing_sig.to_vec(), + covenant_sigs: delegation.covenant_sigs, + staking_output_idx: delegation.staking_output_idx, + unbonding_time: delegation.unbonding_time, + undelegation_info, + params_version: delegation.params_version, + }) + } +} + +impl TryFrom<&ActiveBtcDelegation> for BtcDelegation { + type Error = ContractError; + + fn try_from(delegation: &ActiveBtcDelegation) -> Result { + BtcDelegation::try_from(delegation.clone()) + } +} /// Finality providers by their BTC public key pub(crate) const FPS: Map<&str, FinalityProvider> = Map::new("fps"); /// Delegations by staking tx hash /// TODO: create a new DB object for BTC delegation -pub(crate) const DELEGATIONS: Map<&[u8; HASH_SIZE], ActiveBtcDelegation> = Map::new("delegations"); +pub(crate) const DELEGATIONS: Map<&[u8; HASH_SIZE], BtcDelegation> = Map::new("delegations"); /// Map of staking hashes by finality provider pub(crate) const FP_DELEGATIONS: Map<&str, Vec>> = Map::new("fp_delegations"); /// Reverse map of finality providers by staking hash diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index 6663fc53..854eab13 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -235,39 +235,6 @@ pub struct ActiveBtcDelegation { pub params_version: u32, } -impl ActiveBtcDelegation { - pub fn is_active(&self) -> bool { - // TODO: Implement full delegation status checks (needs BTC height) - // self.get_status(btc_height, w) == BTCDelegationStatus::ACTIVE - !self.is_unbonded_early() - } - - fn is_unbonded_early(&self) -> bool { - if let Some(undelegation_info) = &self.undelegation_info { - !undelegation_info.delegator_unbonding_sig.is_empty() - } else { - // Can only happen if the state is corrupted. - // Every BTC delegation has to have undelegation info - true // Consider broken delegations as unbonded - } - } - - pub fn get_status(&self, btc_height: u64, w: u64) -> BTCDelegationStatus { - // Manually unbonded, staking tx time-lock has not begun, is less than w BTC blocks left, or - // has expired - if self.is_unbonded_early() - || btc_height < self.start_height - || btc_height + w > self.end_height - { - BTCDelegationStatus::UNBONDED - } else { - // At this point, the BTC delegation has an active time-lock, and Babylon is not aware of - // an unbonding tx with the delegator's signature - BTCDelegationStatus::ACTIVE - } - } -} - /// CovenantAdaptorSignatures is a list adaptor signatures signed by the /// covenant with different finality provider's public keys as encryption keys #[cw_serde] From a6017919bdd0422271049675330444d13c567d18 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 9 Aug 2024 16:42:40 +0200 Subject: [PATCH 2/7] Introduce UndelegationInfo for state as well --- contracts/btc-staking/src/staking.rs | 27 ++++++------ contracts/btc-staking/src/state/staking.rs | 48 ++++++++++++++++++++-- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index 6e5d58ed..ef6876d8 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -423,14 +423,13 @@ pub(crate) mod tests { use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; - use babylon_apis::btc_staking_api::BtcUndelegationInfo; - use crate::contract::tests::{ create_new_finality_provider, get_active_btc_delegation, CREATOR, INIT_ADMIN, }; use crate::contract::{execute, instantiate}; use crate::msg::{ExecuteMsg, InstantiateMsg}; use crate::queries; + use crate::state::staking::UndelegationInfo; // Compute staking tx hash of an active delegation pub(crate) fn staking_tx_hash(del: &BtcDelegation) -> Txid { @@ -623,11 +622,13 @@ pub(crate) mod tests { let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, - BtcUndelegationInfo { - unbonding_tx: active_delegation_undelegation.unbonding_tx, - slashing_tx: active_delegation_undelegation.slashing_tx, - delegator_unbonding_sig: Binary::new(vec![]), - delegator_slashing_sig: active_delegation_undelegation.delegator_slashing_sig, + UndelegationInfo { + unbonding_tx: active_delegation_undelegation.unbonding_tx.to_vec(), + slashing_tx: active_delegation_undelegation.slashing_tx.to_vec(), + delegator_unbonding_sig: vec![], + delegator_slashing_sig: active_delegation_undelegation + .delegator_slashing_sig + .to_vec(), covenant_unbonding_sig_list: vec![], covenant_slashing_sigs: vec![], } @@ -655,11 +656,13 @@ pub(crate) mod tests { let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, - BtcUndelegationInfo { - unbonding_tx: active_delegation_undelegation.unbonding_tx, - slashing_tx: active_delegation_undelegation.slashing_tx, - delegator_unbonding_sig: Binary::new(vec![0x01, 0x02, 0x03]), - delegator_slashing_sig: active_delegation_undelegation.delegator_slashing_sig, + UndelegationInfo { + unbonding_tx: active_delegation_undelegation.unbonding_tx.into(), + slashing_tx: active_delegation_undelegation.slashing_tx.into(), + delegator_unbonding_sig: vec![0x01, 0x02, 0x03], + delegator_slashing_sig: active_delegation_undelegation + .delegator_slashing_sig + .into(), covenant_unbonding_sig_list: vec![], covenant_slashing_sigs: vec![], } diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index 952fcd45..0d25c023 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -6,7 +6,7 @@ use crate::msg::FinalityProviderInfo; use crate::state::fp_index::FinalityProviderIndexes; use babylon_apis::btc_staking_api::{ ActiveBtcDelegation, BTCDelegationStatus, BtcUndelegationInfo, CovenantAdaptorSignatures, - FinalityProvider, HASH_SIZE, + FinalityProvider, SignatureInfo, HASH_SIZE, }; use babylon_apis::Bytes; @@ -46,7 +46,7 @@ pub struct BtcDelegation { /// change outputs pub unbonding_time: u32, /// undelegation_info is the undelegation info of this delegation. - pub undelegation_info: BtcUndelegationInfo, + pub undelegation_info: UndelegationInfo, /// params version used to validate the delegation pub params_version: u32, } @@ -82,7 +82,7 @@ impl TryFrom for BtcDelegation { type Error = ContractError; fn try_from(delegation: ActiveBtcDelegation) -> Result { - let undelegation_info = match delegation.undelegation_info { + let btc_undelegation_info = match delegation.undelegation_info { Some(info) => info, None => return Err(ContractError::MissingUnbondingInfo {}), }; @@ -99,7 +99,7 @@ impl TryFrom for BtcDelegation { covenant_sigs: delegation.covenant_sigs, staking_output_idx: delegation.staking_output_idx, unbonding_time: delegation.unbonding_time, - undelegation_info, + undelegation_info: btc_undelegation_info.into(), params_version: delegation.params_version, }) } @@ -113,6 +113,46 @@ impl TryFrom<&ActiveBtcDelegation> for BtcDelegation { } } +#[cw_serde] +pub struct UndelegationInfo { + /// 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. + pub unbonding_tx: Bytes, + /// 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. + pub delegator_unbonding_sig: Bytes, + /// covenant_unbonding_sig_list is the list of signatures on the unbonding tx + /// by covenant members + pub covenant_unbonding_sig_list: Vec, + /// slashing_tx is the unbonding slashing tx + pub slashing_tx: Bytes, + /// 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. + pub delegator_slashing_sig: Bytes, + /// 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. + pub covenant_slashing_sigs: Vec, +} + +impl From for UndelegationInfo { + fn from(info: BtcUndelegationInfo) -> Self { + UndelegationInfo { + unbonding_tx: info.unbonding_tx.to_vec(), + delegator_unbonding_sig: info.delegator_unbonding_sig.to_vec(), + covenant_unbonding_sig_list: info.covenant_unbonding_sig_list, + slashing_tx: info.slashing_tx.to_vec(), + delegator_slashing_sig: info.delegator_slashing_sig.to_vec(), + covenant_slashing_sigs: info.covenant_slashing_sigs, + } + } +} + /// Finality providers by their BTC public key pub(crate) const FPS: Map<&str, FinalityProvider> = Map::new("fps"); From 9a0234021c26878f200a28491c5002156fc30bd9 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 12 Aug 2024 10:10:48 +0200 Subject: [PATCH 3/7] Simplify undelegation info member Remove optional --- contracts/babylon/src/ibc.rs | 90 ++++++++++++---------- contracts/btc-staking/src/contract.rs | 4 +- contracts/btc-staking/src/error.rs | 2 - contracts/btc-staking/src/queries.rs | 13 ++-- contracts/btc-staking/src/staking.rs | 42 +++++----- contracts/btc-staking/src/state/staking.rs | 25 ++---- packages/apis/src/btc_staking_api.rs | 2 +- 7 files changed, 82 insertions(+), 96 deletions(-) diff --git a/contracts/babylon/src/ibc.rs b/contracts/babylon/src/ibc.rs index f3519fc2..55e51e01 100644 --- a/contracts/babylon/src/ibc.rs +++ b/contracts/babylon/src/ibc.rs @@ -226,45 +226,19 @@ pub(crate) mod ibc_packet { active_del: btc_staking .active_del .iter() - .map(|d| ActiveBtcDelegation { - staker_addr: d.staker_addr.clone(), - btc_pk_hex: d.btc_pk_hex.clone(), - fp_btc_pk_list: d.fp_btc_pk_list.clone(), - start_height: d.start_height, - end_height: d.end_height, - total_sat: d.total_sat, - staking_tx: d.staking_tx.to_vec().into(), - slashing_tx: d.slashing_tx.to_vec().into(), - delegator_slashing_sig: d.delegator_slashing_sig.to_vec().into(), - covenant_sigs: d - .covenant_sigs - .iter() - .map(|s| CovenantAdaptorSignatures { - cov_pk: s.cov_pk.to_vec().into(), - adaptor_sigs: s - .adaptor_sigs - .iter() - .map(|a| a.to_vec().into()) - .collect(), - }) - .collect(), - staking_output_idx: d.staking_output_idx, - unbonding_time: d.unbonding_time, - undelegation_info: d.undelegation_info.as_ref().map(|ui| BtcUndelegationInfo { - unbonding_tx: ui.unbonding_tx.to_vec().into(), - delegator_unbonding_sig: ui.delegator_unbonding_sig.to_vec().into(), - covenant_unbonding_sig_list: ui - .covenant_unbonding_sig_list - .iter() - .map(|s| SignatureInfo { - pk: s.pk.to_vec().into(), - sig: s.sig.to_vec().into(), - }) - .collect(), - slashing_tx: ui.slashing_tx.to_vec().into(), - delegator_slashing_sig: ui.delegator_slashing_sig.to_vec().into(), - covenant_slashing_sigs: ui - .covenant_slashing_sigs + .map(|d| { + Ok(ActiveBtcDelegation { + staker_addr: d.staker_addr.clone(), + btc_pk_hex: d.btc_pk_hex.clone(), + fp_btc_pk_list: d.fp_btc_pk_list.clone(), + start_height: d.start_height, + end_height: d.end_height, + total_sat: d.total_sat, + staking_tx: d.staking_tx.to_vec().into(), + slashing_tx: d.slashing_tx.to_vec().into(), + delegator_slashing_sig: d.delegator_slashing_sig.to_vec().into(), + covenant_sigs: d + .covenant_sigs .iter() .map(|s| CovenantAdaptorSignatures { cov_pk: s.cov_pk.to_vec().into(), @@ -275,10 +249,42 @@ pub(crate) mod ibc_packet { .collect(), }) .collect(), - }), - params_version: d.params_version, + staking_output_idx: d.staking_output_idx, + unbonding_time: d.unbonding_time, + undelegation_info: d + .undelegation_info + .as_ref() + .map(|ui| BtcUndelegationInfo { + unbonding_tx: ui.unbonding_tx.to_vec().into(), + delegator_unbonding_sig: ui.delegator_unbonding_sig.to_vec().into(), + covenant_unbonding_sig_list: ui + .covenant_unbonding_sig_list + .iter() + .map(|s| SignatureInfo { + pk: s.pk.to_vec().into(), + sig: s.sig.to_vec().into(), + }) + .collect(), + slashing_tx: ui.slashing_tx.to_vec().into(), + delegator_slashing_sig: ui.delegator_slashing_sig.to_vec().into(), + covenant_slashing_sigs: ui + .covenant_slashing_sigs + .iter() + .map(|s| CovenantAdaptorSignatures { + cov_pk: s.cov_pk.to_vec().into(), + adaptor_sigs: s + .adaptor_sigs + .iter() + .map(|a| a.to_vec().into()) + .collect(), + }) + .collect(), + }) + .ok_or(StdError::generic_err("undelegation info not set"))?, + params_version: d.params_version, + }) }) - .collect(), + .collect::>()?, slashed_del: vec![], // FIXME: Route this unbonded_del: btc_staking .unbonded_del diff --git a/contracts/btc-staking/src/contract.rs b/contracts/btc-staking/src/contract.rs index 7bb4068a..d3d3e199 100644 --- a/contracts/btc-staking/src/contract.rs +++ b/contracts/btc-staking/src/contract.rs @@ -330,7 +330,7 @@ pub(crate) mod tests { .collect(), staking_output_idx: del.staking_output_idx, unbonding_time: del.unbonding_time, - undelegation_info: Some(BtcUndelegationInfo { + 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(vec![]), @@ -339,7 +339,7 @@ pub(crate) mod tests { ), covenant_unbonding_sig_list: vec![], covenant_slashing_sigs: vec![], - }), + }, params_version: del.params_version, } } diff --git a/contracts/btc-staking/src/error.rs b/contracts/btc-staking/src/error.rs index a4d99edc..ed18bd0c 100644 --- a/contracts/btc-staking/src/error.rs +++ b/contracts/btc-staking/src/error.rs @@ -48,8 +48,6 @@ pub enum ContractError { DelegationAlreadyExists(String), #[error("Invalid Btc tx: {0}")] InvalidBtcTx(String), - #[error("Missing unbonding info")] - MissingUnbondingInfo, #[error("Empty unbonding tx")] EmptyUnbondingTx, #[error("Empty Slashing tx")] diff --git a/contracts/btc-staking/src/queries.rs b/contracts/btc-staking/src/queries.rs index 1703915b..6cf6f92e 100644 --- a/contracts/btc-staking/src/queries.rs +++ b/contracts/btc-staking/src/queries.rs @@ -366,7 +366,7 @@ mod tests { assert_eq!(dels.len(), 2); // Sort original delegations by staking tx hash (to compare with the query result) - let sorted_dels = sort_delegations(&[del1.try_into().unwrap(), del2.try_into().unwrap()]); + let sorted_dels = sort_delegations(&[del1.into(), del2.into()]); assert_eq!(dels[0], sorted_dels[0]); assert_eq!(dels[1], sorted_dels[1]); @@ -436,16 +436,13 @@ mod tests { .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().try_into().unwrap(), - del2.clone().try_into().unwrap(), - ]); + 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.try_into().unwrap()).to_string(); + let staking_tx_hash_hex = staking_tx_hash(&del2.into()).to_string(); let msg = ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![], @@ -462,7 +459,7 @@ mod tests { .unwrap() .delegations; assert_eq!(dels.len(), 1); - assert_eq!(dels[0], del1.try_into().unwrap()); + 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)) @@ -593,7 +590,7 @@ mod tests { // Unbond the first delegation // Compute staking tx hash - let staking_tx_hash_hex = staking_tx_hash(&del1.try_into().unwrap()).to_string(); + let staking_tx_hash_hex = staking_tx_hash(&del1.into()).to_string(); let msg = ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![], diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index ef6876d8..28667651 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -181,26 +181,20 @@ pub fn handle_active_delegation( // All good, check initial BTC undelegation information is present // TODO: Check that the sent undelegation info is valid - match active_delegation.undelegation_info { - Some(ref undelegation_info) => { - // Check that the unbonding tx is there - if undelegation_info.unbonding_tx.is_empty() { - return Err(ContractError::EmptyUnbondingTx); - } + let undelegation_info = active_delegation.clone().undelegation_info; + // Check that the unbonding tx is there + if undelegation_info.unbonding_tx.is_empty() { + return Err(ContractError::EmptyUnbondingTx); + } - // Check that the unbonding slashing tx is there - if undelegation_info.slashing_tx.is_empty() { - return Err(ContractError::EmptySlashingTx); - } + // Check that the unbonding slashing tx is there + if undelegation_info.slashing_tx.is_empty() { + return Err(ContractError::EmptySlashingTx); + } - // Check that the delegator slashing signature is there - if undelegation_info.delegator_slashing_sig.is_empty() { - return Err(ContractError::EmptySignature); - } - } - None => { - return Err(ContractError::MissingUnbondingInfo); - } + // Check that the delegator slashing signature is there + if undelegation_info.delegator_slashing_sig.is_empty() { + return Err(ContractError::EmptySignature); } // Check staking tx is not duplicated @@ -255,7 +249,7 @@ pub fn handle_active_delegation( return Err(ContractError::FinalityProviderNotRegistered); } // Add this BTC delegation - let delegation = BtcDelegation::try_from(active_delegation)?; + let delegation = BtcDelegation::from(active_delegation); DELEGATIONS.save(storage, staking_tx_hash.as_ref(), &delegation)?; // Store activated height, if first delegation @@ -319,7 +313,7 @@ fn btc_undelegate( btc_del: &mut BtcDelegation, unbonding_tx_sig: &[u8], ) -> Result<(), ContractError> { - btc_del.undelegation_info.delegator_unbonding_sig = unbonding_tx_sig.to_vec().into(); + btc_del.undelegation_info.delegator_unbonding_sig = unbonding_tx_sig.to_vec(); // Set BTC delegation back to KV store DELEGATIONS.save(storage, staking_tx_hash.as_ref(), btc_del)?; @@ -566,7 +560,7 @@ pub(crate) mod tests { assert_eq!(0, res.messages.len()); // Check the active delegation is being stored - let delegation = BtcDelegation::try_from(&active_delegation).unwrap(); + let delegation = BtcDelegation::from(&active_delegation); let staking_tx_hash_hex = staking_tx_hash(&delegation).to_string(); let query_res = queries::delegation(deps.as_ref(), staking_tx_hash_hex).unwrap(); assert_eq!(query_res, delegation); @@ -613,9 +607,9 @@ pub(crate) mod tests { assert_eq!(0, res.messages.len()); // Check the delegation is active (it has no unbonding or slashing tx signature) - let active_delegation_undelegation = active_delegation.undelegation_info.clone().unwrap(); + let active_delegation_undelegation = active_delegation.undelegation_info.clone(); // Compute the staking tx hash - let delegation = BtcDelegation::try_from(&active_delegation).unwrap(); + let delegation = BtcDelegation::from(&active_delegation); let staking_tx_hash_hex = staking_tx_hash(&delegation).to_string(); let btc_del = queries::delegation(deps.as_ref(), staking_tx_hash_hex.clone()).unwrap(); @@ -651,7 +645,7 @@ pub(crate) mod tests { assert_eq!(0, res.messages.len()); // Check the delegation is not active any more (updated with the unbonding tx signature) - let active_delegation_undelegation = active_delegation.undelegation_info.unwrap(); + let active_delegation_undelegation = active_delegation.undelegation_info; let btc_del = queries::delegation(deps.as_ref(), staking_tx_hash_hex).unwrap(); let btc_undelegation = btc_del.undelegation_info; assert_eq!( diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index 0d25c023..01b25a00 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::error::ContractError; use crate::msg::FinalityProviderInfo; use crate::state::fp_index::FinalityProviderIndexes; use babylon_apis::btc_staking_api::{ @@ -78,15 +77,9 @@ impl BtcDelegation { } } -impl TryFrom for BtcDelegation { - type Error = ContractError; - - fn try_from(delegation: ActiveBtcDelegation) -> Result { - let btc_undelegation_info = match delegation.undelegation_info { - Some(info) => info, - None => return Err(ContractError::MissingUnbondingInfo {}), - }; - Ok(BtcDelegation { +impl From for BtcDelegation { + fn from(delegation: ActiveBtcDelegation) -> Self { + BtcDelegation { staker_addr: delegation.staker_addr, btc_pk_hex: delegation.btc_pk_hex, fp_btc_pk_list: delegation.fp_btc_pk_list, @@ -99,17 +92,15 @@ impl TryFrom for BtcDelegation { covenant_sigs: delegation.covenant_sigs, staking_output_idx: delegation.staking_output_idx, unbonding_time: delegation.unbonding_time, - undelegation_info: btc_undelegation_info.into(), + undelegation_info: delegation.undelegation_info.into(), params_version: delegation.params_version, - }) + } } } -impl TryFrom<&ActiveBtcDelegation> for BtcDelegation { - type Error = ContractError; - - fn try_from(delegation: &ActiveBtcDelegation) -> Result { - BtcDelegation::try_from(delegation.clone()) +impl From<&ActiveBtcDelegation> for BtcDelegation { + fn from(delegation: &ActiveBtcDelegation) -> Self { + BtcDelegation::from(delegation.clone()) } } diff --git a/packages/apis/src/btc_staking_api.rs b/packages/apis/src/btc_staking_api.rs index 854eab13..95370416 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -230,7 +230,7 @@ pub struct ActiveBtcDelegation { /// change outputs pub unbonding_time: u32, /// undelegation_info is the undelegation info of this delegation. - pub undelegation_info: Option, + pub undelegation_info: BtcUndelegationInfo, /// params version used to validate the delegation pub params_version: u32, } From b9b83d0684e6df728ee1d1ce440292859f909d28 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 12 Aug 2024 10:48:18 +0200 Subject: [PATCH 4/7] Introduce ConvenantAdaptorSignatures as well for completeness --- contracts/btc-staking/src/state/staking.rs | 39 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index 01b25a00..51228341 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -4,10 +4,10 @@ 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::{ - ActiveBtcDelegation, BTCDelegationStatus, BtcUndelegationInfo, CovenantAdaptorSignatures, - FinalityProvider, SignatureInfo, HASH_SIZE, + ActiveBtcDelegation, BTCDelegationStatus, BtcUndelegationInfo, FinalityProvider, SignatureInfo, + HASH_SIZE, }; -use babylon_apis::Bytes; +use babylon_apis::{btc_staking_api, Bytes}; #[cw_serde] pub struct BtcDelegation { @@ -89,7 +89,11 @@ impl From for BtcDelegation { staking_tx: delegation.staking_tx.to_vec(), slashing_tx: delegation.slashing_tx.to_vec(), delegator_slashing_sig: delegation.delegator_slashing_sig.to_vec(), - covenant_sigs: delegation.covenant_sigs, + covenant_sigs: delegation + .covenant_sigs + .into_iter() + .map(|sig| sig.into()) + .collect(), staking_output_idx: delegation.staking_output_idx, unbonding_time: delegation.unbonding_time, undelegation_info: delegation.undelegation_info.into(), @@ -104,6 +108,27 @@ impl From<&ActiveBtcDelegation> for BtcDelegation { } } +#[cw_serde] +pub struct CovenantAdaptorSignatures { + /// cov_pk is the public key of the covenant emulator, used as the public key of the adaptor signature + pub cov_pk: Bytes, + /// adaptor_sigs is a list of adaptor signatures, each encrypted by a restaked BTC finality provider's public key + pub adaptor_sigs: Vec, +} + +impl From for CovenantAdaptorSignatures { + fn from(info: btc_staking_api::CovenantAdaptorSignatures) -> Self { + CovenantAdaptorSignatures { + cov_pk: info.cov_pk.to_vec(), + adaptor_sigs: info + .adaptor_sigs + .into_iter() + .map(|sig| sig.to_vec()) + .collect(), + } + } +} + #[cw_serde] pub struct UndelegationInfo { /// unbonding_tx is the transaction which will transfer the funds from staking @@ -139,7 +164,11 @@ impl From for UndelegationInfo { covenant_unbonding_sig_list: info.covenant_unbonding_sig_list, slashing_tx: info.slashing_tx.to_vec(), delegator_slashing_sig: info.delegator_slashing_sig.to_vec(), - covenant_slashing_sigs: info.covenant_slashing_sigs, + covenant_slashing_sigs: info + .covenant_slashing_sigs + .into_iter() + .map(|sig| sig.into()) + .collect(), } } } From c1bf80ec7420740372bd6c55797b0bef3aa33241 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 12 Aug 2024 10:56:19 +0200 Subject: [PATCH 5/7] Better / consistent naming / uses --- contracts/btc-staking/src/staking.rs | 6 +- contracts/btc-staking/src/state/staking.rs | 69 +++++++++++----------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/contracts/btc-staking/src/staking.rs b/contracts/btc-staking/src/staking.rs index 28667651..d2acb1ba 100644 --- a/contracts/btc-staking/src/staking.rs +++ b/contracts/btc-staking/src/staking.rs @@ -423,7 +423,7 @@ pub(crate) mod tests { use crate::contract::{execute, instantiate}; use crate::msg::{ExecuteMsg, InstantiateMsg}; use crate::queries; - use crate::state::staking::UndelegationInfo; + use crate::state::staking::BtcUndelegationInfo; // Compute staking tx hash of an active delegation pub(crate) fn staking_tx_hash(del: &BtcDelegation) -> Txid { @@ -616,7 +616,7 @@ pub(crate) mod tests { let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, - UndelegationInfo { + BtcUndelegationInfo { unbonding_tx: active_delegation_undelegation.unbonding_tx.to_vec(), slashing_tx: active_delegation_undelegation.slashing_tx.to_vec(), delegator_unbonding_sig: vec![], @@ -650,7 +650,7 @@ pub(crate) mod tests { let btc_undelegation = btc_del.undelegation_info; assert_eq!( btc_undelegation, - UndelegationInfo { + BtcUndelegationInfo { unbonding_tx: active_delegation_undelegation.unbonding_tx.into(), slashing_tx: active_delegation_undelegation.slashing_tx.into(), delegator_unbonding_sig: vec![0x01, 0x02, 0x03], diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index 51228341..ce85cad4 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -4,8 +4,7 @@ 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::{ - ActiveBtcDelegation, BTCDelegationStatus, BtcUndelegationInfo, FinalityProvider, SignatureInfo, - HASH_SIZE, + BTCDelegationStatus, FinalityProvider, SignatureInfo, HASH_SIZE, }; use babylon_apis::{btc_staking_api, Bytes}; @@ -45,7 +44,7 @@ pub struct BtcDelegation { /// change outputs pub unbonding_time: u32, /// undelegation_info is the undelegation info of this delegation. - pub undelegation_info: UndelegationInfo, + pub undelegation_info: BtcUndelegationInfo, /// params version used to validate the delegation pub params_version: u32, } @@ -77,34 +76,34 @@ impl BtcDelegation { } } -impl From for BtcDelegation { - fn from(delegation: ActiveBtcDelegation) -> Self { +impl From for BtcDelegation { + fn from(active_delegation: btc_staking_api::ActiveBtcDelegation) -> Self { BtcDelegation { - staker_addr: delegation.staker_addr, - btc_pk_hex: delegation.btc_pk_hex, - fp_btc_pk_list: delegation.fp_btc_pk_list, - start_height: delegation.start_height, - end_height: delegation.end_height, - total_sat: delegation.total_sat, - staking_tx: delegation.staking_tx.to_vec(), - slashing_tx: delegation.slashing_tx.to_vec(), - delegator_slashing_sig: delegation.delegator_slashing_sig.to_vec(), - covenant_sigs: delegation + staker_addr: active_delegation.staker_addr, + btc_pk_hex: active_delegation.btc_pk_hex, + fp_btc_pk_list: active_delegation.fp_btc_pk_list, + start_height: active_delegation.start_height, + end_height: active_delegation.end_height, + total_sat: active_delegation.total_sat, + staking_tx: active_delegation.staking_tx.to_vec(), + slashing_tx: active_delegation.slashing_tx.to_vec(), + delegator_slashing_sig: active_delegation.delegator_slashing_sig.to_vec(), + covenant_sigs: active_delegation .covenant_sigs .into_iter() .map(|sig| sig.into()) .collect(), - staking_output_idx: delegation.staking_output_idx, - unbonding_time: delegation.unbonding_time, - undelegation_info: delegation.undelegation_info.into(), - params_version: delegation.params_version, + staking_output_idx: active_delegation.staking_output_idx, + unbonding_time: active_delegation.unbonding_time, + undelegation_info: active_delegation.undelegation_info.into(), + params_version: active_delegation.params_version, } } } -impl From<&ActiveBtcDelegation> for BtcDelegation { - fn from(delegation: &ActiveBtcDelegation) -> Self { - BtcDelegation::from(delegation.clone()) +impl From<&btc_staking_api::ActiveBtcDelegation> for BtcDelegation { + fn from(active_delegation: &btc_staking_api::ActiveBtcDelegation) -> Self { + BtcDelegation::from(active_delegation.clone()) } } @@ -117,10 +116,10 @@ pub struct CovenantAdaptorSignatures { } impl From for CovenantAdaptorSignatures { - fn from(info: btc_staking_api::CovenantAdaptorSignatures) -> Self { + fn from(cov_adaptor_sigs: btc_staking_api::CovenantAdaptorSignatures) -> Self { CovenantAdaptorSignatures { - cov_pk: info.cov_pk.to_vec(), - adaptor_sigs: info + cov_pk: cov_adaptor_sigs.cov_pk.to_vec(), + adaptor_sigs: cov_adaptor_sigs .adaptor_sigs .into_iter() .map(|sig| sig.to_vec()) @@ -130,7 +129,7 @@ impl From for CovenantAdaptorSignatu } #[cw_serde] -pub struct UndelegationInfo { +pub struct BtcUndelegationInfo { /// 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. @@ -156,15 +155,15 @@ pub struct UndelegationInfo { pub covenant_slashing_sigs: Vec, } -impl From for UndelegationInfo { - fn from(info: BtcUndelegationInfo) -> Self { - UndelegationInfo { - unbonding_tx: info.unbonding_tx.to_vec(), - delegator_unbonding_sig: info.delegator_unbonding_sig.to_vec(), - covenant_unbonding_sig_list: info.covenant_unbonding_sig_list, - slashing_tx: info.slashing_tx.to_vec(), - delegator_slashing_sig: info.delegator_slashing_sig.to_vec(), - covenant_slashing_sigs: info +impl From for BtcUndelegationInfo { + fn from(undelegation_info: btc_staking_api::BtcUndelegationInfo) -> Self { + BtcUndelegationInfo { + unbonding_tx: undelegation_info.unbonding_tx.to_vec(), + delegator_unbonding_sig: undelegation_info.delegator_unbonding_sig.to_vec(), + covenant_unbonding_sig_list: undelegation_info.covenant_unbonding_sig_list, + slashing_tx: undelegation_info.slashing_tx.to_vec(), + delegator_slashing_sig: undelegation_info.delegator_slashing_sig.to_vec(), + covenant_slashing_sigs: undelegation_info .covenant_slashing_sigs .into_iter() .map(|sig| sig.into()) From 949ca09ec995d75ca036068b556ae81640cb5b24 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 12 Aug 2024 11:01:11 +0200 Subject: [PATCH 6/7] Introduce state::staking::SignatureInfo for completeness --- contracts/btc-staking/src/state/staking.rs | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/contracts/btc-staking/src/state/staking.rs b/contracts/btc-staking/src/state/staking.rs index ce85cad4..82645bdc 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -3,9 +3,7 @@ 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, SignatureInfo, HASH_SIZE, -}; +use babylon_apis::btc_staking_api::{BTCDelegationStatus, FinalityProvider, HASH_SIZE}; use babylon_apis::{btc_staking_api, Bytes}; #[cw_serde] @@ -160,7 +158,11 @@ impl From for BtcUndelegationInfo { BtcUndelegationInfo { unbonding_tx: undelegation_info.unbonding_tx.to_vec(), delegator_unbonding_sig: undelegation_info.delegator_unbonding_sig.to_vec(), - covenant_unbonding_sig_list: undelegation_info.covenant_unbonding_sig_list, + covenant_unbonding_sig_list: undelegation_info + .covenant_unbonding_sig_list + .into_iter() + .map(|sig| sig.into()) + .collect(), slashing_tx: undelegation_info.slashing_tx.to_vec(), delegator_slashing_sig: undelegation_info.delegator_slashing_sig.to_vec(), covenant_slashing_sigs: undelegation_info @@ -172,6 +174,21 @@ impl From for BtcUndelegationInfo { } } +#[cw_serde] +pub struct SignatureInfo { + pub pk: Bytes, + pub sig: Bytes, +} + +impl From for SignatureInfo { + fn from(sig_info: btc_staking_api::SignatureInfo) -> Self { + SignatureInfo { + pk: sig_info.pk.to_vec(), + sig: sig_info.sig.to_vec(), + } + } +} + /// Finality providers by their BTC public key pub(crate) const FPS: Map<&str, FinalityProvider> = Map::new("fps"); From 9ec35bff143b31cb20edcf33626c3bde3d8ae2e0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 12 Aug 2024 11:26:33 +0200 Subject: [PATCH 7/7] Update schemas --- contracts/btc-staking/schema/btc-staking.json | 145 ++++++++++-------- contracts/btc-staking/schema/raw/execute.json | 8 +- .../schema/raw/response_to_delegation.json | 8 +- .../schema/raw/response_to_delegations.json | 129 +++++++++------- 4 files changed, 154 insertions(+), 136 deletions(-) diff --git a/contracts/btc-staking/schema/btc-staking.json b/contracts/btc-staking/schema/btc-staking.json index f5fbfb00..5cf86a0f 100644 --- a/contracts/btc-staking/schema/btc-staking.json +++ b/contracts/btc-staking/schema/btc-staking.json @@ -241,7 +241,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -328,12 +329,9 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } @@ -1296,7 +1294,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -1383,12 +1382,9 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } @@ -1516,14 +1512,13 @@ "delegations": { "type": "array", "items": { - "$ref": "#/definitions/ActiveBtcDelegation" + "$ref": "#/definitions/BtcDelegation" } } }, "additionalProperties": false, "definitions": { - "ActiveBtcDelegation": { - "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "BtcDelegation": { "type": "object", "required": [ "btc_pk_hex", @@ -1538,7 +1533,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -1554,11 +1550,12 @@ }, "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" - } - ] + "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", @@ -1581,11 +1578,12 @@ }, "slashing_tx": { "description": "slashing_tx is the slashing tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "staker_addr": { "description": "staker_addr is the address to receive rewards from BTC delegation", @@ -1599,11 +1597,12 @@ }, "staking_tx": { "description": "staking_tx is the staking tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "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", @@ -1625,24 +1624,16 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } }, "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 undeleagation", "type": "object", "required": [ "covenant_slashing_sigs", @@ -1669,41 +1660,44 @@ }, "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" - } - ] + "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.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "slashing_tx": { "description": "slashing_tx is the unbonding slashing tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "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.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "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", @@ -1714,22 +1708,27 @@ "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" + "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", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "additionalProperties": false }, "SignatureInfo": { - "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", "type": "object", "required": [ "pk", @@ -1737,10 +1736,20 @@ ], "properties": { "pk": { - "$ref": "#/definitions/Binary" + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "sig": { - "$ref": "#/definitions/Binary" + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "additionalProperties": false diff --git a/contracts/btc-staking/schema/raw/execute.json b/contracts/btc-staking/schema/raw/execute.json index e1befda1..bc7ba6d1 100644 --- a/contracts/btc-staking/schema/raw/execute.json +++ b/contracts/btc-staking/schema/raw/execute.json @@ -188,7 +188,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -275,12 +276,9 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } diff --git a/contracts/btc-staking/schema/raw/response_to_delegation.json b/contracts/btc-staking/schema/raw/response_to_delegation.json index e3388455..b30ac050 100644 --- a/contracts/btc-staking/schema/raw/response_to_delegation.json +++ b/contracts/btc-staking/schema/raw/response_to_delegation.json @@ -16,7 +16,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -103,12 +104,9 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } diff --git a/contracts/btc-staking/schema/raw/response_to_delegations.json b/contracts/btc-staking/schema/raw/response_to_delegations.json index cd2e13b4..06f7949a 100644 --- a/contracts/btc-staking/schema/raw/response_to_delegations.json +++ b/contracts/btc-staking/schema/raw/response_to_delegations.json @@ -9,14 +9,13 @@ "delegations": { "type": "array", "items": { - "$ref": "#/definitions/ActiveBtcDelegation" + "$ref": "#/definitions/BtcDelegation" } } }, "additionalProperties": false, "definitions": { - "ActiveBtcDelegation": { - "description": "ActiveBTCDelegation is a message sent when a BTC delegation newly receives covenant signatures and thus becomes active", + "BtcDelegation": { "type": "object", "required": [ "btc_pk_hex", @@ -31,7 +30,8 @@ "staking_tx", "start_height", "total_sat", - "unbonding_time" + "unbonding_time", + "undelegation_info" ], "properties": { "btc_pk_hex": { @@ -47,11 +47,12 @@ }, "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" - } - ] + "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", @@ -74,11 +75,12 @@ }, "slashing_tx": { "description": "slashing_tx is the slashing tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "staker_addr": { "description": "staker_addr is the address to receive rewards from BTC delegation", @@ -92,11 +94,12 @@ }, "staking_tx": { "description": "staking_tx is the staking tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "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", @@ -118,24 +121,16 @@ }, "undelegation_info": { "description": "undelegation_info is the undelegation info of this delegation.", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/BtcUndelegationInfo" - }, - { - "type": "null" } ] } }, "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 undeleagation", "type": "object", "required": [ "covenant_slashing_sigs", @@ -162,41 +157,44 @@ }, "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" - } - ] + "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.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "slashing_tx": { "description": "slashing_tx is the unbonding slashing tx", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "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.", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "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", @@ -207,22 +205,27 @@ "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" + "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", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "additionalProperties": false }, "SignatureInfo": { - "description": "SignatureInfo is a BIP-340 signature together with its signer's BIP-340 PK", "type": "object", "required": [ "pk", @@ -230,10 +233,20 @@ ], "properties": { "pk": { - "$ref": "#/definitions/Binary" + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } }, "sig": { - "$ref": "#/definitions/Binary" + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } } }, "additionalProperties": false