From cfaf8a07a3385d125b5d172728bedb1b74545eff Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 5 Dec 2024 09:55:50 +0100 Subject: [PATCH] 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 c3f4a982..3149a038 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, }