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/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 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/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..6cf6f92e 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.into(), del2.into()]); assert_eq!(dels[0], sorted_dels[0]); assert_eq!(dels[1], sorted_dels[1]); @@ -441,13 +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(), del2.clone()]); + 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).to_string(); + let staking_tx_hash_hex = staking_tx_hash(&del2.into()).to_string(); let msg = ExecuteMsg::BtcStaking { new_fp: vec![], active_del: vec![], @@ -464,7 +459,7 @@ mod tests { .unwrap() .delegations; assert_eq!(dels.len(), 1); - assert_eq!(dels[0], del1); + 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)) @@ -595,7 +590,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.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 f3db3f49..d2acb1ba 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,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 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 @@ -213,7 +207,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 +238,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 +249,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::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 +310,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(); // Set BTC delegation back to KV store DELEGATIONS.save(storage, staking_tx_hash.as_ref(), btc_del)?; @@ -429,17 +417,16 @@ 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::BtcUndelegationInfo; // 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 +560,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::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, 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) @@ -619,19 +607,22 @@ 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 staking_tx_hash_hex = staking_tx_hash(&active_delegation).to_string(); + 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(); - let btc_undelegation = btc_del.undelegation_info.unwrap(); + 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, + 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![], } @@ -654,16 +645,18 @@ 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.unwrap(); + 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, + 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 ccd503df..82645bdc 100644 --- a/contracts/btc-staking/src/state/staking.rs +++ b/contracts/btc-staking/src/state/staking.rs @@ -2,16 +2,199 @@ use cosmwasm_schema::cw_serde; use cw_storage_plus::{IndexedSnapshotMap, Item, Map, MultiIndex, Strategy}; 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::{BTCDelegationStatus, FinalityProvider, HASH_SIZE}; +use babylon_apis::{btc_staking_api, 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 From for BtcDelegation { + fn from(active_delegation: btc_staking_api::ActiveBtcDelegation) -> Self { + BtcDelegation { + 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: 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<&btc_staking_api::ActiveBtcDelegation> for BtcDelegation { + fn from(active_delegation: &btc_staking_api::ActiveBtcDelegation) -> Self { + BtcDelegation::from(active_delegation.clone()) + } +} + +#[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(cov_adaptor_sigs: btc_staking_api::CovenantAdaptorSignatures) -> Self { + CovenantAdaptorSignatures { + cov_pk: cov_adaptor_sigs.cov_pk.to_vec(), + adaptor_sigs: cov_adaptor_sigs + .adaptor_sigs + .into_iter() + .map(|sig| sig.to_vec()) + .collect(), + } + } +} + +#[cw_serde] +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. + 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 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 + .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 + .covenant_slashing_sigs + .into_iter() + .map(|sig| sig.into()) + .collect(), + } + } +} + +#[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"); /// 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..95370416 100644 --- a/packages/apis/src/btc_staking_api.rs +++ b/packages/apis/src/btc_staking_api.rs @@ -230,44 +230,11 @@ 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, } -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]