diff --git a/contracts/btc-finality/src/contract.rs b/contracts/btc-finality/src/contract.rs index 045e7913..9e86f060 100644 --- a/contracts/btc-finality/src/contract.rs +++ b/contracts/btc-finality/src/contract.rs @@ -13,7 +13,8 @@ use btc_staking::msg::ActivatedHeightResponse; use crate::error::ContractError; use crate::finality::{ - compute_active_finality_providers, handle_finality_signature, handle_public_randomness_commit, + compute_active_finality_providers, distribute_rewards, handle_finality_signature, + handle_public_randomness_commit, }; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::config::{Config, ADMIN, CONFIG, PARAMS}; @@ -222,10 +223,14 @@ fn handle_update_staking( } fn handle_begin_block(deps: &mut DepsMut, env: Env) -> Result, ContractError> { + // Distribute rewards + distribute_rewards(deps, &env)?; + // Compute active finality provider set let max_active_fps = PARAMS.load(deps.storage)?.max_active_finality_providers as usize; - compute_active_finality_providers(deps, env, max_active_fps)?; + compute_active_finality_providers(deps, env.block.height, max_active_fps)?; + // TODO: Add events Ok(Response::new()) } @@ -245,7 +250,7 @@ fn handle_end_block( let ev = finality::index_block(deps, env.block.height, &hex::decode(app_hash_hex)?)?; res = res.add_event(ev); // Tally all non-finalised blocks - let (msg, events) = finality::tally_blocks(deps, activated_height, env.block.height)?; + let (msg, events) = finality::tally_blocks(deps, &env, activated_height)?; if let Some(msg) = msg { res = res.add_message(msg); } diff --git a/contracts/btc-finality/src/finality.rs b/contracts/btc-finality/src/finality.rs index 13f6cce6..2cf6ecf0 100644 --- a/contracts/btc-finality/src/finality.rs +++ b/contracts/btc-finality/src/finality.rs @@ -1,7 +1,9 @@ use crate::contract::encode_smart_query; use crate::error::ContractError; use crate::state::config::{Config, CONFIG, PARAMS}; -use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER}; +use crate::state::finality::{ + BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, REWARDS, SIGNATURES, TOTAL_REWARDS, +}; use crate::state::public_randomness::{ get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES, }; @@ -13,7 +15,7 @@ use btc_staking::msg::{FinalityProviderInfo, FinalityProvidersByPowerResponse}; use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ to_json_binary, Addr, Coin, Decimal, DepsMut, Env, Event, QuerierWrapper, Response, StdResult, - Storage, WasmMsg, + Storage, Uint128, WasmMsg, }; use k256::ecdsa::signature::Verifier; use k256::schnorr::{Signature, VerifyingKey}; @@ -420,8 +422,8 @@ pub fn index_block( /// It must be invoked only after the BTC staking protocol is activated. pub fn tally_blocks( deps: &mut DepsMut, + env: &Env, activated_height: u64, - height: u64, ) -> Result<(Option, Vec), ContractError> { // Start finalising blocks since max(activated_height, next_height) let next_height = NEXT_HEIGHT.may_load(deps.storage)?.unwrap_or(0); @@ -438,7 +440,7 @@ pub fn tally_blocks( // non-finalisable let mut events = vec![]; let mut finalized_blocks = 0; - for h in start_height..=height { + for h in start_height..=env.block.height { let mut indexed_block = BLOCKS.load(deps.storage, h)?; // Get the finality provider set of this block let fp_set = FP_SET.may_load(deps.storage, h)?; @@ -490,7 +492,7 @@ pub fn tally_blocks( // Assemble mint message let mint_msg = BabylonMsg::MintRewards { amount: rewards, - recipient: cfg.staking.into(), + recipient: env.contract.address.to_string(), }; Some(mint_msg) } else { @@ -528,8 +530,6 @@ fn finalize_block( // Set the next height to finalise as height+1 NEXT_HEIGHT.save(store, &(block.height + 1))?; - // TODO: Distribute rewards to BTC staking delegators - // Record the last finalized height metric let ev = Event::new("finalize_block") .add_attribute("module", "finality") @@ -568,7 +568,7 @@ const QUERY_LIMIT: Option = Some(30); /// power of top finality providers, and records them in the contract state pub fn compute_active_finality_providers( deps: &mut DepsMut, - env: Env, + height: u64, max_active_fps: usize, ) -> Result<(), ContractError> { let cfg = CONFIG.load(deps.storage)?; @@ -602,9 +602,7 @@ pub fn compute_active_finality_providers( // TODO: Filter out slashed / offline / jailed FPs // Save the new set of active finality providers // TODO: Purge old (height - finality depth) FP_SET entries to avoid bloating the storage - FP_SET.save(deps.storage, env.block.height, &finality_providers)?; - // Save the total voting power of the top n finality providers - TOTAL_POWER.save(deps.storage, &total_power)?; + FP_SET.save(deps.storage, height, &finality_providers)?; Ok(()) } @@ -622,3 +620,38 @@ pub fn list_fps_by_power( let res: FinalityProvidersByPowerResponse = querier.query(&query)?; Ok(res.fps) } + +/// `distribute_rewards` distributes rewards to finality providers who are in the active set at `height` +pub fn distribute_rewards(deps: &mut DepsMut, env: &Env) -> Result<(), ContractError> { + // Try to use the finality provider set at the previous height + let active_fps = FP_SET.may_load(deps.storage, env.block.height - 1)?; + // Short-circuit if there are no active finality providers + let active_fps = match active_fps { + Some(active_fps) => active_fps, + None => return Ok(()), + }; + // 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) + let cfg = CONFIG.load(deps.storage)?; + let rewards_amount = deps + .querier + .query_balance(env.contract.address.clone(), cfg.denom)? + .amount; + // Compute the rewards for each active FP + let mut total_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)) + .to_uint_floor(); + // Update the rewards for this FP + REWARDS.update(deps.storage, &fp.btc_pk_hex, |r| { + Ok::(r.unwrap_or_default() + reward) + })?; + // Compute the total rewards + total_rewards += reward; + } + // Update the total rewards + TOTAL_REWARDS.save(deps.storage, &total_rewards)?; + Ok(()) +} diff --git a/contracts/btc-finality/src/state/finality.rs b/contracts/btc-finality/src/state/finality.rs index 06202abf..122916af 100644 --- a/contracts/btc-finality/src/state/finality.rs +++ b/contracts/btc-finality/src/state/finality.rs @@ -1,3 +1,5 @@ +use cosmwasm_std::Uint128; + use cw_storage_plus::{Item, Map}; use babylon_apis::finality_api::{Evidence, IndexedBlock}; @@ -15,9 +17,11 @@ pub const NEXT_HEIGHT: Item = Item::new("next_height"); /// `FP_SET` is the calculated list of the active finality providers by height pub const FP_SET: Map> = Map::new("fp_set"); -/// `TOTAL_POWER` is the total power of all finality providers -// FIXME: Store by height? Remove? Not currently being used in the contract -pub const TOTAL_POWER: Item = Item::new("total_power"); - /// Map of double signing evidence by FP and block height pub const EVIDENCES: Map<(&str, u64), Evidence> = Map::new("evidences"); + +/// Map of pending finality provider rewards +pub const REWARDS: Map<&str, Uint128> = Map::new("rewards"); + +/// Total pending rewards +pub const TOTAL_REWARDS: Item = Item::new("total_rewards");