diff --git a/Cargo.lock b/Cargo.lock index 69efd942..4426d4bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "hex", + "sha2", "tendermint-proto", "thiserror", ] @@ -317,6 +318,7 @@ dependencies = [ "ics23", "pbjson-types", "prost 0.11.9", + "sha2", "tendermint-proto", "test-utils", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 3c66e6b9..4ae864b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ babylon-btcstaking = { path = "./packages/btcstaking" } babylon-contract = { path = "./contracts/babylon" } eots = { path = "./packages/eots" } anyhow = "1.0.82" +bech32 = "0.9.1" bitcoin = "0.31.1" bitvec = "1" -bech32 = "0.9.1" blst = "0.3.11" cosmos-sdk-proto = { version = "0.19.0", default-features = false, features = [ "cosmwasm", diff --git a/contracts/babylon/Cargo.toml b/contracts/babylon/Cargo.toml index e911f8d5..b829268f 100644 --- a/contracts/babylon/Cargo.toml +++ b/contracts/babylon/Cargo.toml @@ -48,6 +48,7 @@ cosmos-sdk-proto = { workspace = true } thiserror = { workspace = true } prost = { workspace = true } ics23 = { workspace = true } +sha2 = { workspace = true } [dev-dependencies] babylon-bindings-test = { path = "../../packages/bindings-test" } diff --git a/contracts/babylon/benches/main.rs b/contracts/babylon/benches/main.rs index 644b2f36..24987605 100644 --- a/contracts/babylon/benches/main.rs +++ b/contracts/babylon/benches/main.rs @@ -54,6 +54,7 @@ pub fn setup_instance() -> Instance { btc_finality_code_id: None, btc_finality_msg: None, admin: None, + transfer_info: None, }; let info = mock_info(CREATOR, &[]); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); diff --git a/contracts/babylon/schema/babylon-contract.json b/contracts/babylon/schema/babylon-contract.json index 21cf91de..d38ab3d2 100644 --- a/contracts/babylon/schema/babylon-contract.json +++ b/contracts/babylon/schema/babylon-contract.json @@ -95,6 +95,17 @@ "notify_cosmos_zone": { "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: If set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" + }, + "transfer_info": { + "description": "IBC information for ICS-020 rewards transfer. If not set, distributed rewards will be native to the Consumer", + "anyOf": [ + { + "$ref": "#/definitions/IbcTransferInfo" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -103,6 +114,22 @@ "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" }, + "IbcTransferInfo": { + "type": "object", + "required": [ + "channel_id", + "recipient" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "recipient": { + "$ref": "#/definitions/Recipient" + } + }, + "additionalProperties": false + }, "Network": { "type": "string", "enum": [ @@ -111,6 +138,34 @@ "signet", "regtest" ] + }, + "Recipient": { + "oneOf": [ + { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "module_addr" + ], + "properties": { + "module_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + ] } } }, @@ -164,6 +219,32 @@ } }, "additionalProperties": false + }, + { + "description": "`SendRewards` is a message sent by the finality contract, to send rewards to Babylon", + "type": "object", + "required": [ + "send_rewards" + ], + "properties": { + "send_rewards": { + "type": "object", + "required": [ + "fp_distribution" + ], + "properties": { + "fp_distribution": { + "description": "`fp_distribution` is the list of finality providers and their rewards", + "type": "array", + "items": { + "$ref": "#/definitions/RewardsDistribution" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -287,6 +368,26 @@ } }, "additionalProperties": false + }, + "RewardsDistribution": { + "type": "object", + "required": [ + "fp_pubkey_hex", + "reward" + ], + "properties": { + "fp_pubkey_hex": { + "type": "string" + }, + "reward": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } }, @@ -533,6 +634,20 @@ } }, "additionalProperties": false + }, + { + "description": "TransferInfo returns the IBC transfer information stored in the contract for ICS-020 rewards transfer. If not set, distributed rewards are native to the Consumer", + "type": "object", + "required": [ + "transfer_info" + ], + "properties": { + "transfer_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -1286,6 +1401,7 @@ "babylon_tag", "btc_confirmation_depth", "checkpoint_finalization_timeout", + "denom", "network", "notify_cosmos_zone" ], @@ -1344,6 +1460,9 @@ "null" ] }, + "denom": { + "type": "string" + }, "network": { "$ref": "#/definitions/Network" }, @@ -1522,6 +1641,41 @@ "type": "string" } } + }, + "transfer_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_TransferInfo", + "anyOf": [ + { + "$ref": "#/definitions/TransferInfo" + }, + { + "type": "null" + } + ], + "definitions": { + "TransferInfo": { + "description": "IBC transfer (ICS-020) channel settings", + "type": "object", + "required": [ + "address_type", + "channel_id", + "to_address" + ], + "properties": { + "address_type": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + } } } } diff --git a/contracts/babylon/schema/raw/execute.json b/contracts/babylon/schema/raw/execute.json index ebc3604f..2d842f5a 100644 --- a/contracts/babylon/schema/raw/execute.json +++ b/contracts/babylon/schema/raw/execute.json @@ -48,6 +48,32 @@ } }, "additionalProperties": false + }, + { + "description": "`SendRewards` is a message sent by the finality contract, to send rewards to Babylon", + "type": "object", + "required": [ + "send_rewards" + ], + "properties": { + "send_rewards": { + "type": "object", + "required": [ + "fp_distribution" + ], + "properties": { + "fp_distribution": { + "description": "`fp_distribution` is the list of finality providers and their rewards", + "type": "array", + "items": { + "$ref": "#/definitions/RewardsDistribution" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -171,6 +197,26 @@ } }, "additionalProperties": false + }, + "RewardsDistribution": { + "type": "object", + "required": [ + "fp_pubkey_hex", + "reward" + ], + "properties": { + "fp_pubkey_hex": { + "type": "string" + }, + "reward": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" } } } diff --git a/contracts/babylon/schema/raw/instantiate.json b/contracts/babylon/schema/raw/instantiate.json index 16a23a79..01cc5cdc 100644 --- a/contracts/babylon/schema/raw/instantiate.json +++ b/contracts/babylon/schema/raw/instantiate.json @@ -91,6 +91,17 @@ "notify_cosmos_zone": { "description": "notify_cosmos_zone indicates whether to send Cosmos zone messages notifying BTC-finalised headers. NOTE: If set to true, then the Cosmos zone needs to integrate the corresponding message handler as well", "type": "boolean" + }, + "transfer_info": { + "description": "IBC information for ICS-020 rewards transfer. If not set, distributed rewards will be native to the Consumer", + "anyOf": [ + { + "$ref": "#/definitions/IbcTransferInfo" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -99,6 +110,22 @@ "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" }, + "IbcTransferInfo": { + "type": "object", + "required": [ + "channel_id", + "recipient" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "recipient": { + "$ref": "#/definitions/Recipient" + } + }, + "additionalProperties": false + }, "Network": { "type": "string", "enum": [ @@ -107,6 +134,34 @@ "signet", "regtest" ] + }, + "Recipient": { + "oneOf": [ + { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "module_addr" + ], + "properties": { + "module_addr": { + "type": "string" + } + }, + "additionalProperties": false + } + ] } } } diff --git a/contracts/babylon/schema/raw/query.json b/contracts/babylon/schema/raw/query.json index e4991b84..d58afe03 100644 --- a/contracts/babylon/schema/raw/query.json +++ b/contracts/babylon/schema/raw/query.json @@ -241,6 +241,20 @@ } }, "additionalProperties": false + }, + { + "description": "TransferInfo returns the IBC transfer information stored in the contract for ICS-020 rewards transfer. If not set, distributed rewards are native to the Consumer", + "type": "object", + "required": [ + "transfer_info" + ], + "properties": { + "transfer_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/contracts/babylon/schema/raw/response_to_config.json b/contracts/babylon/schema/raw/response_to_config.json index 60409127..4ed64023 100644 --- a/contracts/babylon/schema/raw/response_to_config.json +++ b/contracts/babylon/schema/raw/response_to_config.json @@ -6,6 +6,7 @@ "babylon_tag", "btc_confirmation_depth", "checkpoint_finalization_timeout", + "denom", "network", "notify_cosmos_zone" ], @@ -64,6 +65,9 @@ "null" ] }, + "denom": { + "type": "string" + }, "network": { "$ref": "#/definitions/Network" }, diff --git a/contracts/babylon/schema/raw/response_to_transfer_info.json b/contracts/babylon/schema/raw/response_to_transfer_info.json new file mode 100644 index 00000000..99ce240c --- /dev/null +++ b/contracts/babylon/schema/raw/response_to_transfer_info.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_TransferInfo", + "anyOf": [ + { + "$ref": "#/definitions/TransferInfo" + }, + { + "type": "null" + } + ], + "definitions": { + "TransferInfo": { + "description": "IBC transfer (ICS-020) channel settings", + "type": "object", + "required": [ + "address_type", + "channel_id", + "to_address" + ], + "properties": { + "address_type": { + "type": "string" + }, + "channel_id": { + "type": "string" + }, + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 38b82683..89e56d58 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,15 +1,15 @@ use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Reply, - Response, SubMsg, SubMsgResponse, WasmMsg, + to_json_binary, to_json_string, Addr, Binary, Deps, DepsMut, Empty, Env, IbcMsg, MessageInfo, + QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, }; use cw2::set_contract_version; -use cw_utils::ParseReplyError; +use cw_utils::{must_pay, ParseReplyError}; -use babylon_apis::{btc_staking_api, finality_api}; +use babylon_apis::{btc_staking_api, finality_api, to_bech32_addr, to_module_canonical_addr}; use babylon_bindings::BabylonMsg; use crate::error::ContractError; -use crate::ibc::{ibc_packet, IBC_CHANNEL}; +use crate::ibc::{ibc_packet, packet_timeout, TransferInfo, IBC_CHANNEL, IBC_TRANSFER}; use crate::msg::contract::{ContractMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; use crate::state::btc_light_client; @@ -34,6 +34,7 @@ pub fn instantiate( msg.validate()?; // Initialize config with None values for consumer fields + let denom = deps.querier.query_bonded_denom()?; let mut cfg = Config { network: msg.network.clone(), babylon_tag: msg.babylon_tag_to_bytes()?, @@ -44,6 +45,7 @@ pub fn instantiate( btc_finality: None, // Will be set in `reply` if `btc_finality_code_id` is provided consumer_name: None, consumer_description: None, + denom, }; let mut res = Response::new().add_attribute("action", "instantiate"); @@ -94,6 +96,25 @@ pub fn instantiate( // Save the config after potentially updating it CONFIG.save(deps.storage, &cfg)?; + // Format and save the IBC transfer info + if let Some(transfer_info) = msg.transfer_info { + let (to_address, address_type) = match transfer_info.recipient { + crate::msg::ibc::Recipient::ContractAddr(addr) => (addr, "contract"), + crate::msg::ibc::Recipient::ModuleAddr(module) => ( + to_bech32_addr("bbn", &to_module_canonical_addr(&module))?.to_string(), + "module", + ), + }; + IBC_TRANSFER.save( + deps.storage, + &TransferInfo { + channel_id: transfer_info.channel_id, + to_address, + address_type: address_type.to_string(), + }, + )?; + } + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(res) } @@ -197,6 +218,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&queries::cz_last_header(deps)?)?), QueryMsg::CzHeader { height } => Ok(to_json_binary(&queries::cz_header(deps, height)?)?), + QueryMsg::TransferInfo {} => Ok(to_json_binary(&queries::transfer_info(deps)?)?), } } @@ -269,6 +291,51 @@ pub fn execute( // TODO: Add events Ok(res) } + ExecuteMsg::SendRewards { fp_distribution } => { + let cfg = CONFIG.load(deps.storage)?; + // Assert the funds are there + must_pay(&info, &cfg.denom)?; + // Assert the sender is right + let btc_finality = cfg + .btc_finality + .ok_or(ContractError::BtcFinalityNotSet {})?; + if info.sender != btc_finality { + return Err(ContractError::Unauthorized {}); + } + // Route to babylon over IBC, if available + let transfer_info = IBC_TRANSFER.may_load(deps.storage)?; + match transfer_info { + Some(transfer_info) => { + // Build the payload + let payload_msg = to_json_string(&fp_distribution)?; + // Construct the transfer message + let ibc_msg = IbcMsg::Transfer { + channel_id: transfer_info.channel_id, + to_address: transfer_info.to_address, + amount: info.funds[0].clone(), + timeout: packet_timeout(&env), + memo: Some(payload_msg), + }; + + // Send packet only if we are IBC enabled + // TODO: send in test code when multi-test can handle it + #[cfg(not(any(test, feature = "library")))] + { + // TODO: Add events + Ok(Response::new().add_message(ibc_msg)) + } + #[cfg(any(test, feature = "library"))] + { + let _ = ibc_msg; + Ok(Response::new()) + } + } + None => { + // TODO: Send payload over the custom IBC channel for distribution + Ok(Response::new()) + } + } + } } } @@ -305,9 +372,94 @@ mod tests { admin: None, consumer_name: None, consumer_description: None, + transfer_info: None, }; let info = message_info(&deps.api.addr_make(CREATOR), &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(0, res.messages.len()); } + + #[test] + fn instantiate_finality_works() { + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + network: babylon_bitcoin::chain_params::Network::Regtest, + babylon_tag: "01020304".to_string(), + btc_confirmation_depth: 10, + checkpoint_finalization_timeout: 100, + notify_cosmos_zone: false, + btc_staking_code_id: None, + btc_staking_msg: None, + btc_finality_code_id: Some(2), + btc_finality_msg: None, + admin: None, + consumer_name: None, + consumer_description: None, + transfer_info: None, + }; + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(1, res.messages.len()); + assert_eq!(REPLY_ID_INSTANTIATE_FINALITY, res.messages[0].id); + assert_eq!( + res.messages[0].msg, + WasmMsg::Instantiate { + admin: None, + code_id: 2, + msg: Binary::from(b"{}"), + funds: vec![], + label: "BTC Finality".into(), + } + .into() + ); + } + + #[test] + fn instantiate_finality_params_works() { + let mut deps = mock_dependencies(); + let params = r#"{"params": {"epoch_length": 10}}"#; + let msg = InstantiateMsg { + network: babylon_bitcoin::chain_params::Network::Regtest, + babylon_tag: "01020304".to_string(), + btc_confirmation_depth: 10, + checkpoint_finalization_timeout: 100, + notify_cosmos_zone: false, + btc_staking_code_id: None, + btc_staking_msg: None, + btc_finality_code_id: Some(2), + btc_finality_msg: Some(Binary::from(params.as_bytes())), + admin: None, + consumer_name: None, + consumer_description: None, + transfer_info: None, + }; + let info = message_info(&deps.api.addr_make(CREATOR), &[]); + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(1, res.messages.len()); + assert_eq!(REPLY_ID_INSTANTIATE_FINALITY, res.messages[0].id); + assert_eq!( + res.messages[0].msg, + WasmMsg::Instantiate { + admin: None, + code_id: 2, + msg: Binary::from(params.as_bytes()), + funds: vec![], + label: "BTC Finality".into(), + } + .into() + ); + } + + #[test] + fn test_module_address() { + // Example usage + let prefix = "bbn"; + let module_name = "zoneconcierge"; + + let addr = to_bech32_addr(prefix, &to_module_canonical_addr(module_name)).unwrap(); + assert_eq!( + addr.to_string(), + "bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3" + ); + } } diff --git a/contracts/babylon/src/error.rs b/contracts/babylon/src/error.rs index 1429b16f..369e3832 100644 --- a/contracts/babylon/src/error.rs +++ b/contracts/babylon/src/error.rs @@ -1,6 +1,7 @@ +use babylon_apis::error::StakingApiError; use babylon_bitcoin::Work; use cosmwasm_std::StdError; -use cw_utils::ParseReplyError; +use cw_utils::{ParseReplyError, PaymentError}; use hex::FromHexError; use prost::DecodeError; use std::str::Utf8Error; @@ -20,6 +21,10 @@ pub enum ContractError { BabylonEpochError(#[from] BabylonEpochChainError), #[error("{0}")] CzHeaderError(#[from] CZHeaderChainError), + #[error("{0}")] + Payment(#[from] PaymentError), + #[error("API error: {0}")] + ApiError(#[from] StakingApiError), #[error("Contract already has an open IBC channel")] IbcChannelAlreadyOpen {}, #[error("The contract only supports ordered channels")] diff --git a/contracts/babylon/src/ibc.rs b/contracts/babylon/src/ibc.rs index 3a780feb..5a4f184d 100644 --- a/contracts/babylon/src/ibc.rs +++ b/contracts/babylon/src/ibc.rs @@ -3,6 +3,7 @@ use babylon_bindings::BabylonMsg; use babylon_proto::babylon::zoneconcierge::v1::{ zoneconcierge_packet_data::Packet, BtcTimestamp, ZoneconciergePacketData, }; +use cosmwasm_schema::cw_serde; use crate::state::config::CONFIG; use cosmwasm_std::{ @@ -14,12 +15,20 @@ use cosmwasm_std::{ use cw_storage_plus::Item; use prost::Message; +/// IBC custom channel settings pub const IBC_VERSION: &str = "zoneconcierge-1"; pub const IBC_ORDERING: IbcOrder = IbcOrder::Ordered; - -// IBC specific state pub const IBC_CHANNEL: Item = Item::new("ibc_channel"); +/// IBC transfer (ICS-020) channel settings +#[cw_serde] +pub struct TransferInfo { + pub channel_id: String, + pub to_address: String, + pub address_type: String, +} +pub const IBC_TRANSFER: Item = Item::new("ibc_transfer"); + /// This is executed during the ChannelOpenInit and ChannelOpenTry /// of the IBC 4-step channel protocol /// (see https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics#channel-lifecycle-management) @@ -141,6 +150,9 @@ pub fn ibc_packet_receive( Packet::ConsumerSlashing(_) => Err(StdError::generic_err( "ConsumerSlashing packet should not be received", )), + Packet::ConsumerFpDistribution(_) => Err(StdError::generic_err( + "ConsumerFpDistribution packet should not be received", + )), } })() .or_else(|e| { @@ -315,6 +327,7 @@ mod tests { use super::*; use crate::contract::instantiate; use crate::msg::contract::InstantiateMsg; + use crate::msg::ibc::{IbcTransferInfo, Recipient}; use cosmwasm_std::testing::message_info; use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_ibc_channel_open_try, MockApi, MockQuerier, MockStorage, @@ -338,6 +351,10 @@ mod tests { admin: None, consumer_name: None, consumer_description: None, + transfer_info: Some(IbcTransferInfo { + channel_id: "channel-1".to_string(), + recipient: Recipient::ModuleAddr("zoneconcierge".to_string()), + }), }; let info = message_info(&deps.api.addr_make(CREATOR), &[]); let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); diff --git a/contracts/babylon/src/msg/contract.rs b/contracts/babylon/src/msg/contract.rs index b094f0c8..d24b193a 100644 --- a/contracts/babylon/src/msg/contract.rs +++ b/contracts/babylon/src/msg/contract.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Uint128; use cosmwasm_std::{Binary, StdError, StdResult}; use babylon_apis::finality_api::Evidence; @@ -52,6 +53,9 @@ pub struct InstantiateMsg { pub consumer_name: Option, /// Description of the consumer pub consumer_description: Option, + /// IBC information for ICS-020 rewards transfer. + /// If not set, distributed rewards will be native to the Consumer + pub transfer_info: Option, } impl ContractMsg for InstantiateMsg { @@ -83,6 +87,10 @@ impl ContractMsg for InstantiateMsg { } } + if let Some(transfer_info) = &self.transfer_info { + transfer_info.validate()?; + } + Ok(()) } @@ -108,6 +116,17 @@ pub enum ExecuteMsg { /// This will be forwarded over IBC to the Babylon side for propagation to other Consumers, and /// Babylon itself Slashing { evidence: Evidence }, + /// `SendRewards` is a message sent by the finality contract, to send rewards to Babylon + SendRewards { + /// `fp_distribution` is the list of finality providers and their rewards + fp_distribution: Vec, + }, +} + +#[cw_serde] +pub struct RewardsDistribution { + pub fp_pubkey_hex: String, + pub reward: Uint128, } #[cw_serde] @@ -157,4 +176,9 @@ pub enum QueryMsg { /// CzHeader returns the CZ header stored in the contract, by CZ height. #[returns(CzHeaderResponse)] CzHeader { height: u64 }, + /// TransferInfo returns the IBC transfer information stored in the contract + /// for ICS-020 rewards transfer. + /// If not set, distributed rewards are native to the Consumer + #[returns(crate::msg::ibc::TransferInfoResponse)] + TransferInfo {}, } diff --git a/contracts/babylon/src/msg/ibc.rs b/contracts/babylon/src/msg/ibc.rs index f8394e5e..c830183d 100644 --- a/contracts/babylon/src/msg/ibc.rs +++ b/contracts/babylon/src/msg/ibc.rs @@ -1,5 +1,43 @@ +use crate::ibc::TransferInfo; +use babylon_apis::to_canonical_addr; use cosmos_sdk_proto::ibc::core::channel::v1::{acknowledgement::Response, Acknowledgement}; use cosmwasm_schema::cw_serde; +use cosmwasm_std::{StdError, StdResult}; + +#[cw_serde] +pub struct IbcTransferInfo { + pub channel_id: String, + pub recipient: Recipient, +} + +#[cw_serde] +pub enum Recipient { + ContractAddr(String), + ModuleAddr(String), +} + +impl IbcTransferInfo { + pub fn validate(&self) -> StdResult<()> { + if self.channel_id.is_empty() { + return Err(StdError::generic_err("Empty IBC channel id")); + } + match self.recipient { + Recipient::ContractAddr(ref addr) => { + to_canonical_addr(addr, "bbn").map_err(|e| { + StdError::generic_err(format!("Invalid contract address: {}", e)) + })?; + } + Recipient::ModuleAddr(ref addr) => { + if addr.is_empty() { + return Err(StdError::generic_err("Empty module address")); + } + } + } + Ok(()) + } +} + +pub type TransferInfoResponse = Option; pub fn new_ack_res() -> Acknowledgement { let resp = Response::Result(vec![]); diff --git a/contracts/babylon/src/multitest.rs b/contracts/babylon/src/multitest.rs index 44fc23ee..71b236ed 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -37,6 +37,9 @@ fn initialization() { mod instantiation { use super::*; + use crate::msg::ibc::Recipient; + use babylon_apis::{to_bech32_addr, to_module_canonical_addr}; + use cosmwasm_std::to_json_string; #[test] fn instantiate_works() { @@ -48,6 +51,96 @@ mod instantiation { // Confirm the btc-finality contract has been instantiated and set assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); } + + #[test] + fn instantiate_staking_msg_works() { + // Params setting is an all-or-nothing operation, i.e. all the params have to be set + let params = btc_staking::state::config::Params { + covenant_pks: vec![], + covenant_quorum: 1, + btc_network: babylon_bitcoin::chain_params::Network::Regtest, + slashing_pk_script: String::from("76a914010101010101010101010101010101010101010188ab"), + min_slashing_tx_fee_sat: 10000, + slashing_rate: String::from("0.1"), + }; + let staking_instantiation_msg = btc_staking::msg::InstantiateMsg { + params: Some(params), + admin: None, + }; + let suite = SuiteBuilder::new() + .with_staking_msg(&to_json_string(&staking_instantiation_msg).unwrap()) + .build(); + + // Confirm the btc-staking contract has been instantiated and set + let config = suite.get_config(); + assert_eq!(config.btc_staking, Some(Addr::unchecked(CONTRACT1_ADDR))); + // Confirm the btc-finality contract has been instantiated and set + assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); + } + + #[test] + fn instantiate_finality_msg_works() { + // Params setting is an all-or-nothing operation, i.e. all the params have to be set + let params = btc_finality::state::config::Params { + epoch_length: 10, + max_active_finality_providers: 5, + min_pub_rand: 2, + finality_inflation_rate: "0.035".parse().unwrap(), + }; + let finality_instantiation_msg = btc_finality::msg::InstantiateMsg { + params: Some(params), + admin: None, + }; + let suite = SuiteBuilder::new() + .with_finality_msg(&to_json_string(&finality_instantiation_msg).unwrap()) + .build(); + + // Confirm the btc-staking contract has been instantiated and set + let config = suite.get_config(); + assert_eq!(config.btc_staking, Some(Addr::unchecked(CONTRACT1_ADDR))); + // Confirm the btc-finality contract has been instantiated and set + assert_eq!(config.btc_finality, Some(Addr::unchecked(CONTRACT2_ADDR))); + } + + #[test] + fn instantiate_ibc_transfer_module_addr_works() { + let suite = SuiteBuilder::new() + .with_ibc_transfer_info( + "channel-10", + Recipient::ModuleAddr("module-addr".to_string()), + ) + .build(); + + // Confirm the transfer info has been set + let transfer_info = suite.get_transfer_info().unwrap(); + assert_eq!(transfer_info.channel_id, "channel-10"); + assert_eq!( + transfer_info.to_address, + to_bech32_addr("bbn", &to_module_canonical_addr("module-addr")) + .unwrap() + .to_string() + ); + assert_eq!(transfer_info.address_type, "module"); + } + + #[test] + fn instantiate_ibc_transfer_contract_addr_works() { + let suite = SuiteBuilder::new() + .with_ibc_transfer_info( + "channel-10", + Recipient::ContractAddr("bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3".to_string()), + ) + .build(); + + // Confirm the transfer info has been set + let transfer_info = suite.get_transfer_info().unwrap(); + assert_eq!(transfer_info.channel_id, "channel-10"); + assert_eq!( + transfer_info.to_address, + "bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3".to_string() + ); + assert_eq!(transfer_info.address_type, "contract"); + } } mod migration { diff --git a/contracts/babylon/src/multitest/suite.rs b/contracts/babylon/src/multitest/suite.rs index 0c9b9929..44960a76 100644 --- a/contracts/babylon/src/multitest/suite.rs +++ b/contracts/babylon/src/multitest/suite.rs @@ -1,13 +1,18 @@ -use crate::msg::contract::{InstantiateMsg, QueryMsg}; -use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; -use crate::state::config::Config; +use crate::msg::ibc::TransferInfoResponse; +use crate::msg::ibc::{IbcTransferInfo, Recipient}; use anyhow::Result as AnyResult; +use derivative::Derivative; + +use cosmwasm_std::{Addr, Binary, Empty}; +use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; + use babylon_bindings::BabylonMsg; use babylon_bindings_test::BabylonApp; use babylon_bitcoin::chain_params::Network; -use cosmwasm_std::{Addr, Empty}; -use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; -use derivative::Derivative; + +use crate::msg::contract::{InstantiateMsg, QueryMsg}; +use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; +use crate::state::config::Config; fn contract_btc_staking() -> Box> { let contract = ContractWrapper::new( @@ -38,6 +43,9 @@ fn contract_babylon() -> Box> { #[derivative(Default = "new")] pub struct SuiteBuilder { funds: Vec<(Addr, u128)>, + staking_msg: Option, + finality_msg: Option, + transfer_info: Option, } impl SuiteBuilder { @@ -48,6 +56,29 @@ impl SuiteBuilder { self } + /// Sets the staking contract instantiation message + pub fn with_staking_msg(mut self, msg: &str) -> Self { + self.staking_msg = Some(msg.into()); + self + } + + /// Sets the finality contract instantiation message + pub fn with_finality_msg(mut self, msg: &str) -> Self { + self.finality_msg = Some(msg.into()); + self + } + + /// Sets the IBC transfer info + #[allow(dead_code)] + pub fn with_ibc_transfer_info(mut self, channel_id: &str, recipient: Recipient) -> Self { + let transfer_info = IbcTransferInfo { + channel_id: channel_id.into(), + recipient, + }; + self.transfer_info = Some(transfer_info); + self + } + #[track_caller] pub fn build(self) -> Suite { let _funds = self.funds; @@ -66,6 +97,9 @@ impl SuiteBuilder { let btc_finality_code_id = app.store_code_with_creator(owner.clone(), contract_btc_finality()); let contract_code_id = app.store_code_with_creator(owner.clone(), contract_babylon()); + + let staking_msg = self.staking_msg.map(|msg| Binary::from(msg.as_bytes())); + let finality_msg = self.finality_msg.map(|msg| Binary::from(msg.as_bytes())); let contract = app .instantiate_contract( contract_code_id, @@ -77,12 +111,13 @@ impl SuiteBuilder { checkpoint_finalization_timeout: 10, notify_cosmos_zone: false, btc_staking_code_id: Some(btc_staking_code_id), - btc_staking_msg: None, + btc_staking_msg: staking_msg, btc_finality_code_id: Some(btc_finality_code_id), - btc_finality_msg: None, + btc_finality_msg: finality_msg, admin: Some(owner.to_string()), consumer_name: Some("TestConsumer".to_string()), consumer_description: Some("Test Consumer Description".to_string()), + transfer_info: self.transfer_info, }, &[], "babylon", @@ -141,6 +176,14 @@ impl Suite { .unwrap() } + #[track_caller] + pub fn get_transfer_info(&self) -> TransferInfoResponse { + self.app + .wrap() + .query_wasm_smart(self.contract.clone(), &QueryMsg::TransferInfo {}) + .unwrap() + } + pub fn migrate(&mut self, addr: &str, msg: Empty) -> AnyResult { self.app.migrate_contract( Addr::unchecked(addr), diff --git a/contracts/babylon/src/queries/mod.rs b/contracts/babylon/src/queries/mod.rs index c28791fa..5181bcf6 100644 --- a/contracts/babylon/src/queries/mod.rs +++ b/contracts/babylon/src/queries/mod.rs @@ -1,7 +1,11 @@ -use crate::error::{BTCLightclientError, BabylonEpochChainError, CZHeaderChainError}; +use crate::error::{ + BTCLightclientError, BabylonEpochChainError, CZHeaderChainError, ContractError, +}; +use crate::ibc::IBC_TRANSFER; use crate::msg::btc_header::{BtcHeaderResponse, BtcHeadersResponse}; use crate::msg::cz_header::CzHeaderResponse; use crate::msg::epoch::{CheckpointResponse, EpochResponse}; +use crate::msg::ibc::TransferInfoResponse; use crate::state::babylon_epoch_chain::{ get_base_epoch, get_checkpoint, get_epoch, get_last_finalized_epoch, }; @@ -94,6 +98,11 @@ pub(crate) fn cz_header(deps: Deps, height: u64) -> Result Result { + let transfer_info = IBC_TRANSFER.may_load(deps.storage)?; + Ok(transfer_info) +} + #[cfg(test)] mod tests { use super::*; diff --git a/contracts/babylon/src/state/btc_light_client.rs b/contracts/babylon/src/state/btc_light_client.rs index 98e0b661..82e94040 100644 --- a/contracts/babylon/src/state/btc_light_client.rs +++ b/contracts/babylon/src/state/btc_light_client.rs @@ -360,6 +360,7 @@ pub(crate) mod tests { btc_finality: None, consumer_name: None, consumer_description: None, + denom: "ustake".to_string(), }; CONFIG.save(storage, &cfg).unwrap(); w @@ -371,6 +372,7 @@ pub(crate) mod tests { match resp { ExecuteMsg::BtcHeaders { headers } => headers, ExecuteMsg::Slashing { .. } => unreachable!("unexpected slashing message"), + ExecuteMsg::SendRewards { .. } => unreachable!("unexpected send rewards message"), } } diff --git a/contracts/babylon/src/state/config.rs b/contracts/babylon/src/state/config.rs index b1e25121..058aaba4 100644 --- a/contracts/babylon/src/state/config.rs +++ b/contracts/babylon/src/state/config.rs @@ -23,4 +23,5 @@ pub struct Config { pub consumer_name: Option, /// Consumer description pub consumer_description: Option, + pub denom: String, } diff --git a/contracts/babylon/tests/integration.rs b/contracts/babylon/tests/integration.rs index dbe78dde..42e07d6c 100644 --- a/contracts/babylon/tests/integration.rs +++ b/contracts/babylon/tests/integration.rs @@ -32,7 +32,7 @@ use babylon_contract::msg::contract::{ExecuteMsg, InstantiateMsg}; static BABYLON_CONTRACT_WASM: &[u8] = include_bytes!("../../../artifacts/babylon_contract.wasm"); /// Wasm size limit: https://github.com/CosmWasm/wasmd/blob/main/x/wasm/types/validation.go#L24-L25 -const MAX_WASM_SIZE: usize = 800 * 1024; // 800 KB +const MAX_WASM_SIZE: usize = 1024 * 1024; // 1 MB const CREATOR: &str = "creator"; @@ -52,6 +52,7 @@ fn setup() -> Instance { btc_finality_code_id: None, btc_finality_msg: None, admin: None, + transfer_info: None, }; let info = message_info(&Addr::unchecked(CREATOR), &[]); let res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap(); @@ -76,6 +77,7 @@ fn get_fork_msg_test_headers() -> Vec { match resp { ExecuteMsg::BtcHeaders { headers } => headers, ExecuteMsg::Slashing { .. } => unreachable!("unexpected slashing message"), + ExecuteMsg::SendRewards { .. } => unreachable!("unexpected send rewards message"), } } @@ -106,6 +108,7 @@ fn instantiate_works() { btc_finality_code_id: None, btc_finality_msg: None, admin: None, + transfer_info: None, }; let info = message_info(&Addr::unchecked(CREATOR), &[]); let res: ContractResult = instantiate(&mut deps, mock_env(), info, msg); diff --git a/contracts/btc-finality/Cargo.toml b/contracts/btc-finality/Cargo.toml index 45e700ee..78e63945 100644 --- a/contracts/btc-finality/Cargo.toml +++ b/contracts/btc-finality/Cargo.toml @@ -32,7 +32,6 @@ full-validation = [ "btc-staking/full-validation" ] babylon-apis = { path = "../../packages/apis" } babylon-bindings = { path = "../../packages/bindings" } babylon-merkle = { path = "../../packages/merkle" } -babylon-proto = { path = "../../packages/proto" } babylon-btcstaking = { path = "../../packages/btcstaking" } babylon-bitcoin = { path = "../../packages/bitcoin" } eots = { path = "../../packages/eots" } diff --git a/contracts/btc-finality/schema/btc-finality.json b/contracts/btc-finality/schema/btc-finality.json index dbe6a2bf..ed41a56c 100644 --- a/contracts/btc-finality/schema/btc-finality.json +++ b/contracts/btc-finality/schema/btc-finality.json @@ -48,15 +48,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -1811,15 +1818,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-finality/schema/raw/instantiate.json b/contracts/btc-finality/schema/raw/instantiate.json index 546c5674..9cc57850 100644 --- a/contracts/btc-finality/schema/raw/instantiate.json +++ b/contracts/btc-finality/schema/raw/instantiate.json @@ -44,15 +44,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-finality/schema/raw/response_to_params.json b/contracts/btc-finality/schema/raw/response_to_params.json index cb5c715d..7819400c 100644 --- a/contracts/btc-finality/schema/raw/response_to_params.json +++ b/contracts/btc-finality/schema/raw/response_to_params.json @@ -13,15 +13,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 9e86f060..2e8e6677 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -1,16 +1,16 @@ use babylon_apis::finality_api::SudoMsg; use babylon_bindings::BabylonMsg; +use btc_staking::msg::ActivatedHeightResponse; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, to_json_binary, Addr, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, - QuerierWrapper, QueryRequest, QueryResponse, Reply, Response, StdResult, WasmQuery, + attr, coins, to_json_binary, Addr, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, Order, + QuerierWrapper, QueryRequest, QueryResponse, Reply, Response, StdResult, Uint128, WasmMsg, + WasmQuery, }; use cw2::set_contract_version; use cw_utils::{maybe_addr, nonpayable}; -use btc_staking::msg::ActivatedHeightResponse; - use crate::error::ContractError; use crate::finality::{ compute_active_finality_providers, distribute_rewards, handle_finality_signature, @@ -18,6 +18,7 @@ use crate::finality::{ }; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; +use crate::state::finality::{REWARDS, TOTAL_REWARDS}; use crate::{finality, queries, state}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -49,6 +50,7 @@ pub fn instantiate( let params = msg.params.unwrap_or_default(); PARAMS.save(deps.storage, ¶ms)?; // initialize storage, so no issue when reading for the first time + TOTAL_REWARDS.save(deps.storage, &Uint128::zero())?; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::new().add_attribute("action", "instantiate")) @@ -256,9 +258,60 @@ fn handle_end_block( } res = res.add_events(events); } + + // On an epoch boundary, send rewards to Babylon through the babylon contract + let params = PARAMS.load(deps.storage)?; + if env.block.height > 0 && env.block.height % params.epoch_length == 0 { + let rewards = TOTAL_REWARDS.load(deps.storage)?; + if rewards.u128() > 0 { + let wasm_msg = send_rewards_msg(deps, rewards.u128(), &cfg)?; + res = res.add_message(wasm_msg); + // Zero out total rewards + TOTAL_REWARDS.save(deps.storage, &Uint128::zero())?; + } + } Ok(res) } +fn send_rewards_msg( + deps: &mut DepsMut, + rewards: u128, + cfg: &Config, +) -> Result { + // Get the pending rewards distribution + let fp_rewards = REWARDS + .range(deps.storage, None, None, Order::Ascending) + .filter(|item| { + if let Ok((_, reward)) = item { + reward.u128() > 0 + } else { + true // don't filter errors + } + }) + .map(|item| { + let (fp_pubkey_hex, reward) = item?; + Ok(babylon_contract::msg::contract::RewardsDistribution { + fp_pubkey_hex, + reward, + }) + }) + .collect::>>()?; + let msg = babylon_contract::ExecuteMsg::SendRewards { + fp_distribution: fp_rewards.clone(), + }; + let wasm_msg = WasmMsg::Execute { + contract_addr: cfg.babylon.to_string(), + msg: to_json_binary(&msg)?, + funds: coins(rewards, cfg.denom.as_str()), + }; + // Zero out individual rewards + for reward in fp_rewards { + REWARDS.remove(deps.storage, &reward.fp_pubkey_hex); + } + + Ok(wasm_msg) +} + pub fn get_activated_height(staking_addr: &Addr, querier: &QuerierWrapper) -> StdResult { // TODO: Use a raw query let query = encode_smart_query( diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 2cf6ecf0..7214b52f 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -632,14 +632,20 @@ pub fn distribute_rewards(deps: &mut DepsMut, env: &Env) -> Result<(), ContractE }; // Get the voting power of the active FPS let total_voting_power = active_fps.iter().map(|fp| fp.power as u128).sum::(); - // Get the rewards to distribute (bank balance of the finality contract) + // Get the rewards to distribute (bank balance of the finality contract minus already distributed rewards) + let distributed_rewards = TOTAL_REWARDS.load(deps.storage)?; let cfg = CONFIG.load(deps.storage)?; let rewards_amount = deps .querier .query_balance(env.contract.address.clone(), cfg.denom)? - .amount; + .amount + .saturating_sub(distributed_rewards); + // Short-circuit if there are no rewards to distribute + if rewards_amount.is_zero() { + return Ok(()); + } // Compute the rewards for each active FP - let mut total_rewards = Uint128::zero(); + let mut accumulated_rewards = Uint128::zero(); for fp in active_fps { let reward = (Decimal::from_ratio(fp.power as u128, total_voting_power) * Decimal::from_ratio(rewards_amount, 1u128)) @@ -649,9 +655,11 @@ pub fn distribute_rewards(deps: &mut DepsMut, env: &Env) -> Result<(), ContractE Ok::(r.unwrap_or_default() + reward) })?; // Compute the total rewards - total_rewards += reward; + accumulated_rewards += reward; } // Update the total rewards - TOTAL_REWARDS.save(deps.storage, &total_rewards)?; + TOTAL_REWARDS.update(deps.storage, |r| { + Ok::(r + accumulated_rewards) + })?; Ok(()) } diff --git a/contracts/btc-finality/src/multitest/suite.rs b/contracts/btc-finality/src/multitest/suite.rs index ca0fcb30..b7da971a 100644 --- a/contracts/btc-finality/src/multitest/suite.rs +++ b/contracts/btc-finality/src/multitest/suite.rs @@ -108,6 +108,7 @@ impl SuiteBuilder { admin: Some(owner.to_string()), consumer_name: Some("TestConsumer".to_string()), consumer_description: Some("Test Consumer Description".to_string()), + transfer_info: None, }, &[], "babylon", diff --git a/contracts/btc-finality/src/state/config.rs b/contracts/btc-finality/src/state/config.rs index 60a2f342..eb6626ed 100644 --- a/contracts/btc-finality/src/state/config.rs +++ b/contracts/btc-finality/src/state/config.rs @@ -37,4 +37,7 @@ pub struct Params { /// `finality_inflation_rate` is the inflation rate for finality providers' block rewards #[derivative(Default(value = "Decimal::permille(35)"))] // 3.5 % by default pub finality_inflation_rate: Decimal, + /// `epoch_length` is the number of blocks that defines an epoch + #[derivative(Default(value = "50"))] // 50 * ~6.5s = ~5min + pub epoch_length: u64, } diff --git a/contracts/btc-staking/schema/btc-staking.json b/contracts/btc-staking/schema/btc-staking.json index 90cb40be..e8afecb0 100644 --- a/contracts/btc-staking/schema/btc-staking.json +++ b/contracts/btc-staking/schema/btc-staking.json @@ -48,15 +48,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -1811,15 +1818,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-staking/schema/raw/instantiate.json b/contracts/btc-staking/schema/raw/instantiate.json index 546c5674..9cc57850 100644 --- a/contracts/btc-staking/schema/raw/instantiate.json +++ b/contracts/btc-staking/schema/raw/instantiate.json @@ -44,15 +44,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-staking/schema/raw/response_to_params.json b/contracts/btc-staking/schema/raw/response_to_params.json index cb5c715d..7819400c 100644 --- a/contracts/btc-staking/schema/raw/response_to_params.json +++ b/contracts/btc-staking/schema/raw/response_to_params.json @@ -13,15 +13,22 @@ ], "properties": { "btc_network": { - "$ref": "#/definitions/Network" + "description": "`btc_network` is the network the BTC staking protocol is running on", + "allOf": [ + { + "$ref": "#/definitions/Network" + } + ] }, "covenant_pks": { + "description": "`covenant_pks` is the list of public keys held by the covenant committee each PK follows encoding in BIP-340 spec on Bitcoin", "type": "array", "items": { "type": "string" } }, "covenant_quorum": { + "description": "`covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature", "type": "integer", "format": "uint32", "minimum": 0.0 diff --git a/contracts/btc-staking/src/state/config.rs b/contracts/btc-staking/src/state/config.rs index 7215cde7..af2dfe5e 100644 --- a/contracts/btc-staking/src/state/config.rs +++ b/contracts/btc-staking/src/state/config.rs @@ -24,13 +24,13 @@ pub struct Config { #[derive(Derivative)] #[derivative(Default)] pub struct Params { - // covenant_pks is the list of public keys held by the covenant committee each PK - // follows encoding in BIP-340 spec on Bitcoin + /// `covenant_pks` is the list of public keys held by the covenant committee each PK + /// follows encoding in BIP-340 spec on Bitcoin pub covenant_pks: Vec, - // covenant_quorum is the minimum number of signatures needed for the covenant multi-signature + /// `covenant_quorum` is the minimum number of signatures needed for the covenant multi-signature pub covenant_quorum: u32, + /// `btc_network` is the network the BTC staking protocol is running on #[derivative(Default(value = "Network::Regtest"))] - // ntc_network is the network the BTC staking protocol is running on pub btc_network: Network, // `min_commission_rate` is the chain-wide minimum commission rate that a finality provider // can charge their delegators diff --git a/contracts/btc-staking/src/validation/mod.rs b/contracts/btc-staking/src/validation/mod.rs index 9633b54a..c7dc3805 100644 --- a/contracts/btc-staking/src/validation/mod.rs +++ b/contracts/btc-staking/src/validation/mod.rs @@ -96,16 +96,16 @@ pub fn verify_new_fp(new_fp: &NewFinalityProvider) -> Result<(), ContractError> #[cfg(feature = "full-validation")] { // get FP's PK - use babylon_apis::new_canonical_addr; + use babylon_apis::to_canonical_addr; let fp_pk_bytes = hex::decode(&new_fp.btc_pk_hex) .map_err(|e| ContractError::SecP256K1Error(e.to_string()))?; let fp_pk = VerifyingKey::from_bytes(&fp_pk_bytes) .map_err(|e| ContractError::SecP256K1Error(e.to_string()))?; - // get canonicalised FP address + // get canonical FP address // TODO: parameterise `bbn` prefix let addr = new_fp.addr.clone(); - let address = new_canonical_addr(&addr, "bbn")?; + let address = to_canonical_addr(&addr, "bbn")?; // get FP's PoP let pop = new_fp diff --git a/contracts/op-finality-gadget/schema/op-finality-gadget.json b/contracts/op-finality-gadget/schema/op-finality-gadget.json index 2987e0e2..37769bc6 100644 --- a/contracts/op-finality-gadget/schema/op-finality-gadget.json +++ b/contracts/op-finality-gadget/schema/op-finality-gadget.json @@ -126,6 +126,32 @@ }, "additionalProperties": false }, + { + "description": "Slashing message.\n\nThis message can be called by the admin only.", + "type": "object", + "required": [ + "slashing" + ], + "properties": { + "slashing": { + "type": "object", + "required": [ + "evidence", + "sender" + ], + "properties": { + "evidence": { + "$ref": "#/definitions/Evidence" + }, + "sender": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Enable or disable finality gadget.\n\nThis message can be called by the admin only. If disabled, the verifier should bypass the EOTS verification logic, allowing the OP derivation derivation pipeline to pass through. Note this should be implemented in the verifier and is not enforced by the contract itself.", "type": "object", @@ -171,10 +197,90 @@ } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "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" }, + "Evidence": { + "description": "Evidence is the evidence that a finality provider has signed finality signatures with correct public randomness on two conflicting Babylon headers", + "type": "object", + "required": [ + "block_height", + "canonical_app_hash", + "canonical_finality_sig", + "fork_app_hash", + "fork_finality_sig", + "fp_btc_pk", + "pub_rand" + ], + "properties": { + "block_height": { + "description": "`block_height` is the height of the conflicting blocks", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "canonical_app_hash": { + "description": "`canonical_app_hash` is the AppHash of the canonical block", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "canonical_finality_sig": { + "description": "`canonical_finality_sig` is the finality signature to the canonical block, where finality signature is an EOTS signature, i.e., the `s` in a Schnorr signature `(r, s)`. `r` is the public randomness already committed by the finality provider. Deserializes to `SchnorrEOTSSig`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fork_app_hash": { + "description": "`fork_app_hash` is the AppHash of the fork block", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fork_finality_sig": { + "description": "`fork_finality_sig` is the finality signature to the fork block, where finality signature is an EOTS signature. Deserializes to `SchnorrEOTSSig`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fp_btc_pk": { + "description": "`fp_btc_pk` is the BTC PK of the finality provider that casts this vote", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "pub_rand": { + "description": "`pub_rand is` the public randomness the finality provider has committed to. Deserializes to `SchnorrPubRand`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, "Proof": { "description": "A `Proof` is a proof of a leaf's existence in a Merkle tree.\n\nThe convention for proofs is to include leaf hashes, but to exclude the root hash. This convention is implemented across IAVL range proofs as well. Keep this consistent unless there's a very good reason to change everything. This affects the generalized proof system as well.\n\nEquivalent to / adapted from cometbft/crypto/merkle/proof.go.", "type": "object", diff --git a/contracts/op-finality-gadget/schema/raw/execute.json b/contracts/op-finality-gadget/schema/raw/execute.json index 3ce25633..b249b5c2 100644 --- a/contracts/op-finality-gadget/schema/raw/execute.json +++ b/contracts/op-finality-gadget/schema/raw/execute.json @@ -100,6 +100,32 @@ }, "additionalProperties": false }, + { + "description": "Slashing message.\n\nThis message can be called by the admin only.", + "type": "object", + "required": [ + "slashing" + ], + "properties": { + "slashing": { + "type": "object", + "required": [ + "evidence", + "sender" + ], + "properties": { + "evidence": { + "$ref": "#/definitions/Evidence" + }, + "sender": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Enable or disable finality gadget.\n\nThis message can be called by the admin only. If disabled, the verifier should bypass the EOTS verification logic, allowing the OP derivation derivation pipeline to pass through. Note this should be implemented in the verifier and is not enforced by the contract itself.", "type": "object", @@ -145,10 +171,90 @@ } ], "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "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" }, + "Evidence": { + "description": "Evidence is the evidence that a finality provider has signed finality signatures with correct public randomness on two conflicting Babylon headers", + "type": "object", + "required": [ + "block_height", + "canonical_app_hash", + "canonical_finality_sig", + "fork_app_hash", + "fork_finality_sig", + "fp_btc_pk", + "pub_rand" + ], + "properties": { + "block_height": { + "description": "`block_height` is the height of the conflicting blocks", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "canonical_app_hash": { + "description": "`canonical_app_hash` is the AppHash of the canonical block", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "canonical_finality_sig": { + "description": "`canonical_finality_sig` is the finality signature to the canonical block, where finality signature is an EOTS signature, i.e., the `s` in a Schnorr signature `(r, s)`. `r` is the public randomness already committed by the finality provider. Deserializes to `SchnorrEOTSSig`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fork_app_hash": { + "description": "`fork_app_hash` is the AppHash of the fork block", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fork_finality_sig": { + "description": "`fork_finality_sig` is the finality signature to the fork block, where finality signature is an EOTS signature. Deserializes to `SchnorrEOTSSig`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "fp_btc_pk": { + "description": "`fp_btc_pk` is the BTC PK of the finality provider that casts this vote", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + }, + "pub_rand": { + "description": "`pub_rand is` the public randomness the finality provider has committed to. Deserializes to `SchnorrPubRand`", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } + }, + "additionalProperties": false + }, "Proof": { "description": "A `Proof` is a proof of a leaf's existence in a Merkle tree.\n\nThe convention for proofs is to include leaf hashes, but to exclude the root hash. This convention is implemented across IAVL range proofs as well. Keep this consistent unless there's a very good reason to change everything. This affects the generalized proof system as well.\n\nEquivalent to / adapted from cometbft/crypto/merkle/proof.go.", "type": "object", diff --git a/contracts/op-finality-gadget/src/exec/finality.rs b/contracts/op-finality-gadget/src/exec/finality.rs index d0e5f07c..6131f41e 100644 --- a/contracts/op-finality-gadget/src/exec/finality.rs +++ b/contracts/op-finality-gadget/src/exec/finality.rs @@ -519,28 +519,16 @@ pub(crate) mod tests { let attrs: HashMap<_, _> = event .attributes .iter() - .map(|a| (&a.key, &a.value)) + .map(|a| (a.key.clone(), a.value.clone())) .collect(); + assert_eq!(attrs.get("module").unwrap(), "finality"); + assert_eq!(attrs.get("finality_provider").unwrap(), &pk_hex); assert_eq!( - attrs.get(&"module".to_string()).unwrap().as_str(), - "finality" - ); - assert_eq!( - attrs - .get(&"finality_provider".to_string()) - .unwrap() - .as_str(), - &pk_hex - ); - assert_eq!( - attrs.get(&"block_height".to_string()).unwrap().as_str(), + attrs.get("block_height").unwrap(), &block_height.to_string() ); assert_eq!( - attrs - .get(&"canonical_app_hash".to_string()) - .unwrap() - .as_str(), + attrs.get("canonical_app_hash").unwrap(), &hex::encode(&evidence.canonical_app_hash) ); } diff --git a/packages/apis/Cargo.toml b/packages/apis/Cargo.toml index fcb652be..b9f5540a 100644 --- a/packages/apis/Cargo.toml +++ b/packages/apis/Cargo.toml @@ -6,10 +6,11 @@ edition.workspace = true [dependencies] babylon-bitcoin = { path = "../bitcoin" } babylon-merkle = { path = "../merkle" } +babylon-proto = { workspace = true } bech32 = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } hex = { workspace = true } tendermint-proto = { workspace = true } thiserror = { workspace = true } -babylon-proto = { workspace = true } \ No newline at end of file +sha2 = { workspace = true } diff --git a/packages/apis/src/lib.rs b/packages/apis/src/lib.rs index ec20e328..171f72c5 100644 --- a/packages/apis/src/lib.rs +++ b/packages/apis/src/lib.rs @@ -1,10 +1,19 @@ +use bech32::Variant::Bech32; +use bech32::{FromBase32, ToBase32, Variant}; +use sha2::Digest; + +use cosmwasm_std::{Addr, Binary, CanonicalAddr, CustomQuery, QueryRequest, WasmQuery}; + +use error::StakingApiError; + pub mod btc_staking_api; pub mod error; pub mod finality_api; mod validate; -use bech32::{FromBase32, Variant}; -use cosmwasm_std::{Addr, Binary, CanonicalAddr, CustomQuery, QueryRequest, WasmQuery}; +pub type Bytes = Vec; + +pub use validate::Validate; pub fn encode_raw_query, Q: CustomQuery>(addr: &Addr, key: T) -> QueryRequest { WasmQuery::Raw { @@ -14,9 +23,9 @@ pub fn encode_raw_query, Q: CustomQuery>(addr: &Addr, key: T) -> .into() } -/// new_canonical_addr converts a bech32 address to a canonical address +/// to_canonical_addr converts a bech32 address to a canonical address /// ported from cosmwasm-std/testing/mock.rs -pub fn new_canonical_addr(addr: &str, prefix: &str) -> Result { +pub fn to_canonical_addr(addr: &str, prefix: &str) -> Result { // decode bech32 address let decode_result = bech32::decode(addr); if let Err(e) = decode_result { @@ -47,7 +56,23 @@ pub fn new_canonical_addr(addr: &str, prefix: &str) -> Result; +// Hash function to replace Cosmos SDK crypto.AddressHash and Hash. +fn hash(namespace: &str, input: &[u8]) -> Vec { + let mut hasher = sha2::Sha256::new(); + sha2::digest::Update::update(&mut hasher, namespace.as_bytes()); + sha2::digest::Update::update(&mut hasher, input); + hasher.finalize().to_vec() +} -use error::StakingApiError; -pub use validate::Validate; +/// Generates a Cosmos SDK compatible module address from a module name +pub fn to_module_canonical_addr(module_name: &str) -> CanonicalAddr { + CanonicalAddr::from(hash("", module_name.as_bytes())) +} + +/// Converts a CanonicalAddr to a Cosmos SDK compatible Bech32 encoded Addr with +/// the given prefix +pub fn to_bech32_addr(prefix: &str, addr: &CanonicalAddr) -> Result { + let bech32_addr = bech32::encode(prefix, &addr.as_slice().to_base32()[..32], Bech32) + .map_err(|e| StakingApiError::InvalidAddressString(e.to_string()))?; + Ok(Addr::unchecked(bech32_addr)) +} diff --git a/packages/proto/babylon b/packages/proto/babylon index 6682eeca..82a820c7 160000 --- a/packages/proto/babylon +++ b/packages/proto/babylon @@ -1 +1 @@ -Subproject commit 6682eecad96e65c65edb7c257325ec52a65b81fa +Subproject commit 82a820c70b5a522d732c4b2338a94bcdd656174e diff --git a/packages/proto/buf.gen.rust.yaml b/packages/proto/buf.gen.rust.yaml index 506a9004..28e34773 100644 --- a/packages/proto/buf.gen.rust.yaml +++ b/packages/proto/buf.gen.rust.yaml @@ -33,3 +33,4 @@ plugins: - extern_path=.google.protobuf=::pbjson_types - extern_path=.cosmos=cosmos_sdk_proto::cosmos - extern_path=.tendermint=tendermint_proto + - extern_path=.ibc=cosmos_sdk_proto::ibc diff --git a/packages/proto/buf.lock b/packages/proto/buf.lock index 7a2810e6..850ba5bf 100644 --- a/packages/proto/buf.lock +++ b/packages/proto/buf.lock @@ -9,13 +9,23 @@ deps: - remote: buf.build owner: cosmos repository: cosmos-sdk - commit: 954f7b05f38440fc8250134b15adec47 - digest: shake256:2ab4404fd04a7d1d52df0e2d0f2d477a3d83ffd88d876957bf3fedfd702c8e52833d65b3ce1d89a3c5adf2aab512616b0e4f51d8463f07eda9a8a3317ee3ac54 + commit: 5a6ab7bc14314acaa912d5e53aef1c2f + digest: shake256:02c00c73493720055f9b57553a35b5550023a3c1914123b247956288a78fb913aff70e66552777ae14d759467e119079d484af081264a5dd607a94d9fbc8116b - remote: buf.build owner: cosmos repository: gogo-proto commit: 34d970b699f84aa382f3c29773a60836 digest: shake256:3d3bee5229ba579e7d19ffe6e140986a228b48a8c7fe74348f308537ab95e9135210e81812489d42cd8941d33ff71f11583174ccc5972e86e6112924b6ce9f04 + - remote: buf.build + owner: cosmos + repository: ibc + commit: 6b221c7d310545198c1dafe70287d254 + digest: shake256:c5ea4d89af1c47f4d02057892eacdcb863359178079d9599f30d853b374fe9e9bfb3d9ca6720361c3439999a885a4f87fff4cd41c6c26b1f1142d60c386f8323 + - remote: buf.build + owner: cosmos + repository: ics23 + commit: 55085f7c710a45f58fa09947208eb70b + digest: shake256:9bf0bc495b5a11c88d163d39ef521bc4b00bc1374a05758c91d82821bdc61f09e8c2c51dda8452529bf80137f34d852561eacbe9550a59015d51cecb0dacb628 - remote: buf.build owner: googleapis repository: googleapis diff --git a/packages/proto/buf.yaml b/packages/proto/buf.yaml index bdb74d96..ffa3d064 100644 --- a/packages/proto/buf.yaml +++ b/packages/proto/buf.yaml @@ -1,10 +1,11 @@ version: v1 name: buf.build/babylonchain/babylon-proto deps: - - buf.build/cosmos/cosmos-sdk:v0.47.0 + - buf.build/cosmos/cosmos-sdk:v0.50.0 - buf.build/cosmos/cosmos-proto:1935555c206d4afb9e94615dfd0fad31 - buf.build/cosmos/gogo-proto:a14993478f40695898ed8a86931094b6656e8a5d - buf.build/googleapis/googleapis:8d7204855ec14631a499bd7393ce1970 + - buf.build/cosmos/ibc:6b221c7d310545198c1dafe70287d254 # Corresponds to ibc-go v8 breaking: use: - FILE diff --git a/packages/proto/src/gen/babylon.finality.v1.rs b/packages/proto/src/gen/babylon.finality.v1.rs index bdfd2418..57e8bf23 100644 --- a/packages/proto/src/gen/babylon.finality.v1.rs +++ b/packages/proto/src/gen/babylon.finality.v1.rs @@ -109,8 +109,32 @@ pub struct MsgAddFinalitySig { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MsgEquivocationEvidence { - /// evidence is the evidence of equivocation - #[prost(message, optional, tag="1")] - pub evidence: ::core::option::Option, + #[prost(string, tag="1")] + pub signer: ::prost::alloc::string::String, + /// fp_btc_pk is the BTC PK of the finality provider that casts this vote + #[prost(bytes="bytes", tag="2")] + pub fp_btc_pk: ::prost::bytes::Bytes, + /// block_height is the height of the conflicting blocks + #[prost(uint64, tag="3")] + pub block_height: u64, + /// pub_rand is the public randomness the finality provider has committed to + #[prost(bytes="bytes", tag="4")] + pub pub_rand: ::prost::bytes::Bytes, + /// canonical_app_hash is the AppHash of the canonical block + #[prost(bytes="bytes", tag="5")] + pub canonical_app_hash: ::prost::bytes::Bytes, + /// fork_app_hash is the AppHash of the fork block + #[prost(bytes="bytes", tag="6")] + pub fork_app_hash: ::prost::bytes::Bytes, + /// canonical_finality_sig is the finality signature to the canonical block + /// where finality signature is an EOTS signature, i.e., + /// the `s` in a Schnorr signature `(r, s)` + /// `r` is the public randomness that is already committed by the finality provider + #[prost(bytes="bytes", tag="7")] + pub canonical_finality_sig: ::prost::bytes::Bytes, + /// fork_finality_sig is the finality signature to the fork block + /// where finality signature is an EOTS signature + #[prost(bytes="bytes", tag="8")] + pub fork_finality_sig: ::prost::bytes::Bytes, } // @@protoc_insertion_point(module) diff --git a/packages/proto/src/gen/babylon.zoneconcierge.v1.rs b/packages/proto/src/gen/babylon.zoneconcierge.v1.rs index 4ea95c1b..a8a39e36 100644 --- a/packages/proto/src/gen/babylon.zoneconcierge.v1.rs +++ b/packages/proto/src/gen/babylon.zoneconcierge.v1.rs @@ -154,7 +154,7 @@ pub struct ProofFinalizedChainInfo { #[derive(Clone, PartialEq, ::prost::Message)] pub struct ZoneconciergePacketData { /// packet is the actual message carried in the IBC packet - #[prost(oneof="zoneconcierge_packet_data::Packet", tags="1, 2, 3")] + #[prost(oneof="zoneconcierge_packet_data::Packet", tags="1, 2, 3, 4")] pub packet: ::core::option::Option, } /// Nested message and enum types in `ZoneconciergePacketData`. @@ -169,6 +169,8 @@ pub mod zoneconcierge_packet_data { BtcStaking(super::super::super::btcstaking::v1::BtcStakingIbcPacket), #[prost(message, tag="3")] ConsumerSlashing(super::ConsumerSlashingIbcPacket), + #[prost(message, tag="4")] + ConsumerFpDistribution(cosmos_sdk_proto::ibc::applications::transfer::v2::FungibleTokenPacketData), } } /// BTCTimestamp is a BTC timestamp that carries information of a BTC-finalised epoch