Skip to content

Commit

Permalink
feat: update emissions distribution msg
Browse files Browse the repository at this point in the history
  • Loading branch information
javiersuweijie committed Jan 10, 2024
1 parent 2599ecc commit cd636ca
Show file tree
Hide file tree
Showing 8 changed files with 644 additions and 67 deletions.
138 changes: 107 additions & 31 deletions contracts/alliance-lp-hub/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,29 @@ use alliance_protocol::{
};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_json_binary, Addr, Binary, Coin as CwCoin, CosmosMsg, Decimal, DepsMut, Empty, Env,
MessageInfo, Reply, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg,
};
use cosmwasm_std::{to_json_binary, Addr, Binary, Coin as CwCoin, CosmosMsg, Decimal, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, Order};
use cw2::set_contract_version;
use cw_asset::{Asset, AssetInfo, AssetInfoKey};
use cw_asset::{Asset, AssetInfo, AssetInfoKey, AssetInfoUnchecked};
use cw_utils::parse_instantiate_response_data;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::str::FromStr;
use terra_proto_rs::{
alliance::alliance::{MsgClaimDelegationRewards, MsgDelegate, MsgRedelegate, MsgUndelegate},
cosmos::base::v1beta1::Coin,
traits::Message,
};
use alliance_protocol::alliance_oracle_types::EmissionsDistribution;
use alliance_protocol::alliance_protocol::AssetDistribution;

use crate::{
models::{Config, ExecuteMsg, InstantiateMsg, ModifyAsset},
state::{
ASSET_REWARD_DISTRIBUTION, ASSET_REWARD_RATE, BALANCES, CONFIG, TEMP_BALANCE,
ASSET_REWARD_RATE, BALANCES, CONFIG, TEMP_BALANCE,
TOTAL_BALANCES, UNCLAIMED_REWARDS, USER_ASSET_REWARD_RATE, VALIDATORS, WHITELIST,
},
};
use crate::state::UNALLOCATED_REWARDS;

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:terra-alliance-lp-hub";
Expand Down Expand Up @@ -67,6 +69,7 @@ pub fn instantiate(
CONFIG.save(deps.storage, &config)?;

VALIDATORS.save(deps.storage, &HashSet::new())?;
UNALLOCATED_REWARDS.save(deps.storage, &Uint128::zero())?;

Ok(Response::new()
.add_attributes(vec![("action", "instantiate")])
Expand Down Expand Up @@ -108,16 +111,15 @@ pub fn execute(
ExecuteMsg::AllianceRedelegate(msg) => alliance_redelegate(deps, env, info, msg),

ExecuteMsg::UpdateRewards {} => update_rewards(deps, env, info),
ExecuteMsg::RebalanceEmissions {} => rebalance_emissions(deps, env, info),
ExecuteMsg::RebalanceEmissions(distributions) => rebalance_emissions(deps, env, info, distributions),

ExecuteMsg::UpdateRewardsCallback {} => update_reward_callback(deps, env, info),
ExecuteMsg::RebalanceEmissionsCallback {} => rebalance_emissions_callback(deps, env, info),
ExecuteMsg::RebalanceEmissionsCallback(distributions) => rebalance_emissions_callback(deps, env, info, distributions),
}
}

// This method iterate through the list of assets to be modified,
// for each asset it checks if it is being listed or delisted,
// when listed and an asset already exists, it updates the reward rate.
fn modify_assets(
deps: DepsMut,
info: MessageInfo,
Expand All @@ -131,25 +133,18 @@ fn modify_assets(
if asset.delete {
let asset_key = AssetInfoKey::from(asset.asset_info.clone());
WHITELIST.remove(deps.storage, asset_key.clone());
ASSET_REWARD_RATE.update(deps.storage, asset_key, |_| -> StdResult<_> {
Ok(Decimal::zero())
})?;
attrs.extend_from_slice(&[
("asset".to_string(), asset.asset_info.to_string()),
("to_remove".to_string(), asset.delete.to_string()),
]);
} else {
let reward_rate = asset.is_valid_reward_rate()?;
let asset_key = AssetInfoKey::from(asset.asset_info.clone());

WHITELIST.save(deps.storage, asset_key.clone(), &reward_rate)?;
WHITELIST.save(deps.storage, asset_key.clone(), &Decimal::zero())?;
ASSET_REWARD_RATE.update(deps.storage, asset_key, |rate| -> StdResult<_> {
Ok(rate.unwrap_or(Decimal::zero()))
})?;

attrs.extend_from_slice(&[
("asset".to_string(), asset.asset_info.to_string()),
("to_rewards_rate".to_string(), reward_rate.to_string()),
]);
}
}
Expand Down Expand Up @@ -491,9 +486,60 @@ fn update_reward_callback(
if info.sender != env.contract.address {
return Err(ContractError::Unauthorized {});
}
let _config = CONFIG.load(deps.storage)?;
let config = CONFIG.load(deps.storage)?;
// We only deal with alliance rewards here. Other rewards (e.g. ASTRO) needs to be dealt with separately
// This is because the reward distribution only affects alliance rewards. LP rewards are directly distributed to LP holders
// and not pooled together and shared
let reward_asset = AssetInfo::native(config.reward_denom);
let current_balance = reward_asset.query_balance(&deps.querier, env.contract.address)?;
let previous_balance = TEMP_BALANCE.load(deps.storage)?;
let rewards_collected = current_balance - previous_balance;

let whitelist: StdResult<Vec<(AssetInfoKey, Decimal)>> = WHITELIST
.range_raw(deps.storage, None, None, Order::Ascending)
.map(|r| r.map(|(a,d)| (AssetInfoKey(a), d)))
.collect();

let whitelist = whitelist?;

let total_distribution = whitelist
.iter()
.fold(Decimal::zero(), |acc, (_,v ) | acc + v);

// Move all unallocated rewards to the unallocated rewards bucket
if let Ok(unallocated_rewards) = Decimal::one().checked_sub(total_distribution) {
UNALLOCATED_REWARDS.update(deps.storage, |rewards| -> StdResult<_> {
Ok(rewards + unallocated_rewards.to_uint_floor())
})?;
} else {
return Err(ContractError::InvalidTotalDistribution(total_distribution));
}

// Calculate the rewards for each asset
for (asset_key, distribution) in whitelist {
let total_reward_distributed: Decimal = Decimal::from_atomics(rewards_collected, 0)?
* distribution;

// If there are no balances, we stop updating the rate. This means that the emissions are not directed to any stakers.
let total_balance = TOTAL_BALANCES
.load(deps.storage, asset_key.clone())
.unwrap_or(Uint128::zero());
if total_balance.is_zero() {
continue
}

// TODO: maths
// Update reward rates for each asset
let rate_to_update =
total_reward_distributed / Decimal::from_atomics(total_balance, 0)?;
if rate_to_update > Decimal::zero() {
ASSET_REWARD_RATE.update(
deps.storage,
asset_key.clone(),
|rate| -> StdResult<_> { Ok(rate.unwrap_or(Decimal::zero()) + rate_to_update) },
)?;
}
}
TEMP_BALANCE.remove(deps.storage);

Ok(Response::new().add_attributes(vec![("action", "update_rewards_callback")]))
}
Expand All @@ -502,37 +548,67 @@ fn rebalance_emissions(
deps: DepsMut,
env: Env,
info: MessageInfo,
weights: Vec<EmissionsDistribution>,
) -> Result<Response, ContractError> {
// Allow execution only from the controller account
let config = CONFIG.load(deps.storage)?;
is_controller(&info, &config)?;

// Before starting with the rebalance emission process
// rewards must be updated to the current block height
// Skip if no reward distribution in the first place
let res = if ASSET_REWARD_DISTRIBUTION.load(deps.storage).is_ok() {
update_rewards(deps, env.clone(), info)?
} else {
Response::new()
};
// // Before starting with the rebalance emission process
// // rewards must be updated to the current block height
// // Skip if no reward distribution in the first place
// let res = if ASSET_REWARD_DISTRIBUTION.load(deps.storage).is_ok() {
// update_rewards(deps, env.clone(), info)?
// } else {
// Response::new()
// };

let res = Response::new();
Ok(res.add_message(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_json_binary(&ExecuteMsg::RebalanceEmissionsCallback {}).unwrap(),
msg: to_json_binary(&ExecuteMsg::RebalanceEmissionsCallback(weights)).unwrap(),
funds: vec![],
})))
}

