Skip to content

Commit

Permalink
feat: send unallocated rewards to fee collector
Browse files Browse the repository at this point in the history
  • Loading branch information
emidev98 committed Jan 15, 2024
1 parent ea56af9 commit d611779
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 46 deletions.
7 changes: 0 additions & 7 deletions contracts/alliance-lp-hub/src/astro_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ pub enum AstroRewardType {
}

/// This enum describes available Token types.
/// ## Examples
/// ```
/// # use cosmwasm_std::Addr;
/// # use astroport::asset::AssetInfo::{NativeToken, Token};
/// Token { contract_addr: Addr::unchecked("stake...") };
/// NativeToken { denom: String::from("uluna") };
/// ```
#[cw_serde]
#[derive(Hash, Eq)]
pub enum AstroAssetInfo {
Expand Down
24 changes: 15 additions & 9 deletions contracts/alliance-lp-hub/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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, Order};
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, BankMsg};
use cw2::set_contract_version;
use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg};
use cw_asset::{Asset, AssetInfo, AssetInfoKey, AssetInfoUnchecked};
Expand All @@ -28,7 +28,6 @@ use crate::{
TOTAL_BALANCES, UNCLAIMED_REWARDS, USER_ASSET_REWARD_RATE, VALIDATORS, WHITELIST,
}, astro_models::{QueryAstroMsg, RewardInfo, ExecuteAstroMsg, Cw20Msg},
};
use crate::state::UNALLOCATED_REWARDS;

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:terra-alliance-lp-hub";
Expand All @@ -51,6 +50,8 @@ pub fn instantiate(
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let governance_address = deps.api.addr_validate(msg.governance.as_str())?;
let controller_address = deps.api.addr_validate(msg.controller.as_str())?;
let astro_incentives_address = deps.api.addr_validate(msg.astro_incentives_address.as_str())?;
let fee_collector_address = deps.api.addr_validate(msg.fee_collector_address.as_str())?;
let create_msg = TokenExecuteMsg::CreateDenom {
subdenom: "ualliancelp".to_string(),
};
Expand All @@ -61,15 +62,15 @@ pub fn instantiate(
let config = Config {
governance: governance_address,
controller: controller_address,
astro_incentives_addr: msg.astro_incentives_addr,
fee_collector_address: fee_collector_address,
astro_incentives_addr: astro_incentives_address,
alliance_token_denom: "".to_string(),
alliance_token_supply: Uint128::zero(),
reward_denom: msg.reward_denom,
};
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 @@ -547,10 +548,11 @@ fn update_reward_callback(
return Err(ContractError::Unauthorized {});
}
let config = CONFIG.load(deps.storage)?;
let mut res = Response::new().add_attributes(vec![("action", "update_rewards_callback")]);
// 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 reward_asset = AssetInfo::native(config.reward_denom.clone());
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;
Expand All @@ -568,9 +570,13 @@ fn update_reward_callback(

// Move all unallocated rewards to the unallocated rewards bucket
if let Ok(unallocated_distribution) = Decimal::one().checked_sub(total_distribution) {
UNALLOCATED_REWARDS.update(deps.storage, |rewards| -> Result<_, ContractError> {
Ok(rewards + (Decimal::from_atomics(rewards_collected, 0)? * unallocated_distribution).to_uint_floor())
})?;
let unallocated_rewards = (Decimal::from_atomics(rewards_collected, 0)? * unallocated_distribution).to_uint_floor();
if !unallocated_rewards.is_zero() {
res = res.add_message(BankMsg::Send {
to_address: config.fee_collector_address.to_string(),
amount: vec![CwCoin::new(unallocated_rewards.u128(), config.reward_denom)]
})
}
} else {
return Err(ContractError::InvalidTotalDistribution(total_distribution));
}
Expand Down Expand Up @@ -601,7 +607,7 @@ fn update_reward_callback(
}
TEMP_BALANCE.remove(deps.storage);

Ok(Response::new().add_attributes(vec![("action", "update_rewards_callback")]))
Ok(res)
}

fn rebalance_emissions(
Expand Down
4 changes: 3 additions & 1 deletion contracts/alliance-lp-hub/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub type AssetDenom = String;
pub struct Config {
pub governance: Addr,
pub controller: Addr,
pub fee_collector_address: Addr,
pub astro_incentives_addr: Addr,
pub alliance_token_denom: String,
pub alliance_token_supply: Uint128,
Expand All @@ -24,7 +25,8 @@ pub struct Config {
pub struct InstantiateMsg {
pub governance: String,
pub controller: String,
pub astro_incentives_addr: Addr,
pub fee_collector_address: String,
pub astro_incentives_address: String,
pub reward_denom: String,
}

Expand Down
2 changes: 0 additions & 2 deletions contracts/alliance-lp-hub/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,5 @@ pub const ASSET_REWARD_RATE: Map<AssetInfoKey, Decimal> = Map::new("asset_reward
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");
13 changes: 8 additions & 5 deletions contracts/alliance-lp-hub/src/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use alliance_protocol::token_factory::CustomExecuteMsg;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coin, from_json, Deps, DepsMut, Response, StdResult, Uint128, Binary, Addr};
use cw20::Cw20ReceiveMsg;
use cw_asset::{Asset, AssetInfo};
use cw_asset::{Asset, AssetInfo, AssetBase};

pub const DENOM: &str = "token_factory/token";

Expand All @@ -23,7 +23,8 @@ pub fn setup_contract(deps: DepsMut) -> Response<CustomExecuteMsg> {

let init_msg = InstantiateMsg {
governance: "gov".to_string(),
astro_incentives_addr : Addr::unchecked("astro_incentives"),
fee_collector_address: "collector_address".to_string(),
astro_incentives_address : "astro_incentives".to_string(),
controller: "controller".to_string(),
reward_denom: "uluna".to_string(),
};
Expand Down Expand Up @@ -71,11 +72,13 @@ pub fn stake_cw20(deps: DepsMut, user: &str, amount: u128, denom: &str) -> Respo
execute(deps, env, info, msg).unwrap()
}

pub fn unstake(deps: DepsMut, user: &str, amount: u128, denom: &str) -> Response {
pub fn unstake(deps: DepsMut, user: &str, asset: Asset) -> Response {
let info = mock_info(user, &[]);
let env = mock_env();
let msg = ExecuteMsg::Unstake(Asset::native(denom, amount));
execute(deps, env, info, msg).unwrap()
let msg = ExecuteMsg::Unstake(asset);
let res = execute(deps, env, info, msg);

res.unwrap()
}

pub fn alliance_delegate(deps: DepsMut, delegations: Vec<(&str, u128)>) -> Response {
Expand Down
2 changes: 2 additions & 0 deletions contracts/alliance-lp-hub/src/tests/instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn test_setup_contract() {
Config {
governance: Addr::unchecked("gov"),
controller: Addr::unchecked("controller"),
fee_collector_address: Addr::unchecked("collector_address"),
astro_incentives_addr : Addr::unchecked("astro_incentives"),
reward_denom: "uluna".to_string(),
alliance_token_denom: "".to_string(),
Expand Down Expand Up @@ -108,6 +109,7 @@ fn test_reply_create_token() {
Config {
governance: Addr::unchecked("gov"),
controller: Addr::unchecked("controller"),
fee_collector_address: Addr::unchecked("collector_address"),
astro_incentives_addr : Addr::unchecked("astro_incentives"),
reward_denom: "uluna".to_string(),
alliance_token_denom: "factory/cosmos2contract/ualliancelp".to_string(),
Expand Down
16 changes: 10 additions & 6 deletions contracts/alliance-lp-hub/src/tests/rewards.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::contract::execute;
use crate::models::{ExecuteMsg, ModifyAsset, PendingRewardsRes};
use crate::state::{ASSET_REWARD_RATE, TEMP_BALANCE, TOTAL_BALANCES, UNALLOCATED_REWARDS, USER_ASSET_REWARD_RATE, VALIDATORS, WHITELIST};
use crate::state::{ASSET_REWARD_RATE, TEMP_BALANCE, TOTAL_BALANCES, USER_ASSET_REWARD_RATE, VALIDATORS, WHITELIST};
use crate::tests::helpers::{
claim_rewards, query_all_rewards, query_rewards, set_alliance_asset, setup_contract, stake,
unstake, modify_asset, DENOM,
Expand All @@ -10,7 +10,7 @@ use cosmwasm_std::{
coin, coins, to_json_binary, Addr, BankMsg, Binary, CosmosMsg, Decimal, Response, SubMsg,
Uint128, WasmMsg,
};
use cw_asset::{AssetInfo, AssetInfoKey};
use cw_asset::{AssetInfo, AssetInfoKey, Asset};
use std::collections::HashSet;
use terra_proto_rs::alliance::alliance::MsgClaimDelegationRewards;
use terra_proto_rs::traits::Message;
Expand Down Expand Up @@ -214,12 +214,15 @@ fn update_reward_callback_with_unallocated() {
b_whale_rate,
Decimal::from_atomics(Uint128::new(6), 0).unwrap()
);
let unallocated_rewards = UNALLOCATED_REWARDS.load(deps.as_ref().storage).unwrap();
assert_eq!(unallocated_rewards, Uint128::new(300000));

assert_eq!(
res,
Response::new().add_attributes(vec![("action", "update_rewards_callback"),])
Response::new()
.add_attributes(vec![("action", "update_rewards_callback")])
.add_message(BankMsg::Send {
to_address: "collector_address".to_string(),
amount: vec![coin(300000, "uluna")]
})
);
}

Expand Down Expand Up @@ -475,7 +478,8 @@ fn claim_rewards_after_staking_and_unstaking() {
.unwrap();

// Unstake
unstake(deps.as_mut(), "user1", 1000000, "aWHALE");
let asset_info = Asset::native(Addr::unchecked("aWHALE"), 1000000u128);
unstake(deps.as_mut(), "user1", asset_info);

// Accrue rewards again
TEMP_BALANCE
Expand Down
67 changes: 51 additions & 16 deletions contracts/alliance-lp-hub/src/tests/stake_unstake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,25 +256,26 @@ fn test_unstake() {
modify_asset(
deps.as_mut(),
vec![ModifyAsset {
asset_info: AssetInfo::Cw20(Addr::unchecked("cw20_asset")),
asset_info: AssetInfo::native("native_asset"),
delete: false,
}],
);
stake_cw20(deps.as_mut(), "user1", 100, "cw20_asset");

let res = unstake(deps.as_mut(), "user1", 50, "cw20_asset");
stake(deps.as_mut(), "user1", 100, "native_asset");

let asset_info = Asset::native(Addr::unchecked("native_asset"), 50u128);
let res = unstake(deps.as_mut(), "user1",asset_info);
assert_eq!(
res,
Response::default()
.add_attributes(vec![
("action", "unstake"),
("user", "user1"),
("asset", "native:asset1"),
("asset", "native:native_asset"),
("amount", "50"),
])
.add_message(CosmosMsg::Bank(BankMsg::Send {
to_address: "user1".into(),
amount: vec![coin(50, "asset1")],
amount: vec![coin(50, "native_asset")],
}))
);

Expand All @@ -283,25 +284,26 @@ fn test_unstake() {
deps.as_ref().storage,
(
Addr::unchecked("user1"),
AssetInfoKey::from(AssetInfo::Native("asset1".to_string())),
AssetInfoKey::from(AssetInfo::native(Addr::unchecked("native_asset"))),
),
)
.unwrap();
assert_eq!(balance, Uint128::new(50));

let res = unstake(deps.as_mut(), "user1", 50, "asset1");
let asset_info = Asset::native(Addr::unchecked("native_asset"), 50u128);
let res = unstake(deps.as_mut(), "user1", asset_info);
assert_eq!(
res,
Response::default()
.add_attributes(vec![
("action", "unstake"),
("user", "user1"),
("asset", "native:asset1"),
("asset", "native:native_asset"),
("amount", "50"),
])
.add_message(CosmosMsg::Bank(BankMsg::Send {
to_address: "user1".into(),
amount: vec![coin(50, "asset1")],
amount: vec![coin(50, "native_asset")],
}))
);

Expand All @@ -310,7 +312,7 @@ fn test_unstake() {
deps.as_ref().storage,
(
Addr::unchecked("user1"),
AssetInfoKey::from(AssetInfo::Native("asset1".to_string())),
AssetInfoKey::from(AssetInfo::Native("native_asset".to_string())),
),
)
.unwrap();
Expand All @@ -319,14 +321,14 @@ fn test_unstake() {
let total_balance = TOTAL_BALANCES
.load(
deps.as_ref().storage,
AssetInfoKey::from(AssetInfo::Native("asset1".to_string())),
AssetInfoKey::from(AssetInfo::Native("native_asset".to_string())),
)
.unwrap();
assert_eq!(total_balance, Uint128::new(0));
}

#[test]
fn test_unstake_invalid() {
fn test_unstake_cw20_invalid() {
let mut deps = mock_dependencies();
setup_contract(deps.as_mut());

Expand All @@ -341,19 +343,52 @@ fn test_unstake_invalid() {

// User does not have any staked asset
let info = mock_info("user2", &[]);
let msg = ExecuteMsg::Unstake(Asset::native("cw20_asset", 100u128));
let msg = ExecuteMsg::Unstake(Asset::cw20(Addr::unchecked("cw20_asset"), 100u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::InsufficientBalance {});

// User unstakes more than they have
let info = mock_info("user1", &[]);
let msg = ExecuteMsg::Unstake(Asset::cw20(Addr::unchecked("cw20_asset"), 101u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::InsufficientBalance {});

// User unstakes zero amount
let info = mock_info("user1", &[]);
let msg = ExecuteMsg::Unstake(Asset::cw20(Addr::unchecked("cw20_asset"), 0u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::AmountCannotBeZero {});
}

#[test]
fn test_unstake_native_invalid() {
let mut deps = mock_dependencies();
setup_contract(deps.as_mut());

modify_asset(
deps.as_mut(),
vec![ModifyAsset {
asset_info: AssetInfo::native(Addr::unchecked("native_asset")),
delete: false,
}],
);
stake(deps.as_mut(), "user1", 100, "native_asset");

// User does not have any staked asset
let info = mock_info("user2", &[]);
let msg = ExecuteMsg::Unstake(Asset::native("native_asset", 100u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::InsufficientBalance {});

// User unstakes more than they have
let info = mock_info("user1", &[]);
let msg = ExecuteMsg::Unstake(Asset::native("cw20_asset", 101u128));
let msg = ExecuteMsg::Unstake(Asset::native("native_asset", 101u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::InsufficientBalance {});

// User unstakes zero amount
let info = mock_info("user1", &[]);
let msg = ExecuteMsg::Unstake(Asset::native("cw20_asset", 0u128));
let msg = ExecuteMsg::Unstake(Asset::native("native_asset", 0u128));
let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err();
assert_eq!(err, ContractError::AmountCannotBeZero {});
}

0 comments on commit d611779

Please sign in to comment.