From c13ef3e51c9cba94e1c3e6ff556675d2c920155f Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 25 Jan 2024 12:14:34 +0200 Subject: [PATCH] feat: claim astro rewards --- contracts/alliance-lp-hub/src/contract.rs | 200 +++++++++++++----- contracts/alliance-lp-hub/src/models.rs | 12 +- contracts/alliance-lp-hub/src/query.rs | 53 +++-- contracts/alliance-lp-hub/src/state.rs | 6 +- .../alliance-lp-hub/src/tests/helpers.rs | 7 +- .../alliance-lp-hub/src/tests/instantiate.rs | 2 + .../alliance-lp-hub/src/tests/rewards.rs | 19 +- 7 files changed, 225 insertions(+), 74 deletions(-) diff --git a/contracts/alliance-lp-hub/src/contract.rs b/contracts/alliance-lp-hub/src/contract.rs index bf253d5..33cf454 100644 --- a/contracts/alliance-lp-hub/src/contract.rs +++ b/contracts/alliance-lp-hub/src/contract.rs @@ -56,10 +56,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 astro_incentives_addr = deps.api.addr_validate(msg.astro_incentives_addr.as_str())?; + let fee_collector_addr = deps.api.addr_validate(msg.fee_collector_addr.as_str())?; let create_msg = TokenExecuteMsg::CreateDenom { subdenom: "ualliancelp".to_string(), }; @@ -70,11 +68,14 @@ pub fn instantiate( let config = Config { governance: governance_address, controller: controller_address, - fee_collector_addr: fee_collector_address, - astro_incentives_addr: astro_incentives_address, + fee_collector_addr: fee_collector_addr, + + astro_incentives_addr: astro_incentives_addr, + astro_reward_denom: msg.astro_reward_denom, + alliance_token_denom: "".to_string(), alliance_token_supply: Uint128::zero(), - alliance_reward_denom: msg.reward_denom, + alliance_reward_denom: msg.alliance_reward_denom, }; CONFIG.save(deps.storage, &config)?; @@ -183,22 +184,26 @@ fn stake( sender: Addr, received_asset: Asset, ) -> Result { - let asset_key = AssetInfoKey::from(&received_asset.info); + let deposit_asset_key = AssetInfoKey::from(&received_asset.info); WHITELIST - .load(deps.storage, asset_key.clone()) + .load(deps.storage, deposit_asset_key.clone()) .map_err(|_| ContractError::AssetNotWhitelisted(received_asset.info.to_string()))?; let config = CONFIG.load(deps.storage)?; - let reward_token = AssetInfoKey::from(AssetInfo::Native(config.alliance_reward_denom)); + let reward_asset_key = AssetInfoKey::from(AssetInfo::Native(config.alliance_reward_denom)); let rewards = _claim_alliance_rewards( deps.storage, sender.clone(), - received_asset.info.clone(), - reward_token.clone(), + AssetInfoKey::from(received_asset.info.clone()), + reward_asset_key.clone(), )?; if !rewards.is_zero() { UNCLAIMED_REWARDS.update( deps.storage, - (sender.clone(), asset_key.clone()), + ( + sender.clone(), + deposit_asset_key.clone(), + reward_asset_key.clone(), + ), |balance| -> Result<_, ContractError> { let mut unclaimed_rewards = balance.unwrap_or_default(); unclaimed_rewards += rewards; @@ -268,7 +273,7 @@ fn stake( BALANCES.update( deps.storage, - (sender.clone(), asset_key.clone()), + (sender.clone(), deposit_asset_key.clone()), |balance| -> Result<_, ContractError> { match balance { Some(balance) => Ok(balance + received_asset.amount), @@ -278,41 +283,48 @@ fn stake( )?; TOTAL_BALANCES.update( deps.storage, - asset_key.clone(), + deposit_asset_key.clone(), |balance| -> Result<_, ContractError> { Ok(balance.unwrap_or(Uint128::zero()) + received_asset.amount) }, )?; let asset_reward_rate = ASSET_REWARD_RATE - .load(deps.storage, (asset_key.clone(), reward_token.clone())) + .load( + deps.storage, + (deposit_asset_key.clone(), reward_asset_key.clone()), + ) .unwrap_or(Decimal::zero()); USER_ASSET_REWARD_RATE.save( deps.storage, - (sender, asset_key, reward_token), + (sender, deposit_asset_key, reward_asset_key), &asset_reward_rate, )?; Ok(res) } fn unstake(deps: DepsMut, info: MessageInfo, asset: Asset) -> Result { - let asset_key = AssetInfoKey::from(asset.info.clone()); + let deposit_asset_key = AssetInfoKey::from(asset.info.clone()); let sender = info.sender.clone(); if asset.amount.is_zero() { return Err(ContractError::AmountCannotBeZero {}); } let config = CONFIG.load(deps.storage)?; - let reward_token = AssetInfoKey::from(AssetInfo::Native(config.alliance_token_denom)); + let reward_asset_token = AssetInfoKey::from(AssetInfo::Native(config.alliance_token_denom)); let rewards = _claim_alliance_rewards( deps.storage, sender.clone(), - asset.info.clone(), - reward_token, + AssetInfoKey::from(asset.info.clone()), + reward_asset_token.clone(), )?; if !rewards.is_zero() { UNCLAIMED_REWARDS.update( deps.storage, - (sender.clone(), asset_key.clone()), + ( + sender.clone(), + deposit_asset_key.clone(), + reward_asset_token, + ), |balance| -> Result<_, ContractError> { let mut unclaimed_rewards = balance.unwrap_or_default(); unclaimed_rewards += rewards; @@ -323,7 +335,7 @@ fn unstake(deps: DepsMut, info: MessageInfo, asset: Asset) -> Result Result<_, ContractError> { match balance { Some(balance) => { @@ -338,7 +350,7 @@ fn unstake(deps: DepsMut, info: MessageInfo, asset: Asset) -> Result Result<_, ContractError> { let balance = balance.unwrap_or(Uint128::zero()); if balance < asset.amount { @@ -363,56 +375,117 @@ fn unstake(deps: DepsMut, info: MessageInfo, asset: Asset) -> Result Result { let user = info.sender; let config = CONFIG.load(deps.storage)?; - let reward_token = AssetInfoKey::from(AssetInfo::Native(config.alliance_reward_denom.clone())); - let rewards = _claim_alliance_rewards(deps.storage, user.clone(), asset.clone(), reward_token)?; + let mut res = Response::new().add_attribute("action", "claim_rewards"); + + // Claim alliance rewards, add the rewards to the response, + // create the transfer msg and send the tokens to the user. + let alliance_reward_token_key = + AssetInfoKey::from(AssetInfo::Native(config.alliance_reward_denom.clone())); + let alliance_rewards = _claim_alliance_rewards( + deps.storage, + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + alliance_reward_token_key.clone(), + )?; let unclaimed_rewards = UNCLAIMED_REWARDS .load( deps.storage, - (user.clone(), AssetInfoKey::from(asset.clone())), + ( + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + alliance_reward_token_key.clone(), + ), ) .unwrap_or_default(); - let final_rewards = rewards + unclaimed_rewards; + let final_alliance_rewards = alliance_rewards + unclaimed_rewards; UNCLAIMED_REWARDS.remove( deps.storage, - (user.clone(), AssetInfoKey::from(asset.clone())), + ( + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + alliance_reward_token_key.clone(), + ), ); - let response = Response::new().add_attributes(vec![ - ("action", "claim_rewards"), + + res = res.add_attributes(vec![ ("user", user.as_ref()), - ("asset", &asset.to_string()), - ("reward_amount", &final_rewards.to_string()), + ("asset", &deposit_asset.to_string()), + ( + "alliance_reward_amount", + &final_alliance_rewards.to_string(), + ), ]); - if !final_rewards.is_zero() { + if !final_alliance_rewards.is_zero() { let rewards_asset = Asset { info: AssetInfo::Native(config.alliance_reward_denom), - amount: final_rewards, + amount: final_alliance_rewards, }; - Ok(response.add_message(rewards_asset.transfer_msg(&user)?)) - } else { - Ok(response) + res = res.add_message(rewards_asset.transfer_msg(&user)?) + } + + let astro_reward_token = + AssetInfoKey::from(AssetInfo::Native(config.astro_reward_denom.clone())); + let astro_rewards = _claim_astro_rewards( + deps.storage, + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + astro_reward_token.clone(), + )?; + let unclaimed_rewards = UNCLAIMED_REWARDS + .load( + deps.storage, + ( + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + astro_reward_token.clone(), + ), + ) + .unwrap_or_default(); + let final_astro_rewards = astro_rewards + unclaimed_rewards; + UNCLAIMED_REWARDS.remove( + deps.storage, + ( + user.clone(), + AssetInfoKey::from(deposit_asset.clone()), + astro_reward_token, + ), + ); + res = res.add_attribute("astro_reward_amount", &final_astro_rewards.to_string()); + if !final_astro_rewards.is_zero() { + let info = match deps.api.addr_validate(&config.astro_reward_denom) { + Ok(addr) => AssetInfo::Cw20(addr), + Err(_) => AssetInfo::Native(config.astro_reward_denom.clone()), + }; + + let rewards_asset = Asset { + info: info, + amount: final_astro_rewards, + }; + res = res.add_message(rewards_asset.transfer_msg(&user)?) } + + Ok(res) } fn _claim_alliance_rewards( storage: &mut dyn Storage, user: Addr, - staked_asset: AssetInfo, + staked_asset: AssetInfoKey, reward_denom: AssetInfoKey, ) -> Result { - let asset_key = AssetInfoKey::from(&staked_asset); - let state_key = (user.clone(), asset_key.clone(), reward_denom.clone()); + let state_key = (user.clone(), staked_asset.clone(), reward_denom.clone()); let user_reward_rate = USER_ASSET_REWARD_RATE.load(storage, state_key.clone()); let asset_reward_rate = ASSET_REWARD_RATE - .load(storage, (asset_key.clone(), reward_denom.clone())) + .load(storage, (staked_asset.clone(), reward_denom.clone())) .unwrap_or_default(); if let Ok(user_reward_rate) = user_reward_rate { let user_staked = BALANCES - .load(storage, (user.clone(), asset_key.clone())) + .load(storage, (user.clone(), staked_asset.clone())) .unwrap_or_default(); let user_staked = Decimal::from_atomics(user_staked, 0)?; let rewards = ((asset_reward_rate - user_reward_rate) * user_staked).to_uint_floor(); @@ -429,6 +502,38 @@ fn _claim_alliance_rewards( } } +fn _claim_astro_rewards( + storage: &mut dyn Storage, + user: Addr, + staked_asset: AssetInfoKey, + reward_denom: AssetInfoKey, +) -> Result { + let state_key: (Addr, AssetInfoKey, AssetInfoKey) = + (user.clone(), staked_asset.clone(), reward_denom.clone()); + let user_reward_rate = USER_ASSET_REWARD_RATE.load(storage, state_key.clone()); + let asset_reward_rate = ASSET_REWARD_RATE + .load(storage, (staked_asset.clone(), reward_denom.clone())) + .unwrap_or_default(); + + if let Ok(user_reward_rate) = user_reward_rate { + let total_staked = TOTAL_BALANCES + .load(storage, staked_asset.clone()) + .unwrap_or_default(); + let user_staked = Decimal::from_atomics(total_staked, 0)?; + let rewards = ((asset_reward_rate - user_reward_rate) * user_staked).to_uint_floor(); + + if rewards.is_zero() { + Ok(Uint128::zero()) + } else { + USER_ASSET_REWARD_RATE.save(storage, state_key, &asset_reward_rate)?; + Ok(rewards) + } + } else { + USER_ASSET_REWARD_RATE.save(storage, state_key, &asset_reward_rate)?; + Ok(Uint128::zero()) + } +} + fn alliance_delegate( deps: DepsMut, env: Env, @@ -545,7 +650,7 @@ fn _update_astro_rewards( let asset_info = asset_info.check(deps.api, None)?; let asset_string = asset_info.to_string(); let asset_denom = asset_string.split(":").collect::>()[1].to_string(); - + whitelist.push(asset_denom); } @@ -861,12 +966,11 @@ fn reply_claim_astro_rewards( let coin = CwCoin::from_str(&attr.value)?; astro_claim.rewards.add(coin)?; } - - if attr.key == "claimed_reward" && !count_claim_position { + + if attr.key == "claimed_reward" && !count_claim_position { astro_claims.push(astro_claim.clone()); count_claim_position = true } - } // Given the claimed rewards account them to the state of the contract diff --git a/contracts/alliance-lp-hub/src/models.rs b/contracts/alliance-lp-hub/src/models.rs index 8a3ceab..08a7658 100644 --- a/contracts/alliance-lp-hub/src/models.rs +++ b/contracts/alliance-lp-hub/src/models.rs @@ -17,7 +17,10 @@ pub struct Config { pub governance: Addr, pub controller: Addr, pub fee_collector_addr: Addr, + + pub astro_reward_denom: String, pub astro_incentives_addr: Addr, + pub alliance_token_denom: String, pub alliance_token_supply: Uint128, pub alliance_reward_denom: String, @@ -27,9 +30,12 @@ pub struct Config { pub struct InstantiateMsg { pub governance: String, pub controller: String, - pub fee_collector_address: String, - pub astro_incentives_address: String, - pub reward_denom: String, + pub fee_collector_addr: String, + + pub astro_reward_denom: String, + pub astro_incentives_addr: String, + + pub alliance_reward_denom: String, } #[cw_serde] diff --git a/contracts/alliance-lp-hub/src/query.rs b/contracts/alliance-lp-hub/src/query.rs index 1a4eb05..acb0b4d 100644 --- a/contracts/alliance-lp-hub/src/query.rs +++ b/contracts/alliance-lp-hub/src/query.rs @@ -93,9 +93,9 @@ fn get_pending_rewards(deps: Deps, asset_query: AssetQuery) -> StdResult let asset_reward_rate = ASSET_REWARD_RATE.load(deps.storage, (deposit_asset.clone(), reward_asset.clone()))?; - let user_balance = BALANCES.load(deps.storage, (addr.clone(), deposit_asset).clone())?; + let user_balance = BALANCES.load(deps.storage, (addr.clone(), deposit_asset.clone()))?; let unclaimed_rewards = UNCLAIMED_REWARDS - .load(deps.storage, (addr, reward_asset)) + .load(deps.storage, (addr, deposit_asset, reward_asset)) .unwrap_or_default(); let pending_rewards = (asset_reward_rate - user_reward_rate) * user_balance; @@ -141,23 +141,48 @@ fn get_all_pending_rewards(deps: Deps, query: AllPendingRewardsQuery) -> StdResu .map(|item| { let (assets, user_reward_rate) = item?; - let deposit_asset = AssetInfoKey::from(assets.0.check(deps.api, None)?); - let reward_asset = AssetInfoKey::from(assets.1.check(deps.api, None)?); + let deposit_asset_info = assets.0.check(deps.api, None)?; + let reward_asset_info = assets.1.check(deps.api, None)?; + let deposit_asset = AssetInfoKey::from(deposit_asset_info.clone()); + let reward_asset = AssetInfoKey::from(reward_asset_info.clone()); let asset_reward_rate = ASSET_REWARD_RATE - .load(deps.storage, (deposit_asset.clone(), reward_asset.clone()))?; - let user_balance = - BALANCES.load(deps.storage, (addr.clone(), deposit_asset.clone()))?; + .load(deps.storage, (deposit_asset.clone(), reward_asset.clone())) + .unwrap_or_default(); let unclaimed_rewards = UNCLAIMED_REWARDS - .load(deps.storage, (addr.clone(), deposit_asset)) + .load( + deps.storage, + (addr.clone(), deposit_asset.clone(), reward_asset), + ) .unwrap_or_default(); - let alliance_pending_rewards = (asset_reward_rate - user_reward_rate) * user_balance; - Ok(PendingRewardsRes { - rewards: alliance_pending_rewards + unclaimed_rewards, - deposit_asset: assets.0.check(deps.api, None)?, - reward_asset: AssetInfo::Native(config.alliance_reward_denom.to_string()), - }) + if AssetInfo::Native(config.alliance_reward_denom.to_string()).eq(&reward_asset_info) { + let user_balance = BALANCES + .load(deps.storage, (addr.clone(), deposit_asset.clone())) + .unwrap_or_default(); + + let alliance_pending_rewards = + (asset_reward_rate - user_reward_rate) * user_balance; + + Ok(PendingRewardsRes { + rewards: alliance_pending_rewards + unclaimed_rewards, + deposit_asset: deposit_asset_info, + reward_asset: reward_asset_info, + }) + } else { + let total_balances = TOTAL_BALANCES + .load(deps.storage, deposit_asset.clone()) + .unwrap_or_default(); + + let alliance_pending_rewards = + (asset_reward_rate - user_reward_rate) * total_balances; + + Ok(PendingRewardsRes { + rewards: alliance_pending_rewards + unclaimed_rewards, + deposit_asset: deposit_asset_info, + reward_asset: reward_asset_info, + }) + } }) .collect::>>(); diff --git a/contracts/alliance-lp-hub/src/state.rs b/contracts/alliance-lp-hub/src/state.rs index 0915cdd..1b84c29 100644 --- a/contracts/alliance-lp-hub/src/state.rs +++ b/contracts/alliance-lp-hub/src/state.rs @@ -30,7 +30,11 @@ pub const USER_ASSET_REWARD_RATE: Map<(Addr, AssetInfoKey, AssetInfoKey), Decima Map::new("user_asset_reward_rate"); // The following map is used to keep track of the unclaimed rewards -pub const UNCLAIMED_REWARDS: Map<(Addr, AssetInfoKey), Uint128> = Map::new("unclaimed_rewards"); +// - Addr: is the address of the user, +// - AssetInfoKey: is the asset that is being deposited, +// - AssetInfoKey: is the asset that is being rewarded, +// - Decimal: is the reward rate, +pub const UNCLAIMED_REWARDS: Map<(Addr, AssetInfoKey, AssetInfoKey), Uint128> = Map::new("unclaimed_rewards"); pub const TEMP_BALANCE: Map = Map::new("temp_balance"); diff --git a/contracts/alliance-lp-hub/src/tests/helpers.rs b/contracts/alliance-lp-hub/src/tests/helpers.rs index 42e5e02..871f9f9 100644 --- a/contracts/alliance-lp-hub/src/tests/helpers.rs +++ b/contracts/alliance-lp-hub/src/tests/helpers.rs @@ -24,10 +24,11 @@ pub fn setup_contract(deps: DepsMut) -> Response { let init_msg = InstantiateMsg { governance: "gov".to_string(), - fee_collector_address: "collector_address".to_string(), - astro_incentives_address: "astro_incentives".to_string(), + fee_collector_addr: "collector_address".to_string(), + astro_incentives_addr: "astro_incentives".to_string(), + astro_reward_denom: "astro_reward_denom".to_string(), controller: "controller".to_string(), - reward_denom: "uluna".to_string(), + alliance_reward_denom: "uluna".to_string(), }; instantiate(deps, env, info, init_msg).unwrap() } diff --git a/contracts/alliance-lp-hub/src/tests/instantiate.rs b/contracts/alliance-lp-hub/src/tests/instantiate.rs index e6a83ea..4f23975 100644 --- a/contracts/alliance-lp-hub/src/tests/instantiate.rs +++ b/contracts/alliance-lp-hub/src/tests/instantiate.rs @@ -39,6 +39,7 @@ fn test_setup_contract() { controller: Addr::unchecked("controller"), fee_collector_addr: Addr::unchecked("collector_address"), astro_incentives_addr: Addr::unchecked("astro_incentives"), + astro_reward_denom: "astro_reward_denom".to_string(), alliance_reward_denom: "uluna".to_string(), alliance_token_denom: "".to_string(), alliance_token_supply: Uint128::new(0), @@ -111,6 +112,7 @@ fn test_reply_create_token() { controller: Addr::unchecked("controller"), fee_collector_addr: Addr::unchecked("collector_address"), astro_incentives_addr: Addr::unchecked("astro_incentives"), + astro_reward_denom: "astro_reward_denom".to_string(), alliance_reward_denom: "uluna".to_string(), alliance_token_denom: "factory/cosmos2contract/ualliancelp".to_string(), alliance_token_supply: Uint128::new(1000000000000), diff --git a/contracts/alliance-lp-hub/src/tests/rewards.rs b/contracts/alliance-lp-hub/src/tests/rewards.rs index af6413d..ba79785 100644 --- a/contracts/alliance-lp-hub/src/tests/rewards.rs +++ b/contracts/alliance-lp-hub/src/tests/rewards.rs @@ -386,7 +386,8 @@ fn claim_user_rewards() { ("action", "claim_rewards"), ("user", "user1"), ("asset", "native:aWHALE"), - ("reward_amount", "100000"), + ("alliance_reward_amount", "100000"), + ("astro_reward_amount", "0"), ]) .add_message(CosmosMsg::Bank(BankMsg::Send { to_address: "user1".to_string(), @@ -429,6 +430,10 @@ fn claim_user_rewards() { assert_eq!( all_rewards, vec![PendingRewardsRes { + rewards: Uint128::zero(), + deposit_asset: AssetInfo::Native("aWHALE".to_string()), + reward_asset: AssetInfo::Native("astro_reward_denom".to_string()), + },PendingRewardsRes { rewards: Uint128::zero(), deposit_asset: AssetInfo::Native("aWHALE".to_string()), reward_asset: AssetInfo::Native("uluna".to_string()), @@ -442,7 +447,8 @@ fn claim_user_rewards() { ("action", "claim_rewards"), ("user", "user1"), ("asset", "native:aWHALE"), - ("reward_amount", "0"), + ("alliance_reward_amount", "0"), + ("astro_reward_amount", "0"), ]) ); @@ -471,7 +477,8 @@ fn claim_user_rewards() { ("action", "claim_rewards"), ("user", "user1"), ("asset", "native:aWHALE"), - ("reward_amount", "10000"), + ("alliance_reward_amount", "10000"), + ("astro_reward_amount", "0"), ]) .add_message(CosmosMsg::Bank(BankMsg::Send { to_address: "user1".to_string(), @@ -537,7 +544,8 @@ fn claim_user_rewards_after_staking() { ("action", "claim_rewards"), ("user", "user1"), ("asset", "native:aWHALE"), - ("reward_amount", "100000"), + ("alliance_reward_amount", "100000"), + ("astro_reward_amount", "0"), ]) .add_message(CosmosMsg::Bank(BankMsg::Send { to_address: "user1".to_string(), @@ -553,7 +561,8 @@ fn claim_user_rewards_after_staking() { ("action", "claim_rewards"), ("user", "user1"), ("asset", "native:aWHALE"), - ("reward_amount", "0"), + ("alliance_reward_amount", "0"), + ("astro_reward_amount", "0"), ]) ); }