Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F/rewards generation 2 #81

Merged
merged 13 commits into from
Nov 8, 2024
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