diff --git a/bindings-test/src/multitest.rs b/bindings-test/src/multitest.rs index 13875cf7..078ae75b 100644 --- a/bindings-test/src/multitest.rs +++ b/bindings-test/src/multitest.rs @@ -652,6 +652,7 @@ impl Module for ElysModule { })?) } ElysQuery::CommitmentNumberOfCommitments {} => todo!("CommitmentNumberOfCommitments"), + ElysQuery::LeveragelpRewards { .. } => todo!(), } } @@ -1045,6 +1046,7 @@ impl Module for ElysModule { data: Some(data), }) } + ElysMsg::LeveragelpClaimRewards { .. } => todo!(), } } diff --git a/bindings/src/msg.rs b/bindings/src/msg.rs index 2b178363..c177c8a6 100644 --- a/bindings/src/msg.rs +++ b/bindings/src/msg.rs @@ -136,6 +136,11 @@ pub enum ElysMsg { amount: Int128, owner: String, }, + + LeveragelpClaimRewards { + sender: String, + id: Vec, + }, } impl ElysMsg { @@ -364,6 +369,10 @@ impl ElysMsg { } } + pub fn leveragelp_withdraw_reward(sender: String, id: Vec) -> Self { + Self::LeveragelpClaimRewards { sender, id } + } + pub fn estaking_withdraw_reward(delegator_address: String, validator_address: String) -> Self { Self::EstakingWithdrawReward { delegator_address, diff --git a/bindings/src/querier.rs b/bindings/src/querier.rs index e956359f..ce9d6611 100644 --- a/bindings/src/querier.rs +++ b/bindings/src/querier.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use std::str::FromStr; +use std::{ascii::AsciiExt, collections::HashMap}; use cosmwasm_std::{ coin, to_json_vec, Binary, Coin, ContractResult, Decimal, Int128, QuerierWrapper, QueryRequest, @@ -790,6 +790,17 @@ impl<'a> ElysQuerier<'a> { )) } + pub fn query_leverage_lp_rewards( + &self, + address: String, + ids: Vec, + ) -> StdResult { + self.querier + .query(&QueryRequest::Custom(ElysQuery::query_leverage_lp_rewards( + address, ids, + ))) + } + pub fn get_masterchef_pool_apr(&self, pool_ids: Vec) -> StdResult { let query = ElysQuery::get_masterchef_pool_apr(pool_ids); let request = QueryRequest::Custom(query); @@ -885,22 +896,54 @@ impl<'a> ElysQuerier<'a> { }; Ok(resp) } - pub fn leveragelp_query_positions_for_address( + + fn leveragelp_query_positions_for_address( &self, address: impl Into, pagination: Option, - ) -> StdResult { + ) -> StdResult { let req = QueryRequest::Custom(ElysQuery::leveragelp_query_positions_for_address( address.into(), pagination, )); - let raw_resp: LeveragelpPositionsResponseRaw = self.querier.query(&req)?; - let positions = raw_resp.positions.unwrap_or(vec![]); - Ok(LeveragelpPositionsResponse { - positions, + self.querier.query(&req) + } + + pub fn get_leveragelp_query_positions_for_address( + &self, + address: impl Into, + prev_pagination: Option, + ) -> StdResult { + let address: String = address.into(); + let raw_resp: LeveragelpPositionsResponseRaw = + self.leveragelp_query_positions_for_address(address.to_string(), prev_pagination)?; + let leverage_reward_data = + self.query_leverage_lp_rewards(address.to_string(), raw_resp.get_pools())?; + + let mut usdc = Decimal::zero(); + let mut eden = Uint128::zero(); + + for coin in leverage_reward_data.total_rewards { + if coin.denom == "uusdc".to_string() { + usdc = CoinValue::from_coin(&coin, self)?.amount_usd; + } else { + eden = coin.amount; + } + } + + Ok(LeveragelpPositionsAndRewardsResponse { + positions: raw_resp.positions.unwrap_or(vec![]), pagination: raw_resp.pagination, + usdc, + eden, }) } + pub fn leveragelp_pool_ids_for_address(&self, address: String) -> StdResult> { + let pagination = PageRequest::total(); + let raw_resp = + self.leveragelp_query_positions_for_address(address.clone(), Some(pagination.clone()))?; + Ok(raw_resp.get_pools()) + } pub fn leveragelp_get_whitelist( &self, pagination: Option, diff --git a/bindings/src/query.rs b/bindings/src/query.rs index c4937a76..fcb62d53 100644 --- a/bindings/src/query.rs +++ b/bindings/src/query.rs @@ -80,6 +80,8 @@ pub enum ElysQuery { CommitmentVestingInfo { address: String }, #[returns(CommitmentNumberOfCommitmentsResponse)] CommitmentNumberOfCommitments {}, + #[returns(GetLeverageLpRewardsResp)] + LeveragelpRewards { address: String, ids: Vec }, // Define Incentive #[returns(QueryAprResponse)] @@ -451,4 +453,7 @@ impl ElysQuery { pub fn commitment_number_of_commitments() -> Self { Self::CommitmentNumberOfCommitments {} } + pub fn query_leverage_lp_rewards(address: String, ids: Vec) -> Self { + Self::LeveragelpRewards { address, ids } + } } diff --git a/bindings/src/query_resp.rs b/bindings/src/query_resp.rs index fd4f1eae..fe09f314 100644 --- a/bindings/src/query_resp.rs +++ b/bindings/src/query_resp.rs @@ -611,12 +611,31 @@ pub struct LeveragelpPositionsResponseRaw { pub pagination: Option, } +impl LeveragelpPositionsResponseRaw { + pub fn get_pools(&self) -> Vec { + self.positions + .clone() + .unwrap_or(vec![]) + .iter() + .map(|x| x.amm_pool_id) + .collect() + } +} + #[cw_serde] pub struct LeveragelpPositionsResponse { pub positions: Vec, pub pagination: Option, } +#[cw_serde] +pub struct LeveragelpPositionsAndRewardsResponse { + pub positions: Vec, + pub pagination: Option, + pub usdc: Decimal, + pub eden: Uint128, +} + #[cw_serde] pub struct LeveragelpStatusResponse { pub open_position_count: u64, @@ -955,3 +974,25 @@ pub struct CommitmentNumberOfCommitmentsResponseRaw { pub struct CommitmentNumberOfCommitmentsResponse { pub number: i64, } + +#[cw_serde] +pub struct GetLeverageLpRewardsResp { + pub rewards: RewardInfo, + pub total_rewards: Vec, +} + +#[cw_serde] +pub struct RewardInfo { + pub position_id: u64, + pub reward: Vec, +} + +impl GetLeverageLpRewardsResp { + pub fn total_rewards_to_coin(&self, querier: &ElysQuerier<'_>) -> StdResult> { + let mut coin_values = Vec::new(); + for reward in &self.total_rewards { + coin_values.push(CoinValue::from_coin(reward, querier)?); + } + Ok(coin_values) + } +} diff --git a/bindings/src/trade_shield/msg/query_msg.rs b/bindings/src/trade_shield/msg/query_msg.rs index e9aff053..4fb9db48 100644 --- a/bindings/src/trade_shield/msg/query_msg.rs +++ b/bindings/src/trade_shield/msg/query_msg.rs @@ -101,6 +101,8 @@ pub enum QueryMsg { }, #[returns(LeveragelpPositionResponse)] LeveragelpPosition { address: String, id: u64 }, + #[returns(GetLeverageLpRewardsResp)] + LeveragelpRewards { address: String, ids: Vec }, #[returns(TradeShieldParamsResponse)] GetParams {}, #[returns(NumberOfPendingOrderResp)] diff --git a/bindings/src/types.rs b/bindings/src/types.rs index bc02dcfb..c123b386 100644 --- a/bindings/src/types.rs +++ b/bindings/src/types.rs @@ -158,6 +158,16 @@ impl PageRequest { pub fn update(&mut self, key: Option) -> () { self.key = key; } + + pub fn total() -> Self { + Self { + key: None, + offset: Some(0), + limit: 0, + count_total: true, + reverse: false, + } + } } #[cw_serde] diff --git a/contracts/trade-shield-contract/src/action/execute/claim_rewards_request.rs b/contracts/trade-shield-contract/src/action/execute/claim_rewards_request.rs index 8a133a1d..c863afbd 100644 --- a/contracts/trade-shield-contract/src/action/execute/claim_rewards_request.rs +++ b/contracts/trade-shield-contract/src/action/execute/claim_rewards_request.rs @@ -69,6 +69,14 @@ pub fn claim_rewards_request( pools_ids_to_claim, )); } + let ids = querier.leveragelp_pool_ids_for_address(info.sender.to_string())?; + + if !ids.is_empty() { + msgs.push(ElysMsg::leveragelp_withdraw_reward( + info.sender.to_string(), + ids, + )); + } let resp = Response::new().add_messages(msgs); diff --git a/contracts/trade-shield-contract/src/entry_point/query.rs b/contracts/trade-shield-contract/src/entry_point/query.rs index efa2eed1..ac4e46c9 100644 --- a/contracts/trade-shield-contract/src/entry_point/query.rs +++ b/contracts/trade-shield-contract/src/entry_point/query.rs @@ -127,11 +127,14 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Ok(to_json_binary( - &querier.leveragelp_query_positions_for_address(address, pagination)?, + &querier.get_leveragelp_query_positions_for_address(address, pagination)?, )?), LeveragelpGetWhitelist { pagination } => Ok(to_json_binary( &querier.leveragelp_get_whitelist(pagination)?, )?), + LeveragelpRewards { address, ids } => Ok(to_json_binary( + &querier.query_leverage_lp_rewards(address, ids)?, + )?), LeveragelpIsWhitelisted { address } => Ok(to_json_binary( &querier.leveragelp_is_whitelisted(address)?, )?), diff --git a/contracts/trade-shield-contract/src/tests/claim_rewards_request/claim_rewards_request.rs b/contracts/trade-shield-contract/src/tests/claim_rewards_request/claim_rewards_request.rs index 6423802c..01dfc8e4 100644 --- a/contracts/trade-shield-contract/src/tests/claim_rewards_request/claim_rewards_request.rs +++ b/contracts/trade-shield-contract/src/tests/claim_rewards_request/claim_rewards_request.rs @@ -8,6 +8,8 @@ use crate::entry_point::{execute, query}; use anyhow::{bail, Result as AnyResult}; use cosmwasm_std::coin; use cosmwasm_std::Coin; +use cosmwasm_std::Decimal; +use cosmwasm_std::Int128; use cosmwasm_std::StdError; use cosmwasm_std::Uint128; use cosmwasm_std::{to_json_binary, Addr, Empty}; @@ -19,10 +21,13 @@ use cw_storage_plus::Item; use cw_storage_plus::Map; use elys_bindings::query_resp::DelegationDelegatorReward; use elys_bindings::query_resp::EstakingRewardsResponse; +use elys_bindings::query_resp::LeveragelpPosition; +use elys_bindings::query_resp::LeveragelpPositionsResponse; use elys_bindings::query_resp::MasterchefUserPendingRewardData; use elys_bindings::query_resp::MasterchefUserPendingRewardResponse; use elys_bindings::query_resp::Validator; use elys_bindings::trade_shield::msg::ExecuteMsg; +use elys_bindings::types::PageResponse; use elys_bindings::{ElysMsg, ElysQuery}; use elys_bindings_test::ElysModule; @@ -115,6 +120,32 @@ impl Module for ElysModuleWrapper { }; Ok(to_json_binary(&resp)?) } + ElysQuery::LeveragelpQueryPositionsForAddress { .. } => { + let position = LeveragelpPosition { + address: "user".to_string(), + collateral: Coin { + denom: "uelys".to_string(), + amount: Uint128::new(100000000), + }, + liabilities: Int128::zero(), + interest_paid: Int128::zero(), + leverage: Decimal::new(Uint128::new(10)), + leveraged_lp_amount: Int128::new(10000000), + position_health: Decimal::one(), + id: 1, + amm_pool_id: 1, + stop_loss_price: Decimal::one(), + }; + + let resp = LeveragelpPositionsResponse { + positions: vec![position], + pagination: Some(PageResponse { + next_key: None, + total: Some(1), + }), + }; + Ok(to_json_binary(&resp)?) + } _ => panic!("not implemented {request:?}"), } } @@ -197,6 +228,20 @@ impl Module for ElysModuleWrapper { }), ) } + ElysMsg::LeveragelpClaimRewards { sender, .. } => router.sudo( + api, + storage, + block, + SudoMsg::Bank(BankSudo::Mint { + to_address: sender, + amount: vec![Coin { + denom: + "ibc/2180E84E20F5679FCC760D8C165B60F42065DEF7F46A72B447CFF1B7DC6C0A65" + .to_string(), + amount: Uint128::new(10000000), + }], + }), + ), _ => bail!("not implemented {msg:?}"), } } @@ -281,7 +326,7 @@ fn claim_rewards_request() { let msgs_called = app.init_modules(|_, _, storage| MSG_CALLED.load(storage).unwrap()); - assert_eq!(msgs_called.len(), 3); + assert_eq!(msgs_called.len(), 4); assert_eq!( msgs_called[0], ElysMsg::estaking_withdraw_elys_staking_rewards("user".to_string()) @@ -294,10 +339,14 @@ fn claim_rewards_request() { msgs_called[2], ElysMsg::get_masterchef_claim_rewards("user".to_string(), vec![2]) ); + assert_eq!( + msgs_called[3], + ElysMsg::leveragelp_withdraw_reward("user".to_string(), vec![1]) + ); let user_usdc_balance = app.wrap().query_balance("user", DENOM_INFO[0].0).unwrap(); let user_uelys_balance = app.wrap().query_balance("user", "uelys").unwrap(); - assert_eq!(user_usdc_balance, coin(500, DENOM_INFO[0].0)); + assert_eq!(user_usdc_balance, coin(10000500, DENOM_INFO[0].0)); assert_eq!(user_uelys_balance, coin(24100000, "uelys")); }