Skip to content

Commit

Permalink
F/rewards generation 2 (#81)
Browse files Browse the repository at this point in the history
Rewards generation impl
  • Loading branch information
maurolacy authored Nov 8, 2024
1 parent 50f6aff commit 18b4530
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ cosmwasm-std = { version = "2.1.4", default-features = false, features = [
] }
cw2 = "2.0.0"
cw-controllers = "2.0.0"
cw-multi-test = "2.0.1"
cw-multi-test = { version = "2.0.1", features = [ "staking", "cosmwasm_1_1", "cosmwasm_2_0" ] }
cw-storage-plus = "2.0.0"
cw-utils = "2.0.0"
derivative = "2"
Expand Down
6 changes: 4 additions & 2 deletions contracts/btc-finality/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@ full-validation = [ "btc-staking/full-validation" ]
[dependencies]
babylon-apis = { path = "../../packages/apis" }
babylon-bindings = { path = "../../packages/bindings" }
babylon-contract = { path = "../babylon", features = [ "library" ] }
babylon-merkle = { path = "../../packages/merkle" }
babylon-proto = { path = "../../packages/proto" }
babylon-btcstaking = { path = "../../packages/btcstaking" }
babylon-bitcoin = { path = "../../packages/bitcoin" }
btc-staking = { path = "../btc-staking", features = [ "library" ] }
eots = { path = "../../packages/eots" }

babylon-contract = { path = "../babylon", features = [ "library" ] }
btc-staking = { path = "../btc-staking", features = [ "library" ] }

anybuf = { workspace = true }
bitcoin = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
Expand Down
38 changes: 37 additions & 1 deletion contracts/btc-finality/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response<BabylonMsg>, ContractError> {
nonpayable(&info)?;
let denom = deps.querier.query_bonded_denom()?;

// Query blocks per year from the chain's mint module
let blocks_per_year = get_blocks_per_year(&mut deps)?;
let config = Config {
denom,
blocks_per_year,
babylon: info.sender,
staking: Addr::unchecked("UNSET"), // To be set later, through `UpdateStaking`
};
Expand All @@ -47,6 +53,33 @@ pub fn instantiate(
Ok(Response::new().add_attribute("action", "instantiate"))
}

/// Queries the chain's blocks per year using the mint Params Grpc query
fn get_blocks_per_year(deps: &mut DepsMut) -> Result<u64, ContractError> {
let blocks_per_year;
#[cfg(any(test, all(feature = "library", not(target_arch = "wasm32"))))]
{
let _ = deps;
blocks_per_year = 60 * 60 * 24 * 365 / 6; // Default / hardcoded value for tests
}
#[cfg(not(any(test, all(feature = "library", not(target_arch = "wasm32")))))]
{
let res = deps.querier.query_grpc(
"/cosmos.mint.v1beta1.Query/Params".into(),
cosmwasm_std::Binary::new("".into()),
)?;
// Deserialize protobuf
let res_decoded = anybuf::Bufany::deserialize(&res).unwrap();
// See https://github.com/cosmos/cosmos-sdk/blob/8bfcf554275c1efbb42666cc8510d2da139b67fa/proto/cosmos/mint/v1beta1/query.proto#L35-L36
let res_params = res_decoded.message(1).unwrap();
// See https://github.com/cosmos/cosmos-sdk/blob/8bfcf554275c1efbb42666cc8510d2da139b67fa/proto/cosmos/mint/v1beta1/mint.proto#L60-L61
// to see from where the field number comes from
blocks_per_year = res_params
.uint64(6)
.ok_or(ContractError::MissingBlocksPerYear {})?;
}
Ok(blocks_per_year)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(_deps: DepsMut, _env: Env, _reply: Reply) -> StdResult<Response> {
Ok(Response::default())
Expand Down Expand Up @@ -212,7 +245,10 @@ 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 events = finality::tally_blocks(deps, activated_height, env.block.height)?;
let (msg, events) = finality::tally_blocks(deps, activated_height, env.block.height)?;
if let Some(msg) = msg {
res = res.add_message(msg);
}
res = res.add_events(events);
}
Ok(res)
Expand Down
4 changes: 4 additions & 0 deletions contracts/btc-finality/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,8 @@ pub enum ContractError {
SecretKeyExtractionError(String),
#[error("Hash length error: {0}")]
WrongHashLength(String),
#[error("Blocks per year could not be queried from the mint module")]
MissingBlocksPerYear {},
#[error("Division by zero")]
DivideByZero,
}
63 changes: 52 additions & 11 deletions contracts/btc-finality/src/finality.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
use k256::ecdsa::signature::Verifier;
use k256::schnorr::{Signature, VerifyingKey};
use k256::sha2::{Digest, Sha256};
use std::cmp::max;
use std::collections::HashSet;

use crate::contract::encode_smart_query;
use crate::error::ContractError;
use crate::state::config::{CONFIG, PARAMS};
use crate::state::config::{Config, CONFIG, PARAMS};
use crate::state::finality::{BLOCKS, EVIDENCES, FP_SET, NEXT_HEIGHT, SIGNATURES, TOTAL_POWER};
use crate::state::public_randomness::{
get_last_pub_rand_commit, get_pub_rand_commit_for_height, PUB_RAND_COMMITS, PUB_RAND_VALUES,
Expand All @@ -18,9 +12,15 @@ use babylon_merkle::Proof;
use btc_staking::msg::{FinalityProviderInfo, FinalityProvidersByPowerResponse};
use cosmwasm_std::Order::Ascending;
use cosmwasm_std::{
to_json_binary, Addr, DepsMut, Env, Event, QuerierWrapper, Response, StdResult, Storage,
WasmMsg,
to_json_binary, Addr, Coin, Decimal, DepsMut, Env, Event, QuerierWrapper, Response, StdResult,
Storage, WasmMsg,
};
use k256::ecdsa::signature::Verifier;
use k256::schnorr::{Signature, VerifyingKey};
use k256::sha2::{Digest, Sha256};
use std::cmp::max;
use std::collections::HashSet;
use std::ops::Mul;

pub fn handle_public_randomness_commit(
deps: DepsMut,
Expand Down Expand Up @@ -422,7 +422,7 @@ pub fn tally_blocks(
deps: &mut DepsMut,
activated_height: u64,
height: u64,
) -> Result<Vec<Event>, ContractError> {
) -> Result<(Option<BabylonMsg>, Vec<Event>), ContractError> {
// Start finalising blocks since max(activated_height, next_height)
let next_height = NEXT_HEIGHT.may_load(deps.storage)?.unwrap_or(0);
let start_height = max(activated_height, next_height);
Expand All @@ -437,6 +437,7 @@ pub fn tally_blocks(
// After this for loop, the blocks since the earliest activated height are either finalised or
// non-finalisable
let mut events = vec![];
let mut finalized_blocks = 0;
for h in start_height..=height {
let mut indexed_block = BLOCKS.load(deps.storage, h)?;
// Get the finality provider set of this block
Expand All @@ -452,6 +453,7 @@ pub fn tally_blocks(
if tally(&fp_set, &voter_btc_pks) {
// If this block gets >2/3 votes, finalise it
let ev = finalize_block(deps.storage, &mut indexed_block, &voter_btc_pks)?;
finalized_blocks += 1;
events.push(ev);
} else {
// If not, then this block and all subsequent blocks should not be finalised.
Expand Down Expand Up @@ -480,7 +482,21 @@ pub fn tally_blocks(
}
}
}
Ok(events)

// Compute block rewards for finalized blocks
let msg = if finalized_blocks > 0 {
let cfg = CONFIG.load(deps.storage)?;
let rewards = compute_block_rewards(deps, &cfg, finalized_blocks)?;
// Assemble mint message
let mint_msg = BabylonMsg::MintRewards {
amount: rewards,
recipient: cfg.staking.into(),
};
Some(mint_msg)
} else {
None
};
Ok((msg, events))
}

/// `tally` checks whether a block with the given finality provider set and votes reaches a quorum
Expand Down Expand Up @@ -521,6 +537,31 @@ fn finalize_block(
Ok(ev)
}

/// `compute_block_rewards` computes the block rewards for the finality providers
fn compute_block_rewards(
deps: &mut DepsMut,
cfg: &Config,
finalized_blocks: u64,
) -> Result<Coin, ContractError> {
// Get the total supply (standard bank query)
let total_supply = deps.querier.query_supply(cfg.denom.clone())?;

// Get the finality inflation rate (params)
let finality_inflation_rate = PARAMS.load(deps.storage)?.finality_inflation_rate;

// Compute the block rewards for the finalized blocks
let inv_blocks_per_year = Decimal::from_ratio(1u128, cfg.blocks_per_year);
let block_rewards = finality_inflation_rate
.mul(Decimal::from_ratio(total_supply.amount, 1u128))
.mul(inv_blocks_per_year)
.mul(Decimal::from_ratio(finalized_blocks, 1u128));

Ok(Coin {
denom: cfg.denom.clone(),
amount: block_rewards.to_uint_floor(),
})
}

const QUERY_LIMIT: Option<u32> = Some(30);

/// `compute_active_finality_providers` sorts all finality providers, counts the total voting
Expand Down
21 changes: 17 additions & 4 deletions contracts/btc-finality/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ mod finality {
use babylon_apis::finality_api::IndexedBlock;
use test_utils::get_public_randomness_commitment;

use cosmwasm_std::Event;
use cosmwasm_std::{coin, Event};
use test_utils::{
create_new_finality_provider, get_add_finality_sig, get_derived_btc_delegation,
get_pub_rand_value,
Expand Down Expand Up @@ -78,8 +78,12 @@ mod finality {
let proof = add_finality_signature.proof.unwrap();

let initial_height = pub_rand.start_height;
let initial_funds = &[coin(1_000_000, "TOKEN")];

let mut suite = SuiteBuilder::new().with_height(initial_height).build();
let mut suite = SuiteBuilder::new()
.with_height(initial_height)
.with_funds(initial_funds)
.build();

// Register one FP
// NOTE: the test data ensures that pub rand commit / finality sig are
Expand Down Expand Up @@ -166,8 +170,12 @@ mod finality {
let proof = add_finality_signature.proof.unwrap();

let initial_height = pub_rand.start_height;
let initial_funds = &[coin(1_000_000_000_000, "TOKEN")];

let mut suite = SuiteBuilder::new().with_height(initial_height).build();
let mut suite = SuiteBuilder::new()
.with_funds(initial_funds)
.with_height(initial_height)
.build();

// signed by the 1st FP
let new_fp = create_new_finality_provider(1);
Expand Down Expand Up @@ -260,6 +268,7 @@ mod finality {

mod slashing {
use babylon_apis::finality_api::IndexedBlock;
use cosmwasm_std::coin;
use test_utils::{
create_new_finality_provider, get_add_finality_sig, get_add_finality_sig_2,
get_derived_btc_delegation, get_pub_rand_value,
Expand All @@ -278,8 +287,12 @@ mod slashing {
let proof = add_finality_signature.proof.unwrap();

let initial_height = pub_rand.start_height;
let initial_funds = &[coin(10_000_000_000_000, "TOKEN")];

let mut suite = SuiteBuilder::new().with_height(initial_height).build();
let mut suite = SuiteBuilder::new()
.with_funds(initial_funds)
.with_height(initial_height)
.build();

// Register one FP
// NOTE: the test data ensures that pub rand commit / finality sig are
Expand Down
14 changes: 11 additions & 3 deletions contracts/btc-finality/src/multitest/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use anyhow::Result as AnyResult;
use derivative::Derivative;
use hex::ToHex;

use cosmwasm_std::{to_json_binary, Addr};
use cosmwasm_std::{to_json_binary, Addr, Coin};

use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor};

Expand Down Expand Up @@ -52,6 +52,7 @@ fn contract_babylon() -> Box<dyn Contract<BabylonMsg>> {
#[derivative(Default = "new")]
pub struct SuiteBuilder {
height: Option<u64>,
init_funds: Vec<Coin>,
}

impl SuiteBuilder {
Expand All @@ -60,6 +61,11 @@ impl SuiteBuilder {
self
}

pub fn with_funds(mut self, funds: &[Coin]) -> Self {
self.init_funds = funds.to_vec();
self
}

#[track_caller]
pub fn build(self) -> Suite {
let owner = Addr::unchecked("owner");
Expand All @@ -68,8 +74,10 @@ impl SuiteBuilder {

let _block_info = app.block_info();

app.init_modules(|_router, _api, _storage| -> AnyResult<()> { Ok(()) })
.unwrap();
app.init_modules(|router, _api, storage| -> AnyResult<()> {
router.bank.init_balance(storage, &owner, self.init_funds)
})
.unwrap();

let btc_staking_code_id =
app.store_code_with_creator(owner.clone(), contract_btc_staking());
Expand Down
7 changes: 6 additions & 1 deletion contracts/btc-finality/src/state/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use derivative::Derivative;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cosmwasm_std::{Addr, Decimal};

use cw_controllers::Admin;
use cw_storage_plus::Item;
Expand All @@ -15,6 +15,8 @@ pub(crate) const ADMIN: Admin = Admin::new("admin");
// TODO: Add / enable config entries as needed
#[cw_serde]
pub struct Config {
pub denom: String,
pub blocks_per_year: u64,
pub babylon: Addr,
pub staking: Addr,
}
Expand All @@ -32,4 +34,7 @@ pub struct Params {
/// should commit
#[derivative(Default(value = "1"))]
pub min_pub_rand: u64,
/// `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,
}
4 changes: 4 additions & 0 deletions packages/bindings-test/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ impl Module for BabylonModule {
// FIXME? We don't do anything here
Ok(AppResponse::default())
}
BabylonMsg::MintRewards { .. } => {
// FIXME? We don't do anything here
Ok(AppResponse::default())
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion packages/bindings/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! - FinalizedHeader: reporting a BTC-finalised header.
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{CosmosMsg, Empty};
use cosmwasm_std::{Coin, CosmosMsg, Empty};

/// BabylonMsg is the message that the Babylon contract can send to the Cosmos zone.
/// The Cosmos zone has to integrate https://github.com/babylonlabs-io/wasmbinding for
Expand All @@ -17,6 +17,11 @@ pub enum BabylonMsg {
height: i64,
time: i64, // NOTE: UNIX timestamp is in i64
},
/// MintRewards mints the requested block rewards for the finality providers.
/// It can only be sent from the finality contract.
/// The rewards are minted to the staking contract address, so that they
/// can be distributed across the active finality provider set
MintRewards { amount: Coin, recipient: String },
}

pub type BabylonSudoMsg = Empty;
Expand Down

0 comments on commit 18b4530

Please sign in to comment.