fn rebalance_emissions_callback(
_deps: DepsMut,
deps: DepsMut,
env: Env,
info: MessageInfo,
distributions: Vec<EmissionsDistribution>,
) -> Result<Response, ContractError> {
if info.sender != env.contract.address {
return Err(ContractError::Unauthorized {});
}
// TODO maths
Ok(Response::new())

let total_distribution = distributions
.iter()
.map(|a| a.distribution.to_decimal().unwrap())
.fold(Decimal::zero(), |acc, v| acc + v);
if total_distribution > Decimal::one() {
return Err(ContractError::InvalidTotalDistribution(total_distribution));
}

for distribution in distributions.iter() {
let asset_info: AssetInfo = AssetInfoUnchecked::from_str(&distribution.denom)?.check(deps.api, None)?;
let asset_key = AssetInfoKey::from(asset_info.clone());
WHITELIST.update(deps.storage, asset_key, |current| -> Result<_, ContractError> {
if let Some(current) = current {
return Ok(current + distribution.distribution.to_decimal()?);
} else {
return Err(ContractError::AssetNotWhitelisted {});
}
})?;
}

let mut attrs = vec![("action".to_string(), "rebalance_emissions".to_string())];
for distribution in distributions {
attrs.push((
distribution.denom.to_string(),
distribution.distribution.to_string(),
));
}
Ok(Response::new().add_attributes(attrs))
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down
27 changes: 4 additions & 23 deletions contracts/alliance-lp-hub/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use cosmwasm_std::{Addr, Decimal, Uint128};
use cw20::Cw20ReceiveMsg;
use cw_asset::{Asset, AssetInfo};
use std::collections::{HashMap, HashSet};
use alliance_protocol::alliance_oracle_types::EmissionsDistribution;

pub type AssetDenom = String;

Expand Down Expand Up @@ -49,43 +50,23 @@ pub enum ExecuteMsg {
AllianceDelegate(AllianceDelegateMsg),
AllianceUndelegate(AllianceUndelegateMsg),
AllianceRedelegate(AllianceRedelegateMsg),
RebalanceEmissions {},
RebalanceEmissionsCallback {},
RebalanceEmissions(Vec<EmissionsDistribution>),
RebalanceEmissionsCallback(Vec<EmissionsDistribution>),
}

#[cw_serde]
pub struct ModifyAsset {
pub asset_info: AssetInfo,
pub rewards_rate: Option<Decimal>,
pub delete: bool,
}

impl ModifyAsset {
pub fn new(asset_info: AssetInfo, rewards_rate: Option<Decimal>, delete: bool) -> Self {
pub fn new(asset_info: AssetInfo, delete: bool) -> Self {
ModifyAsset {
asset_info,
rewards_rate,
delete,
}
}

pub fn is_valid_reward_rate(&self) -> Result<Decimal, ContractError> {
match self.rewards_rate {
Some(rate) => {
if rate < Decimal::zero() || rate > Decimal::one() {
return Err(ContractError::InvalidRewardRate(
rate,
self.asset_info.to_string(),
));
}
Ok(rate)
}
None => Err(ContractError::InvalidRewardRate(
Decimal::zero(),
self.asset_info.to_string(),
)),
}
}
}

#[cw_serde]
Expand Down
28 changes: 22 additions & 6 deletions contracts/alliance-lp-hub/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ use crate::models::{
};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdResult, Uint128};
use cw_asset::{AssetInfo, AssetInfoKey};
use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdResult, Uint128, Decimal};
use cw_asset::{AssetInfo, AssetInfoKey, AssetInfoUnchecked};
use std::collections::HashMap;
use std::convert::TryFrom;
use alliance_protocol::alliance_oracle_types::EmissionsDistribution;
use alliance_protocol::alliance_protocol::AssetDistribution;
use alliance_protocol::signed_decimal::{Sign, SignedDecimal};

