From 863a5c33bb544505d3283473dceb2aabd18fe66a Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 5 Dec 2024 09:55:50 +0100 Subject: [PATCH 01/29] Add send rewards distribution over FPs --- contracts/babylon/src/contract.rs | 3 + contracts/babylon/src/msg/contract.rs | 12 ++++ .../babylon/src/state/btc_light_client.rs | 1 + contracts/babylon/tests/integration.rs | 1 + contracts/btc-finality/Cargo.toml | 1 - contracts/btc-finality/src/contract.rs | 60 +++++++++++++++++-- contracts/btc-finality/src/state/config.rs | 3 + 7 files changed, 76 insertions(+), 5 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 38b82683..36f9b70f 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -269,6 +269,9 @@ pub fn execute( // TODO: Add events Ok(res) } + ExecuteMsg::SendRewards { .. } => { + todo!() + } } } diff --git a/contracts/babylon/src/msg/contract.rs b/contracts/babylon/src/msg/contract.rs index b094f0c8..734e77a1 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; @@ -108,6 +109,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] diff --git a/contracts/babylon/src/state/btc_light_client.rs b/contracts/babylon/src/state/btc_light_client.rs index 98e0b661..93835f91 100644 --- a/contracts/babylon/src/state/btc_light_client.rs +++ b/contracts/babylon/src/state/btc_light_client.rs @@ -371,6 +371,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/tests/integration.rs b/contracts/babylon/tests/integration.rs index dbe78dde..82b5b7c8 100644 --- a/contracts/babylon/tests/integration.rs +++ b/contracts/babylon/tests/integration.rs @@ -76,6 +76,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"), } } 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/src/contract.rs b/contracts/btc-finality/src/contract.rs index 9e86f060..16cb4431 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"); @@ -256,9 +257,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/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, } From a590c9a6ae05ae68d877094388a34ee319642a17 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 9 Dec 2024 17:50:25 +0100 Subject: [PATCH 02/29] Send rewards over IBC using ICS20 transfer --- contracts/babylon/src/contract.rs | 49 ++++++++++++++++--- contracts/babylon/src/error.rs | 4 +- .../babylon/src/state/btc_light_client.rs | 1 + contracts/babylon/src/state/config.rs | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 36f9b70f..41b71fac 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_bindings::BabylonMsg; use crate::error::ContractError; -use crate::ibc::{ibc_packet, IBC_CHANNEL}; +use crate::ibc::{ibc_packet, packet_timeout, IBC_CHANNEL}; 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"); @@ -269,8 +271,43 @@ pub fn execute( // TODO: Add events Ok(res) } - ExecuteMsg::SendRewards { .. } => { - todo!() + 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 {}); + } + // Build the memo payload + let memo_msg = to_json_string(&fp_distribution)?; + // Route to babylon over IBC + let channel = IBC_CHANNEL.load(deps.storage)?; + + // Construct the transfer message + let ibc_msg = IbcMsg::Transfer { + channel_id: channel.endpoint.channel_id, + to_address: "zoneconcierge".to_string(), + amount: info.funds[0].clone(), + timeout: packet_timeout(&env), + memo: Some(memo_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()) + } } } } diff --git a/contracts/babylon/src/error.rs b/contracts/babylon/src/error.rs index 1429b16f..42b85437 100644 --- a/contracts/babylon/src/error.rs +++ b/contracts/babylon/src/error.rs @@ -1,6 +1,6 @@ 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 +20,8 @@ pub enum ContractError { BabylonEpochError(#[from] BabylonEpochChainError), #[error("{0}")] CzHeaderError(#[from] CZHeaderChainError), + #[error("{0}")] + Payment(#[from] PaymentError), #[error("Contract already has an open IBC channel")] IbcChannelAlreadyOpen {}, #[error("The contract only supports ordered channels")] diff --git a/contracts/babylon/src/state/btc_light_client.rs b/contracts/babylon/src/state/btc_light_client.rs index 93835f91..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 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, } From 7054fc439c24a792ad05048bc31eb30776cd63f9 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 11 Dec 2024 11:38:37 +0100 Subject: [PATCH 03/29] Update schema --- .../babylon/schema/babylon-contract.json | 50 +++++++++++++++++++ contracts/babylon/schema/raw/execute.json | 46 +++++++++++++++++ .../schema/raw/response_to_config.json | 4 ++ 3 files changed, 100 insertions(+) diff --git a/contracts/babylon/schema/babylon-contract.json b/contracts/babylon/schema/babylon-contract.json index 21cf91de..6b5ea7ec 100644 --- a/contracts/babylon/schema/babylon-contract.json +++ b/contracts/babylon/schema/babylon-contract.json @@ -164,6 +164,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 +313,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" } } }, @@ -1286,6 +1332,7 @@ "babylon_tag", "btc_confirmation_depth", "checkpoint_finalization_timeout", + "denom", "network", "notify_cosmos_zone" ], @@ -1344,6 +1391,9 @@ "null" ] }, + "denom": { + "type": "string" + }, "network": { "$ref": "#/definitions/Network" }, 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/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" }, From 7c7981a09d0c9622bc0cc91c90a1c51fd11efa8d Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 14:14:47 +0100 Subject: [PATCH 04/29] Fix: don't fail if no rewards --- contracts/btc-finality/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 16cb4431..b15ff587 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -261,7 +261,7 @@ fn handle_end_block( // 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)?; + let rewards = TOTAL_REWARDS.may_load(deps.storage)?.unwrap_or_default(); if rewards.u128() > 0 { let wasm_msg = send_rewards_msg(deps, rewards.u128(), &cfg)?; res = res.add_message(wasm_msg); From fde567bda1c9806942f62b98212af22c6dcda0ea Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 15:07:51 +0100 Subject: [PATCH 05/29] Point babylon to consumer-reward-distribution dev branch --- packages/proto/babylon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto/babylon b/packages/proto/babylon index 6682eeca..bc74a3f5 160000 --- a/packages/proto/babylon +++ b/packages/proto/babylon @@ -1 +1 @@ -Subproject commit 6682eecad96e65c65edb7c257325ec52a65b81fa +Subproject commit bc74a3f57fe51f7f0c4b5271b7f4ffbf40519586 From 454b9c2785363a4700de5e79a604cb8e89321f17 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 15:14:46 +0100 Subject: [PATCH 06/29] Update / sync buf deps --- packages/proto/buf.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 7a99c6bd85164d35d7d459019a36fa5c232a089b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 17:36:11 +0100 Subject: [PATCH 07/29] Update babylon consumer-reward-distribution dev branch reverted broken op slashing msg --- packages/proto/babylon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto/babylon b/packages/proto/babylon index bc74a3f5..00629ff5 160000 --- a/packages/proto/babylon +++ b/packages/proto/babylon @@ -1 +1 @@ -Subproject commit bc74a3f57fe51f7f0c4b5271b7f4ffbf40519586 +Subproject commit 00629ff51bccc674b5d1c781296e1b39cf26c99c From 79587bd444be8896d33dc2d361de94d1d5f2db74 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 17:47:09 +0100 Subject: [PATCH 08/29] Remove nort found / reverted MsgEquivocationEvidence --- packages/proto/buf.gen.rust.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/proto/buf.gen.rust.yaml b/packages/proto/buf.gen.rust.yaml index 506a9004..78d9715f 100644 --- a/packages/proto/buf.gen.rust.yaml +++ b/packages/proto/buf.gen.rust.yaml @@ -23,7 +23,6 @@ types: - babylon.finality.v1.PubRandCommit - babylon.finality.v1.MsgAddFinalitySig - babylon.finality.v1.MsgCommitPubRandList - - babylon.finality.v1.MsgEquivocationEvidence plugins: - plugin: buf.build/community/neoeinstein-prost:v0.2.3 out: src/gen From 76eb9393c904f948b8d14ef06615a25856157daa Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 17:47:47 +0100 Subject: [PATCH 09/29] Add extern path rewrite / mapping rule for ibc --- packages/proto/buf.gen.rust.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto/buf.gen.rust.yaml b/packages/proto/buf.gen.rust.yaml index 78d9715f..2a314fd3 100644 --- a/packages/proto/buf.gen.rust.yaml +++ b/packages/proto/buf.gen.rust.yaml @@ -32,3 +32,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 From 15cec6a4d830d9cbaf79ad89908f7c42810fa8de Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 17:48:49 +0100 Subject: [PATCH 10/29] Update genrated files --- packages/proto/buf.lock | 14 ++++++++++++-- packages/proto/src/gen/babylon.finality.v1.rs | 8 -------- packages/proto/src/gen/babylon.zoneconcierge.v1.rs | 4 +++- 3 files changed, 15 insertions(+), 11 deletions(-) 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/src/gen/babylon.finality.v1.rs b/packages/proto/src/gen/babylon.finality.v1.rs index bdfd2418..78451791 100644 --- a/packages/proto/src/gen/babylon.finality.v1.rs +++ b/packages/proto/src/gen/babylon.finality.v1.rs @@ -105,12 +105,4 @@ pub struct MsgAddFinalitySig { #[prost(bytes="bytes", tag="7")] pub finality_sig: ::prost::bytes::Bytes, } -/// MsgEquivocationEvidence is the message for handling evidence of equivocation -#[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, -} // @@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 From c462edb724216aca9ab610dc1230ccdc9b32588c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 17 Dec 2024 18:00:14 +0100 Subject: [PATCH 11/29] Add error handler for IBC consumer fp distribution recv --- contracts/babylon/src/ibc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/babylon/src/ibc.rs b/contracts/babylon/src/ibc.rs index 3a780feb..3121db0c 100644 --- a/contracts/babylon/src/ibc.rs +++ b/contracts/babylon/src/ibc.rs @@ -141,6 +141,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| { From 4ab5e9d752f40768f4d220dccbfa1b085f59d1ff Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Dec 2024 08:35:13 +0100 Subject: [PATCH 12/29] Fix: Rewards distribution amount --- contracts/btc-finality/src/contract.rs | 3 ++- contracts/btc-finality/src/finality.rs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index b15ff587..2e8e6677 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -50,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")) @@ -261,7 +262,7 @@ fn handle_end_block( // 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.may_load(deps.storage)?.unwrap_or_default(); + 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); 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(()) } From 7ecfb9cd344e92e837fdc3cd406f897d0d6b58d5 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Dec 2024 15:42:21 +0100 Subject: [PATCH 13/29] Update babylon ref to latest base --- packages/proto/babylon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto/babylon b/packages/proto/babylon index 00629ff5..e901f1c4 160000 --- a/packages/proto/babylon +++ b/packages/proto/babylon @@ -1 +1 @@ -Subproject commit 00629ff51bccc674b5d1c781296e1b39cf26c99c +Subproject commit e901f1c4ca8f8adf26d617b4265d6d66346d26d2 From 6a0ab485eac51488b4e49e09e835a54cd4198fd0 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Dec 2024 15:42:39 +0100 Subject: [PATCH 14/29] Revert "Remove nort found / reverted MsgEquivocationEvidence" This reverts commit 878776c697d2c0571bc8aefe67379d2906459fbb. --- packages/proto/buf.gen.rust.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/proto/buf.gen.rust.yaml b/packages/proto/buf.gen.rust.yaml index 2a314fd3..28e34773 100644 --- a/packages/proto/buf.gen.rust.yaml +++ b/packages/proto/buf.gen.rust.yaml @@ -23,6 +23,7 @@ types: - babylon.finality.v1.PubRandCommit - babylon.finality.v1.MsgAddFinalitySig - babylon.finality.v1.MsgCommitPubRandList + - babylon.finality.v1.MsgEquivocationEvidence plugins: - plugin: buf.build/community/neoeinstein-prost:v0.2.3 out: src/gen From 97d57d9d3d3cc2e165c6b1cb06d5514c4f1de08c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Dec 2024 15:46:07 +0100 Subject: [PATCH 15/29] Update babylon to f/consumer-reward-distribution dev branch --- packages/proto/babylon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto/babylon b/packages/proto/babylon index e901f1c4..82a820c7 160000 --- a/packages/proto/babylon +++ b/packages/proto/babylon @@ -1 +1 @@ -Subproject commit e901f1c4ca8f8adf26d617b4265d6d66346d26d2 +Subproject commit 82a820c70b5a522d732c4b2338a94bcdd656174e From d0a071101b95ecc019be5d75eb82535b6f52852b Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 18 Dec 2024 15:46:58 +0100 Subject: [PATCH 16/29] Update generated protos --- packages/proto/src/gen/babylon.finality.v1.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/proto/src/gen/babylon.finality.v1.rs b/packages/proto/src/gen/babylon.finality.v1.rs index 78451791..57e8bf23 100644 --- a/packages/proto/src/gen/babylon.finality.v1.rs +++ b/packages/proto/src/gen/babylon.finality.v1.rs @@ -105,4 +105,36 @@ pub struct MsgAddFinalitySig { #[prost(bytes="bytes", tag="7")] pub finality_sig: ::prost::bytes::Bytes, } +/// MsgEquivocationEvidence is the message for handling evidence of equivocation +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgEquivocationEvidence { + #[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) From f20b71e6c46c1b08233d69a08abcaff195f6ebeb Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 19 Dec 2024 08:52:57 +0100 Subject: [PATCH 17/29] Add some staking and finality contract instantiation tests --- contracts/babylon/src/contract.rs | 69 ++++++++++++++++++++++++ contracts/babylon/src/multitest.rs | 51 ++++++++++++++++++ contracts/babylon/src/multitest/suite.rs | 36 ++++++++++--- 3 files changed, 148 insertions(+), 8 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 41b71fac..721e7c50 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -350,4 +350,73 @@ mod tests { 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, + }; + 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, + }; + 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() + ); + } } diff --git a/contracts/babylon/src/multitest.rs b/contracts/babylon/src/multitest.rs index 44fc23ee..72cca6ce 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -37,6 +37,7 @@ fn initialization() { mod instantiation { use super::*; + use cosmwasm_std::to_json_string; #[test] fn instantiate_works() { @@ -48,6 +49,56 @@ 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))); + } } mod migration { diff --git a/contracts/babylon/src/multitest/suite.rs b/contracts/babylon/src/multitest/suite.rs index 0c9b9929..da682644 100644 --- a/contracts/babylon/src/multitest/suite.rs +++ b/contracts/babylon/src/multitest/suite.rs @@ -1,13 +1,16 @@ -use crate::msg::contract::{InstantiateMsg, QueryMsg}; -use crate::multitest::{CONTRACT1_ADDR, CONTRACT2_ADDR}; -use crate::state::config::Config; 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 +41,8 @@ fn contract_babylon() -> Box> { #[derivative(Default = "new")] pub struct SuiteBuilder { funds: Vec<(Addr, u128)>, + staking_msg: Option, + finality_msg: Option, } impl SuiteBuilder { @@ -48,6 +53,18 @@ 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 + } + #[track_caller] pub fn build(self) -> Suite { let _funds = self.funds; @@ -66,6 +83,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,9 +97,9 @@ 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()), From 166fb6bc1ab13cbe0971561937ae7507a525c0e8 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 19 Dec 2024 09:06:56 +0100 Subject: [PATCH 18/29] Improve staking doc strings --- contracts/btc-staking/src/state/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From a49f7937a35d04906e289aaf92444a5800659d41 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 19 Dec 2024 09:07:35 +0100 Subject: [PATCH 19/29] Increase max wasm size limit check --- contracts/babylon/tests/integration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/babylon/tests/integration.rs b/contracts/babylon/tests/integration.rs index 82b5b7c8..6d609dc6 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 = 1 * 1024 * 1024; // 1 MB const CREATOR: &str = "creator"; From f8397f4be4abec32bd37405960a1fcbfe891660c Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Sun, 22 Dec 2024 13:23:21 +0100 Subject: [PATCH 20/29] Hardcoded IBC ICS20 channel info for tests --- contracts/babylon/src/contract.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 721e7c50..6a1dba97 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,7 +1,4 @@ -use cosmwasm_std::{ - to_json_binary, to_json_string, Addr, Binary, Deps, DepsMut, Empty, Env, IbcMsg, MessageInfo, - QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, -}; +use cosmwasm_std::{to_json_binary, to_json_string, Addr, Binary, Deps, DepsMut, Empty, Env, IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg}; use cw2::set_contract_version; use cw_utils::{must_pay, ParseReplyError}; @@ -285,7 +282,21 @@ pub fn execute( // Build the memo payload let memo_msg = to_json_string(&fp_distribution)?; // Route to babylon over IBC - let channel = IBC_CHANNEL.load(deps.storage)?; + // let channel = IBC_CHANNEL.load(deps.storage)?; + // TODO: Get from the list of open channels + let channel = IbcChannel::new( + IbcEndpoint { + port_id: "transfer".to_string(), + channel_id: "channel-1".to_string(), + }, + IbcEndpoint { + port_id: "transfer".to_string(), + channel_id: "channel-1".to_string(), + }, + IbcOrder::Unordered, + "ics20-1", + "connection-0", + ); // Construct the transfer message let ibc_msg = IbcMsg::Transfer { @@ -301,6 +312,7 @@ pub fn execute( #[cfg(not(any(test, feature = "library")))] { // TODO: Add events + deps.api.debug(&format!("FINALITY CONTRACT IBC msg: {:#?}", ibc_msg)); Ok(Response::new().add_message(ibc_msg)) } #[cfg(any(test, feature = "library"))] From 15822201469d759d22c91fe5348bc9c5e4792a39 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Mon, 23 Dec 2024 17:19:34 +0100 Subject: [PATCH 21/29] Hard-code babylon module (zoneconcierge) address --- contracts/babylon/src/contract.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index 6a1dba97..085bdefc 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -282,7 +282,6 @@ pub fn execute( // Build the memo payload let memo_msg = to_json_string(&fp_distribution)?; // Route to babylon over IBC - // let channel = IBC_CHANNEL.load(deps.storage)?; // TODO: Get from the list of open channels let channel = IbcChannel::new( IbcEndpoint { @@ -299,9 +298,11 @@ pub fn execute( ); // Construct the transfer message + // TODO: Generate or get from config + let module_address = "bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3"; let ibc_msg = IbcMsg::Transfer { channel_id: channel.endpoint.channel_id, - to_address: "zoneconcierge".to_string(), + to_address: module_address.to_string(), amount: info.funds[0].clone(), timeout: packet_timeout(&env), memo: Some(memo_msg), @@ -312,7 +313,6 @@ pub fn execute( #[cfg(not(any(test, feature = "library")))] { // TODO: Add events - deps.api.debug(&format!("FINALITY CONTRACT IBC msg: {:#?}", ibc_msg)); Ok(Response::new().add_message(ibc_msg)) } #[cfg(any(test, feature = "library"))] From 7f4daacab358967c90c61b99ad96f27538992882 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Tue, 24 Dec 2024 09:51:27 +0100 Subject: [PATCH 22/29] Add module_address and to_bech32 helpers --- Cargo.lock | 2 ++ Cargo.toml | 2 +- contracts/babylon/Cargo.toml | 2 ++ contracts/babylon/src/contract.rs | 57 ++++++++++++++++++++++++++----- contracts/babylon/src/error.rs | 2 ++ 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69efd942..78993e7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,7 @@ dependencies = [ "babylon-bindings-test", "babylon-bitcoin", "babylon-proto", + "bech32 0.9.1", "blst", "btc-finality", "btc-staking", @@ -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..3d948c74 100644 --- a/contracts/babylon/Cargo.toml +++ b/contracts/babylon/Cargo.toml @@ -36,6 +36,7 @@ babylon-apis = { path = "../../packages/apis" } babylon-bindings = { path = "../../packages/bindings" } babylon-proto = { path = "../../packages/proto" } babylon-bitcoin = { path = "../../packages/bitcoin" } +bech32 = { workspace = true } blst = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } @@ -48,6 +49,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/src/contract.rs b/contracts/babylon/src/contract.rs index 085bdefc..4020127b 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,16 +1,21 @@ -use cosmwasm_std::{to_json_binary, to_json_string, Addr, Binary, Deps, DepsMut, Empty, Env, IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg}; -use cw2::set_contract_version; -use cw_utils::{must_pay, ParseReplyError}; - -use babylon_apis::{btc_staking_api, finality_api}; -use babylon_bindings::BabylonMsg; - use crate::error::ContractError; use crate::ibc::{ibc_packet, packet_timeout, IBC_CHANNEL}; use crate::msg::contract::{ContractMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; use crate::state::btc_light_client; use crate::state::config::{Config, CONFIG}; +use babylon_apis::{btc_staking_api, finality_api}; +use babylon_bindings::BabylonMsg; +use bech32::ToBase32; +use bech32::Variant::Bech32; +use cosmwasm_std::{ + to_json_binary, to_json_string, Addr, Binary, CanonicalAddr, Deps, DepsMut, Empty, Env, + IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, MessageInfo, QueryResponse, Reply, Response, SubMsg, + SubMsgResponse, WasmMsg, +}; +use cw2::set_contract_version; +use cw_utils::{must_pay, ParseReplyError}; +use sha2::Digest; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -297,9 +302,9 @@ pub fn execute( "connection-0", ); + // TODO: Get prefix and module name from config + let module_address = to_bech32("bbn", &module_address("zoneconcierge"))?; // Construct the transfer message - // TODO: Generate or get from config - let module_address = "bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3"; let ibc_msg = IbcMsg::Transfer { channel_id: channel.endpoint.channel_id, to_address: module_address.to_string(), @@ -324,6 +329,27 @@ pub fn execute( } } +/// 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() +} + +/// Generates a Cosmos SDK compatible module address from a module name +// TODO: Move this to a common utility module +fn module_address(module_name: &str) -> CanonicalAddr { + CanonicalAddr::from(hash("", module_name.as_bytes())) +} + +/// Converts a CanonicalAddr to a Cosmos SDK compatible Bech32 encoded Addr +// TODO: Move this to a common utility module +fn to_bech32(prefix: &str, addr: &CanonicalAddr) -> Result { + let bech32_addr = bech32::encode(prefix, &addr.as_slice().to_base32()[..32], Bech32)?; + Ok(Addr::unchecked(bech32_addr)) +} + #[cfg(test)] mod tests { use super::*; @@ -431,4 +457,17 @@ mod tests { .into() ); } + + #[test] + fn test_module_address() { + // Example usage + let prefix = "bbn"; + let module_name = "zoneconcierge"; + + let addr = to_bech32(prefix, &module_address(module_name)).unwrap(); + assert_eq!( + addr.to_string(), + "bbn1wdptld6nw2plxzf0w62gqc60tlw5kypzej89y3" + ); + } } diff --git a/contracts/babylon/src/error.rs b/contracts/babylon/src/error.rs index 42b85437..e9a4ddca 100644 --- a/contracts/babylon/src/error.rs +++ b/contracts/babylon/src/error.rs @@ -22,6 +22,8 @@ pub enum ContractError { CzHeaderError(#[from] CZHeaderChainError), #[error("{0}")] Payment(#[from] PaymentError), + #[error("{0}")] + Bech32(#[from] bech32::Error), #[error("Contract already has an open IBC channel")] IbcChannelAlreadyOpen {}, #[error("The contract only supports ordered channels")] From b55f8df7d85a05d222b132a9392bb576fd49aa63 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 16:18:14 +0100 Subject: [PATCH 23/29] Implement transfer info parameter / option / query --- contracts/babylon/benches/main.rs | 1 + contracts/babylon/src/contract.rs | 106 +++++++++++++---------- contracts/babylon/src/ibc.rs | 18 +++- contracts/babylon/src/msg/contract.rs | 12 +++ contracts/babylon/src/msg/ibc.rs | 38 ++++++++ contracts/babylon/src/multitest.rs | 42 +++++++++ contracts/babylon/src/multitest/suite.rs | 23 +++++ contracts/babylon/src/queries/mod.rs | 11 ++- contracts/babylon/tests/integration.rs | 4 +- 9 files changed, 205 insertions(+), 50 deletions(-) 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/src/contract.rs b/contracts/babylon/src/contract.rs index 4020127b..f9e46301 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,5 +1,5 @@ use crate::error::ContractError; -use crate::ibc::{ibc_packet, packet_timeout, 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; @@ -9,9 +9,8 @@ use babylon_bindings::BabylonMsg; use bech32::ToBase32; use bech32::Variant::Bech32; use cosmwasm_std::{ - to_json_binary, to_json_string, Addr, Binary, CanonicalAddr, Deps, DepsMut, Empty, Env, - IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, MessageInfo, QueryResponse, Reply, Response, SubMsg, - SubMsgResponse, WasmMsg, + to_json_binary, to_json_string, Addr, Binary, CanonicalAddr, Deps, DepsMut, Empty, Env, IbcMsg, + MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, }; use cw2::set_contract_version; use cw_utils::{must_pay, ParseReplyError}; @@ -98,6 +97,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("bbn", &module_address(&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) } @@ -201,6 +219,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)?)?), } } @@ -284,46 +303,38 @@ pub fn execute( if info.sender != btc_finality { return Err(ContractError::Unauthorized {}); } - // Build the memo payload - let memo_msg = to_json_string(&fp_distribution)?; - // Route to babylon over IBC - // TODO: Get from the list of open channels - let channel = IbcChannel::new( - IbcEndpoint { - port_id: "transfer".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcEndpoint { - port_id: "transfer".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "ics20-1", - "connection-0", - ); - - // TODO: Get prefix and module name from config - let module_address = to_bech32("bbn", &module_address("zoneconcierge"))?; - // Construct the transfer message - let ibc_msg = IbcMsg::Transfer { - channel_id: channel.endpoint.channel_id, - to_address: module_address.to_string(), - amount: info.funds[0].clone(), - timeout: packet_timeout(&env), - memo: Some(memo_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()) + // 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()) + } } } } @@ -339,13 +350,13 @@ fn hash(namespace: &str, input: &[u8]) -> Vec { /// Generates a Cosmos SDK compatible module address from a module name // TODO: Move this to a common utility module -fn module_address(module_name: &str) -> CanonicalAddr { +pub fn module_address(module_name: &str) -> CanonicalAddr { CanonicalAddr::from(hash("", module_name.as_bytes())) } /// Converts a CanonicalAddr to a Cosmos SDK compatible Bech32 encoded Addr // TODO: Move this to a common utility module -fn to_bech32(prefix: &str, addr: &CanonicalAddr) -> Result { +pub fn to_bech32(prefix: &str, addr: &CanonicalAddr) -> Result { let bech32_addr = bech32::encode(prefix, &addr.as_slice().to_base32()[..32], Bech32)?; Ok(Addr::unchecked(bech32_addr)) } @@ -383,6 +394,7 @@ 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(); @@ -405,6 +417,7 @@ 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(); @@ -440,6 +453,7 @@ 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(); diff --git a/contracts/babylon/src/ibc.rs b/contracts/babylon/src/ibc.rs index 3121db0c..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) @@ -318,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, @@ -341,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 734e77a1..d24b193a 100644 --- a/contracts/babylon/src/msg/contract.rs +++ b/contracts/babylon/src/msg/contract.rs @@ -53,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 { @@ -84,6 +87,10 @@ impl ContractMsg for InstantiateMsg { } } + if let Some(transfer_info) = &self.transfer_info { + transfer_info.validate()?; + } + Ok(()) } @@ -169,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..7eefaf76 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::new_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) => { + new_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 72cca6ce..86f27b78 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -37,6 +37,8 @@ fn initialization() { mod instantiation { use super::*; + use crate::contract::{module_address, to_bech32}; + use crate::msg::ibc::Recipient; use cosmwasm_std::to_json_string; #[test] @@ -99,6 +101,46 @@ 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_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("bbn", &module_address("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 da682644..44960a76 100644 --- a/contracts/babylon/src/multitest/suite.rs +++ b/contracts/babylon/src/multitest/suite.rs @@ -1,3 +1,5 @@ +use crate::msg::ibc::TransferInfoResponse; +use crate::msg::ibc::{IbcTransferInfo, Recipient}; use anyhow::Result as AnyResult; use derivative::Derivative; @@ -43,6 +45,7 @@ pub struct SuiteBuilder { funds: Vec<(Addr, u128)>, staking_msg: Option, finality_msg: Option, + transfer_info: Option, } impl SuiteBuilder { @@ -65,6 +68,17 @@ impl SuiteBuilder { 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; @@ -103,6 +117,7 @@ impl SuiteBuilder { 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", @@ -161,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/tests/integration.rs b/contracts/babylon/tests/integration.rs index 6d609dc6..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 = 1 * 1024 * 1024; // 1 MB +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(); @@ -107,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); From cef1f1b5efd9574442fe8b06999194a3be0a8076 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 16:42:10 +0100 Subject: [PATCH 24/29] Move addr utils / helpers to apis --- packages/apis/Cargo.toml | 3 ++- packages/apis/src/lib.rs | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) 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)) +} From 3cb26479c49829e4c467e9f6714a3f48b8210576 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 17:00:07 +0100 Subject: [PATCH 25/29] cargo clippy fixes in passing --- .../op-finality-gadget/src/exec/finality.rs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) 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) ); } From beab17c31460e53dcde98c9c944ad6acf5d91a50 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 17:02:53 +0100 Subject: [PATCH 26/29] Fix: babylon contract instantiation in mt --- contracts/btc-finality/src/multitest/suite.rs | 1 + 1 file changed, 1 insertion(+) 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", From 27bb35a3b5e72ff8a294c50f921cfc232ea7f7c7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 17:07:18 +0100 Subject: [PATCH 27/29] Refactor to use addr helpers from api --- contracts/babylon/Cargo.toml | 1 - contracts/babylon/src/contract.rs | 46 ++++++--------------- contracts/babylon/src/error.rs | 5 ++- contracts/babylon/src/msg/ibc.rs | 4 +- contracts/babylon/src/multitest.rs | 4 +- contracts/btc-staking/src/validation/mod.rs | 6 +-- 6 files changed, 22 insertions(+), 44 deletions(-) diff --git a/contracts/babylon/Cargo.toml b/contracts/babylon/Cargo.toml index 3d948c74..b829268f 100644 --- a/contracts/babylon/Cargo.toml +++ b/contracts/babylon/Cargo.toml @@ -36,7 +36,6 @@ babylon-apis = { path = "../../packages/apis" } babylon-bindings = { path = "../../packages/bindings" } babylon-proto = { path = "../../packages/proto" } babylon-bitcoin = { path = "../../packages/bitcoin" } -bech32 = { workspace = true } blst = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/babylon/src/contract.rs b/contracts/babylon/src/contract.rs index f9e46301..89e56d58 100644 --- a/contracts/babylon/src/contract.rs +++ b/contracts/babylon/src/contract.rs @@ -1,20 +1,19 @@ +use cosmwasm_std::{ + 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::{must_pay, ParseReplyError}; + +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, packet_timeout, TransferInfo, IBC_CHANNEL, IBC_TRANSFER}; use crate::msg::contract::{ContractMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::queries; use crate::state::btc_light_client; use crate::state::config::{Config, CONFIG}; -use babylon_apis::{btc_staking_api, finality_api}; -use babylon_bindings::BabylonMsg; -use bech32::ToBase32; -use bech32::Variant::Bech32; -use cosmwasm_std::{ - to_json_binary, to_json_string, Addr, Binary, CanonicalAddr, Deps, DepsMut, Empty, Env, IbcMsg, - MessageInfo, QueryResponse, Reply, Response, SubMsg, SubMsgResponse, WasmMsg, -}; -use cw2::set_contract_version; -use cw_utils::{must_pay, ParseReplyError}; -use sha2::Digest; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -102,7 +101,7 @@ pub fn instantiate( 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("bbn", &module_address(&module))?.to_string(), + to_bech32_addr("bbn", &to_module_canonical_addr(&module))?.to_string(), "module", ), }; @@ -340,27 +339,6 @@ pub fn execute( } } -/// 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() -} - -/// Generates a Cosmos SDK compatible module address from a module name -// TODO: Move this to a common utility module -pub fn module_address(module_name: &str) -> CanonicalAddr { - CanonicalAddr::from(hash("", module_name.as_bytes())) -} - -/// Converts a CanonicalAddr to a Cosmos SDK compatible Bech32 encoded Addr -// TODO: Move this to a common utility module -pub fn to_bech32(prefix: &str, addr: &CanonicalAddr) -> Result { - let bech32_addr = bech32::encode(prefix, &addr.as_slice().to_base32()[..32], Bech32)?; - Ok(Addr::unchecked(bech32_addr)) -} - #[cfg(test)] mod tests { use super::*; @@ -478,7 +456,7 @@ mod tests { let prefix = "bbn"; let module_name = "zoneconcierge"; - let addr = to_bech32(prefix, &module_address(module_name)).unwrap(); + 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 e9a4ddca..369e3832 100644 --- a/contracts/babylon/src/error.rs +++ b/contracts/babylon/src/error.rs @@ -1,3 +1,4 @@ +use babylon_apis::error::StakingApiError; use babylon_bitcoin::Work; use cosmwasm_std::StdError; use cw_utils::{ParseReplyError, PaymentError}; @@ -22,8 +23,8 @@ pub enum ContractError { CzHeaderError(#[from] CZHeaderChainError), #[error("{0}")] Payment(#[from] PaymentError), - #[error("{0}")] - Bech32(#[from] bech32::Error), + #[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/msg/ibc.rs b/contracts/babylon/src/msg/ibc.rs index 7eefaf76..c830183d 100644 --- a/contracts/babylon/src/msg/ibc.rs +++ b/contracts/babylon/src/msg/ibc.rs @@ -1,5 +1,5 @@ use crate::ibc::TransferInfo; -use babylon_apis::new_canonical_addr; +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}; @@ -23,7 +23,7 @@ impl IbcTransferInfo { } match self.recipient { Recipient::ContractAddr(ref addr) => { - new_canonical_addr(addr, "bbn").map_err(|e| { + to_canonical_addr(addr, "bbn").map_err(|e| { StdError::generic_err(format!("Invalid contract address: {}", e)) })?; } diff --git a/contracts/babylon/src/multitest.rs b/contracts/babylon/src/multitest.rs index 86f27b78..71b236ed 100644 --- a/contracts/babylon/src/multitest.rs +++ b/contracts/babylon/src/multitest.rs @@ -37,8 +37,8 @@ fn initialization() { mod instantiation { use super::*; - use crate::contract::{module_address, to_bech32}; use crate::msg::ibc::Recipient; + use babylon_apis::{to_bech32_addr, to_module_canonical_addr}; use cosmwasm_std::to_json_string; #[test] @@ -116,7 +116,7 @@ mod instantiation { assert_eq!(transfer_info.channel_id, "channel-10"); assert_eq!( transfer_info.to_address, - to_bech32("bbn", &module_address("module-addr")) + to_bech32_addr("bbn", &to_module_canonical_addr("module-addr")) .unwrap() .to_string() ); 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 From f509d7e462b04eb547885ab803e4bdceaae58046 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 17:15:56 +0100 Subject: [PATCH 28/29] Update lock file --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 78993e7b..4426d4bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "hex", + "sha2", "tendermint-proto", "thiserror", ] @@ -300,7 +301,6 @@ dependencies = [ "babylon-bindings-test", "babylon-bitcoin", "babylon-proto", - "bech32 0.9.1", "blst", "btc-finality", "btc-staking", From f42067df8d0f8c651302e742b03681a1a2696542 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 3 Jan 2025 17:16:13 +0100 Subject: [PATCH 29/29] Update schemas --- .../babylon/schema/babylon-contract.json | 104 +++++++++++++++++ contracts/babylon/schema/raw/instantiate.json | 55 +++++++++ contracts/babylon/schema/raw/query.json | 14 +++ .../schema/raw/response_to_transfer_info.json | 35 ++++++ .../btc-finality/schema/btc-finality.json | 18 ++- .../btc-finality/schema/raw/instantiate.json | 9 +- .../schema/raw/response_to_params.json | 9 +- contracts/btc-staking/schema/btc-staking.json | 18 ++- .../btc-staking/schema/raw/instantiate.json | 9 +- .../schema/raw/response_to_params.json | 9 +- .../schema/op-finality-gadget.json | 106 ++++++++++++++++++ .../schema/raw/execute.json | 106 ++++++++++++++++++ 12 files changed, 484 insertions(+), 8 deletions(-) create mode 100644 contracts/babylon/schema/raw/response_to_transfer_info.json diff --git a/contracts/babylon/schema/babylon-contract.json b/contracts/babylon/schema/babylon-contract.json index 6b5ea7ec..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 + } + ] } } }, @@ -579,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 } ] }, @@ -1572,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/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_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/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-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/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",