use crate::state::{
ASSET_REWARD_DISTRIBUTION, ASSET_REWARD_RATE, BALANCES, CONFIG, TOTAL_BALANCES,
ASSET_REWARD_RATE, BALANCES, CONFIG, TOTAL_BALANCES,
UNCLAIMED_REWARDS, USER_ASSET_REWARD_RATE, VALIDATORS, WHITELIST,
};

Expand Down Expand Up @@ -55,9 +59,21 @@ fn get_whitelisted_assets(deps: Deps) -> StdResult<Binary> {
}

fn get_rewards_distribution(deps: Deps) -> StdResult<Binary> {
let asset_rewards_distr = ASSET_REWARD_DISTRIBUTION.load(deps.storage)?;

to_json_binary(&asset_rewards_distr)
let whitelist: StdResult<Vec<(AssetInfoUnchecked, Decimal)>> = WHITELIST
.range(deps.storage, None, None, Order::Ascending)
.collect();
let whitelist = whitelist?;

let reward_distribution: Vec<EmissionsDistribution> = whitelist
.iter()
.map(|(asset_info, distribution) |
EmissionsDistribution {
denom: asset_info.check( deps.api, None).unwrap().to_string(),
distribution: SignedDecimal::from_decimal(distribution.clone(), Sign::Positive),
}
)
.collect();
to_json_binary(&reward_distribution)
}

fn get_staked_balance(deps: Deps, asset_query: AssetQuery) -> StdResult<Binary> {
Expand Down
4 changes: 2 additions & 2 deletions contracts/alliance-lp-hub/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ pub const TOTAL_BALANCES: Map<AssetInfoKey, Uint128> = Map::new("total_balances"

pub const VALIDATORS: Item<HashSet<String>> = Item::new("validators");

pub const ASSET_REWARD_DISTRIBUTION: Item<Vec<AssetDistribution>> =
Item::new("asset_reward_distribution");
pub const ASSET_REWARD_RATE: Map<AssetInfoKey, Decimal> = Map::new("asset_reward_rate");
pub const USER_ASSET_REWARD_RATE: Map<(Addr, AssetInfoKey), Decimal> =
Map::new("user_asset_reward_rate");
pub const UNCLAIMED_REWARDS: Map<(Addr, AssetInfoKey), Uint128> = Map::new("unclaimed_rewards");
// Unallocated Alliance rewards that are to be returned to the fee pool for stakers
pub const UNALLOCATED_REWARDS: Item<Uint128> = Item::new("unallocated_rewards");

pub const TEMP_BALANCE: Item<Uint128> = Item::new("temp_balance");
2 changes: 1 addition & 1 deletion contracts/alliance-lp-hub/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod helpers;
mod instantiate;
mod stake_unstake;

mod rewards;
Loading

0 comments on commit cd636ca

Please sign in to comment.