diff --git a/contracts/incentives/src/contract.rs b/contracts/incentives/src/contract.rs index 329fc644f..f4d2a991a 100644 --- a/contracts/incentives/src/contract.rs +++ b/contracts/incentives/src/contract.rs @@ -1,10 +1,11 @@ +use std::collections::HashMap; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coins, to_binary, Addr, BankMsg, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Order, Response, StdResult, Uint128, + attr, coins, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + Event, MessageInfo, Order, Response, StdResult, Uint128, }; -use cw_storage_plus::Bound; use mars_owner::{OwnerInit::SetInitialOwner, OwnerUpdate}; use mars_red_bank_types::{ address_provider::{self, MarsAddressType}, @@ -21,16 +22,14 @@ use crate::{ error::ContractError, helpers::{ compute_user_accrued_rewards, compute_user_unclaimed_rewards, update_asset_incentive_index, + UserAssetIncentiveStatus, }, - state::{ASSET_INCENTIVES, CONFIG, OWNER, USER_ASSET_INDICES, USER_UNCLAIMED_REWARDS}, + state::{self, ASSET_INCENTIVES, CONFIG, OWNER, USER_ASSET_INDICES, USER_UNCLAIMED_REWARDS}, }; pub const CONTRACT_NAME: &str = "crates.io:mars-incentives"; pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const DEFAULT_LIMIT: u32 = 5; -const MAX_LIMIT: u32 = 10; - // INIT #[cfg_attr(not(feature = "library"), entry_point)] @@ -71,7 +70,8 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::SetAssetIncentive { - denom, + collateral_denom, + incentive_denom, emission_per_second, start_time, duration, @@ -79,7 +79,8 @@ pub fn execute( deps, env, info, - denom, + collateral_denom, + incentive_denom, emission_per_second, start_time, duration, @@ -98,7 +99,18 @@ pub fn execute( user_amount_scaled_before, total_amount_scaled_before, ), - ExecuteMsg::ClaimRewards {} => execute_claim_rewards(deps, env, info), + ExecuteMsg::ClaimRewards { + start_after_collateral_denom, + start_after_incentive_denom, + limit, + } => execute_claim_rewards( + deps, + env, + info, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + ), ExecuteMsg::UpdateConfig { address_provider, mars_denom, @@ -111,17 +123,40 @@ pub fn execute_set_asset_incentive( deps: DepsMut, env: Env, info: MessageInfo, - denom: String, + collateral_denom: String, + incentive_denom: String, emission_per_second: Option, start_time: Option, duration: Option, ) -> Result { OWNER.assert_owner(deps.storage, &info.sender)?; - validate_native_denom(&denom)?; + validate_native_denom(&collateral_denom)?; + validate_native_denom(&incentive_denom)?; + + // Query Red Bank to check if market exists + let config = CONFIG.load(deps.storage)?; + let red_bank_addr = address_provider::helpers::query_contract_addr( + deps.as_ref(), + &config.address_provider, + MarsAddressType::RedBank, + )?; + let market: red_bank::Market = deps + .querier + .query_wasm_smart( + &red_bank_addr, + &red_bank::QueryMsg::Market { + denom: collateral_denom.to_string(), + }, + ) + .map_err(|_| ContractError::InvalidIncentive { + reason: "Market does not exist on Red Bank".to_string(), + })?; let current_block_time = env.block.time.seconds(); - let new_asset_incentive = match ASSET_INCENTIVES.may_load(deps.storage, &denom)? { + let new_asset_incentive = match ASSET_INCENTIVES + .may_load(deps.storage, (&collateral_denom, &incentive_denom))? + { Some(mut asset_incentive) => { let (start_time, duration, emission_per_second) = validate_params_for_existing_incentive( @@ -132,21 +167,6 @@ pub fn execute_set_asset_incentive( current_block_time, )?; - let config = CONFIG.load(deps.storage)?; - - let red_bank_addr = address_provider::helpers::query_contract_addr( - deps.as_ref(), - &config.address_provider, - MarsAddressType::RedBank, - )?; - - let market: red_bank::Market = deps.querier.query_wasm_smart( - red_bank_addr, - &red_bank::QueryMsg::Market { - denom: denom.clone(), - }, - )?; - // Update index up to now update_asset_incentive_index( &mut asset_incentive, @@ -179,11 +199,16 @@ pub fn execute_set_asset_incentive( } }; - ASSET_INCENTIVES.save(deps.storage, &denom, &new_asset_incentive)?; + ASSET_INCENTIVES.save( + deps.storage, + (&collateral_denom, &incentive_denom), + &new_asset_incentive, + )?; let response = Response::new().add_attributes(vec![ attr("action", "set_asset_incentive"), - attr("denom", denom), + attr("collateral_denom", collateral_denom), + attr("incentive_denom", incentive_denom), attr("emission_per_second", new_asset_incentive.emission_per_second), attr("start_time", new_asset_incentive.start_time.to_string()), attr("duration", new_asset_incentive.duration.to_string()), @@ -286,7 +311,7 @@ pub fn execute_balance_change( env: Env, info: MessageInfo, user_addr: Addr, - denom: String, + collateral_denom: String, user_amount_scaled_before: Uint128, total_amount_scaled_before: Uint128, ) -> Result { @@ -296,116 +321,155 @@ pub fn execute_balance_change( return Err(MarsError::Unauthorized {}.into()); } - let mut asset_incentive = match ASSET_INCENTIVES.may_load(deps.storage, &denom)? { - // If there are no incentives, - // an empty successful response is returned as the - // success of the call is needed for the call that triggered the change to - // succeed and be persisted to state. - None => return Ok(Response::default()), - - Some(ai) => ai, - }; + let base_event = Event::new("mars/incentives/balance_change") + .add_attribute("action", "balance_change") + .add_attribute("denom", collateral_denom.clone()) + .add_attribute("user", user_addr.to_string()); + let mut events = vec![base_event]; - update_asset_incentive_index( - &mut asset_incentive, - total_amount_scaled_before, - env.block.time.seconds(), - )?; - ASSET_INCENTIVES.save(deps.storage, &denom, &asset_incentive)?; + let asset_incentives = ASSET_INCENTIVES + .prefix(&collateral_denom) + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; - // Check if user has accumulated uncomputed rewards (which means index is not up to date) - let user_asset_index_key = USER_ASSET_INDICES.key((&user_addr, &denom)); + for (incentive_denom, mut asset_incentive) in asset_incentives { + update_asset_incentive_index( + &mut asset_incentive, + total_amount_scaled_before, + env.block.time.seconds(), + )?; + ASSET_INCENTIVES.save( + deps.storage, + (&collateral_denom, &incentive_denom.clone()), + &asset_incentive, + )?; - let user_asset_index = - user_asset_index_key.may_load(deps.storage)?.unwrap_or_else(Decimal::zero); + // Check if user has accumulated uncomputed rewards (which means index is not up to date) + let user_asset_index_key = + USER_ASSET_INDICES.key((&user_addr, &collateral_denom, &incentive_denom)); - let mut accrued_rewards = Uint128::zero(); + let user_asset_index = + user_asset_index_key.may_load(deps.storage)?.unwrap_or_else(Decimal::zero); - if user_asset_index != asset_incentive.index { - // Compute user accrued rewards and update state - accrued_rewards = compute_user_accrued_rewards( - user_amount_scaled_before, - user_asset_index, - asset_incentive.index, - )?; + let mut accrued_rewards = Uint128::zero(); - // Store user accrued rewards as unclaimed - if !accrued_rewards.is_zero() { - USER_UNCLAIMED_REWARDS.update( - deps.storage, - &user_addr, - |ur: Option| -> StdResult { - match ur { - Some(unclaimed_rewards) => Ok(unclaimed_rewards + accrued_rewards), - None => Ok(accrued_rewards), - } - }, + if user_asset_index != asset_incentive.index { + // Compute user accrued rewards and update state + accrued_rewards = compute_user_accrued_rewards( + user_amount_scaled_before, + user_asset_index, + asset_incentive.index, )?; + + // Store user accrued rewards as unclaimed + if !accrued_rewards.is_zero() { + state::increase_unclaimed_rewards( + deps.storage, + &user_addr, + &collateral_denom, + &incentive_denom, + accrued_rewards, + )?; + } + + user_asset_index_key.save(deps.storage, &asset_incentive.index)?; } - user_asset_index_key.save(deps.storage, &asset_incentive.index)?; + events.push( + Event::new("mars/incentives/balance_change/reward_accrued") + .add_attribute("incentive_denom", incentive_denom) + .add_attribute("rewards_accrued", accrued_rewards) + .add_attribute("asset_index", asset_incentive.index.to_string()), + ); } - let response = Response::new().add_attributes(vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", user_addr), - attr("rewards_accrued", accrued_rewards), - attr("asset_index", asset_incentive.index.to_string()), - ]); - - Ok(response) + Ok(Response::new().add_events(events)) } pub fn execute_claim_rewards( deps: DepsMut, env: Env, info: MessageInfo, + start_after_collateral_denom: Option, + start_after_incentive_denom: Option, + limit: Option, ) -> Result { let red_bank_addr = query_red_bank_address(deps.as_ref())?; let user_addr = info.sender; - let (total_unclaimed_rewards, user_asset_incentive_statuses_to_update) = - compute_user_unclaimed_rewards(deps.as_ref(), &env.block, &red_bank_addr, &user_addr)?; - // Commit updated asset_incentives and user indexes - for user_asset_incentive_status in user_asset_incentive_statuses_to_update { - let asset_incentive_updated = user_asset_incentive_status.asset_incentive_updated; + let mut response = Response::new(); + let base_event = Event::new("mars/incentives/claim_rewards") + .add_attribute("action", "claim_rewards") + .add_attribute("user", user_addr.to_string()); + let mut events = vec![base_event]; - ASSET_INCENTIVES.save( - deps.storage, - &user_asset_incentive_status.denom, - &asset_incentive_updated, - )?; + let asset_incentives = state::paginate_asset_incentives( + deps.storage, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + )?; + + let mut total_unclaimed_rewards: HashMap = HashMap::new(); + + for ((collateral_denom, incentive_denom), _) in asset_incentives { + let (unclaimed_rewards, user_asset_incentive_statuses_to_update) = + compute_user_unclaimed_rewards( + deps.as_ref(), + &env.block, + &red_bank_addr, + &user_addr, + &collateral_denom, + &incentive_denom, + )?; - if asset_incentive_updated.index != user_asset_incentive_status.user_index_current { - USER_ASSET_INDICES.save( + // Commit updated asset_incentives and user indexes + if let Some(UserAssetIncentiveStatus { + user_index_current, + asset_incentive_updated, + }) = user_asset_incentive_statuses_to_update + { + ASSET_INCENTIVES.save( deps.storage, - (&user_addr, &user_asset_incentive_status.denom), - &asset_incentive_updated.index, - )? + (&collateral_denom.clone(), &incentive_denom), + &asset_incentive_updated, + )?; + + if asset_incentive_updated.index != user_index_current { + USER_ASSET_INDICES.save( + deps.storage, + (&user_addr, &collateral_denom, &incentive_denom), + &asset_incentive_updated.index, + )? + } } - } - // clear unclaimed rewards - USER_UNCLAIMED_REWARDS.save(deps.storage, &user_addr, &Uint128::zero())?; + // clear unclaimed rewards + USER_UNCLAIMED_REWARDS.save( + deps.storage, + (&user_addr, &collateral_denom, &incentive_denom), + &Uint128::zero(), + )?; - let mut response = Response::new(); - if !total_unclaimed_rewards.is_zero() { - let config = CONFIG.load(deps.storage)?; - // Build message to send mars to the user + total_unclaimed_rewards + .entry(incentive_denom) + .and_modify(|amount| *amount += unclaimed_rewards) + .or_insert(unclaimed_rewards); + } + + for (denom, amount) in total_unclaimed_rewards.iter() { response = response.add_message(CosmosMsg::Bank(BankMsg::Send { to_address: user_addr.to_string(), - amount: coins(total_unclaimed_rewards.u128(), config.mars_denom), + amount: coins(amount.u128(), denom), })); - }; - - response = response.add_attributes(vec![ - attr("action", "claim_rewards"), - attr("user", user_addr), - attr("mars_rewards", total_unclaimed_rewards), - ]); + events.push( + Event::new("mars/incentives/claim_rewards/claimed_reward") + .add_attribute("denom", denom) + .add_attribute("amount", *amount), + ); + } - Ok(response) + Ok(response.add_events(events)) } pub fn execute_update_config( @@ -449,15 +513,32 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Config {} => to_binary(&query_config(deps)?), QueryMsg::AssetIncentive { - denom, - } => to_binary(&query_asset_incentive(deps, denom)?), + collateral_denom, + incentive_denom, + } => to_binary(&query_asset_incentive(deps, collateral_denom, incentive_denom)?), QueryMsg::AssetIncentives { - start_after, + start_after_collateral_denom, + start_after_incentive_denom, limit, - } => to_binary(&query_asset_incentives(deps, start_after, limit)?), + } => to_binary(&query_asset_incentives( + deps, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + )?), QueryMsg::UserUnclaimedRewards { user, - } => to_binary(&query_user_unclaimed_rewards(deps, env, user)?), + start_after_collateral_denom, + start_after_incentive_denom, + limit, + } => to_binary(&query_user_unclaimed_rewards( + deps, + env, + user, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + )?), } } @@ -472,36 +553,79 @@ pub fn query_config(deps: Deps) -> StdResult { }) } -pub fn query_asset_incentive(deps: Deps, denom: String) -> StdResult { - let asset_incentive = ASSET_INCENTIVES.load(deps.storage, &denom)?; - Ok(AssetIncentiveResponse::from(denom, asset_incentive)) +pub fn query_asset_incentive( + deps: Deps, + collateral_denom: String, + incentive_denom: String, +) -> StdResult { + let asset_incentive = + ASSET_INCENTIVES.load(deps.storage, (&collateral_denom, &incentive_denom))?; + Ok(AssetIncentiveResponse::from(collateral_denom, incentive_denom, asset_incentive)) } pub fn query_asset_incentives( deps: Deps, - start_after: Option, + start_after_collateral_denom: Option, + start_after_incentive_denom: Option, limit: Option, ) -> StdResult> { - let start = start_after.map(|denom| Bound::ExclusiveRaw(denom.into_bytes())); - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - ASSET_INCENTIVES - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let (denom, ai) = item?; - Ok(AssetIncentiveResponse::from(denom, ai)) + let asset_incentives = state::paginate_asset_incentives( + deps.storage, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + )?; + + asset_incentives + .into_iter() + .map(|((collateral_denom, incentive_denom), ai)| { + Ok(AssetIncentiveResponse::from(collateral_denom, incentive_denom, ai)) }) .collect() } -pub fn query_user_unclaimed_rewards(deps: Deps, env: Env, user: String) -> StdResult { +pub fn query_user_unclaimed_rewards( + deps: Deps, + env: Env, + user: String, + start_after_collateral_denom: Option, + start_after_incentive_denom: Option, + limit: Option, +) -> StdResult> { let red_bank_addr = query_red_bank_address(deps)?; let user_addr = deps.api.addr_validate(&user)?; - let (unclaimed_rewards, _) = - compute_user_unclaimed_rewards(deps, &env.block, &red_bank_addr, &user_addr)?; - Ok(unclaimed_rewards) + let asset_incentives = state::paginate_asset_incentives( + deps.storage, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + )?; + + let mut total_unclaimed_rewards: HashMap = HashMap::new(); + + for ((collateral_denom, incentive_denom), _) in asset_incentives { + let (unclaimed_rewards, _) = compute_user_unclaimed_rewards( + deps, + &env.block, + &red_bank_addr, + &user_addr, + &collateral_denom, + &incentive_denom, + )?; + total_unclaimed_rewards + .entry(incentive_denom) + .and_modify(|amount| *amount += unclaimed_rewards) + .or_insert(unclaimed_rewards); + } + + Ok(total_unclaimed_rewards + .into_iter() + .map(|(denom, amount)| Coin { + denom, + amount, + }) + .collect()) } fn query_red_bank_address(deps: Deps) -> StdResult { diff --git a/contracts/incentives/src/error.rs b/contracts/incentives/src/error.rs index f68cb324f..45f89bfe3 100644 --- a/contracts/incentives/src/error.rs +++ b/contracts/incentives/src/error.rs @@ -27,4 +27,13 @@ pub enum ContractError { InvalidIncentive { reason: String, }, + + #[error("Invalid Pagination Params. If start_after_incentive_denom is supplied, then start_after_collateral_denom must also be supplied")] + InvalidPaginationParams, +} + +impl From for StdError { + fn from(err: ContractError) -> Self { + StdError::generic_err(err.to_string()) + } } diff --git a/contracts/incentives/src/helpers.rs b/contracts/incentives/src/helpers.rs index 73437a843..5cd062fb1 100644 --- a/contracts/incentives/src/helpers.rs +++ b/contracts/incentives/src/helpers.rs @@ -1,8 +1,7 @@ use std::cmp::{max, min}; use cosmwasm_std::{ - Addr, BlockInfo, Decimal, Deps, Order, OverflowError, OverflowOperation, StdError, StdResult, - Uint128, + Addr, BlockInfo, Decimal, Deps, OverflowError, OverflowOperation, StdError, StdResult, Uint128, }; use mars_red_bank_types::{incentives::AssetIncentive, red_bank}; @@ -78,8 +77,6 @@ pub fn compute_user_accrued_rewards( /// Result of querying and updating the status of the user and a give asset incentives in order to /// compute unclaimed rewards. pub struct UserAssetIncentiveStatus { - /// Denom of the asset that's the incentives target - pub denom: String, /// Current user index's value on the contract store (not updated by current asset index) pub user_index_current: Decimal, /// Asset incentive with values updated to the current block (not neccesarily commited @@ -92,64 +89,62 @@ pub fn compute_user_unclaimed_rewards( block: &BlockInfo, red_bank_addr: &Addr, user_addr: &Addr, -) -> StdResult<(Uint128, Vec)> { - let mut total_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.may_load(deps.storage, user_addr)?.unwrap_or_else(Uint128::zero); - - let result_asset_incentives: StdResult> = - ASSET_INCENTIVES.range(deps.storage, None, None, Order::Ascending).collect(); - - let mut user_asset_incentive_statuses_to_update: Vec = vec![]; - - for (denom, mut asset_incentive) in result_asset_incentives? { - // Get asset user balances and total supply - let collateral: red_bank::UserCollateralResponse = deps.querier.query_wasm_smart( - red_bank_addr, - &red_bank::QueryMsg::UserCollateral { - user: user_addr.to_string(), - denom: denom.clone(), - }, - )?; - let market: red_bank::Market = deps.querier.query_wasm_smart( - red_bank_addr, - &red_bank::QueryMsg::Market { - denom: denom.clone(), - }, - )?; - - // If user's balance is 0 there should be no rewards to accrue, so we don't care about - // updating indexes. If the user's balance changes, the indexes will be updated correctly at - // that point in time. - if collateral.amount_scaled.is_zero() { - continue; - } + collateral_denom: &str, + incentive_denom: &str, +) -> StdResult<(Uint128, Option)> { + let mut unclaimed_rewards = USER_UNCLAIMED_REWARDS + .may_load(deps.storage, (user_addr, collateral_denom, incentive_denom))? + .unwrap_or_else(Uint128::zero); + + let mut asset_incentive = + ASSET_INCENTIVES.load(deps.storage, (collateral_denom, incentive_denom))?; //TODO: Use may_load or handle error + + // Get asset user balances and total supply + let collateral: red_bank::UserCollateralResponse = deps.querier.query_wasm_smart( + red_bank_addr, + &red_bank::QueryMsg::UserCollateral { + user: user_addr.to_string(), + denom: collateral_denom.to_string(), + }, + )?; + let market: red_bank::Market = deps.querier.query_wasm_smart( + red_bank_addr, + &red_bank::QueryMsg::Market { + denom: collateral_denom.to_string(), + }, + )?; + + // If user's balance is 0 there should be no rewards to accrue, so we don't care about + // updating indexes. If the user's balance changes, the indexes will be updated correctly at + // that point in time. + if collateral.amount_scaled.is_zero() { + return Ok((unclaimed_rewards, None)); + } - update_asset_incentive_index( - &mut asset_incentive, - market.collateral_total_scaled, - block.time.seconds(), + update_asset_incentive_index( + &mut asset_incentive, + market.collateral_total_scaled, + block.time.seconds(), + )?; + + let user_asset_index = USER_ASSET_INDICES + .may_load(deps.storage, (user_addr, collateral_denom, incentive_denom))? + .unwrap_or_else(Decimal::zero); + + if user_asset_index != asset_incentive.index { + // Compute user accrued rewards and update user index + let asset_accrued_rewards = compute_user_accrued_rewards( + collateral.amount_scaled, + user_asset_index, + asset_incentive.index, )?; - - let user_asset_index = USER_ASSET_INDICES - .may_load(deps.storage, (user_addr, &denom))? - .unwrap_or_else(Decimal::zero); - - if user_asset_index != asset_incentive.index { - // Compute user accrued rewards and update user index - let asset_accrued_rewards = compute_user_accrued_rewards( - collateral.amount_scaled, - user_asset_index, - asset_incentive.index, - )?; - total_unclaimed_rewards += asset_accrued_rewards; - } - - user_asset_incentive_statuses_to_update.push(UserAssetIncentiveStatus { - denom, - user_index_current: user_asset_index, - asset_incentive_updated: asset_incentive, - }); + unclaimed_rewards += asset_accrued_rewards; } - Ok((total_unclaimed_rewards, user_asset_incentive_statuses_to_update)) + let user_asset_incentive_status_to_update = UserAssetIncentiveStatus { + user_index_current: user_asset_index, + asset_incentive_updated: asset_incentive, + }; + + Ok((unclaimed_rewards, Some(user_asset_incentive_status_to_update))) } diff --git a/contracts/incentives/src/state.rs b/contracts/incentives/src/state.rs index edf2f444e..878e7aff4 100644 --- a/contracts/incentives/src/state.rs +++ b/contracts/incentives/src/state.rs @@ -1,13 +1,141 @@ -use cosmwasm_std::{Addr, Decimal, Uint128}; -use cw_storage_plus::{Item, Map}; +use cosmwasm_std::{Addr, Decimal, Order, StdResult, Storage, Uint128}; +use cw_storage_plus::{Bound, Item, Map, PrefixBound}; use mars_owner::Owner; use mars_red_bank_types::incentives::{AssetIncentive, Config}; -// keys (for singleton) +use crate::ContractError; + +/// The owner of the contract pub const OWNER: Owner = Owner::new("owner"); + +/// The configuration of the contract pub const CONFIG: Item = Item::new("config"); -// namespaces (for buckets) -pub const ASSET_INCENTIVES: Map<&str, AssetIncentive> = Map::new("incentives"); -pub const USER_ASSET_INDICES: Map<(&Addr, &str), Decimal> = Map::new("indices"); -pub const USER_UNCLAIMED_REWARDS: Map<&Addr, Uint128> = Map::new("unclaimed_rewards"); +/// A map containing a configuration of an incentive for a given collateral and incentive denom. +/// The key is (collateral denom, incentive denom). +pub const ASSET_INCENTIVES: Map<(&str, &str), AssetIncentive> = Map::new("incentives"); + +/// A map containing the incentive index for a given user, collateral denom and incentive denom. +/// The key is (user address, collateral denom, incentive denom). +pub const USER_ASSET_INDICES: Map<(&Addr, &str, &str), Decimal> = Map::new("indices"); + +/// A map containing the amount of unclaimed incentives for a given user and incentive denom. +/// The key is (user address, collateral denom, incentive denom). +pub const USER_UNCLAIMED_REWARDS: Map<(&Addr, &str, &str), Uint128> = Map::new("unclaimed_rewards"); + +/// The default limit for pagination over asset incentives +pub const DEFAULT_LIMIT: u32 = 5; + +/// The maximum limit for pagination over asset incentives +/// TODO: Remove MAX_LIMIT? What is the purpose? Surely better to have the limit be whatever is the max gas limit? +pub const MAX_LIMIT: u32 = 10; + +/// Helper function to update unclaimed rewards for a given user, collateral denom and incentive +/// denom. Adds `accrued_rewards` to the existing amount. +pub fn increase_unclaimed_rewards( + storage: &mut dyn Storage, + user_addr: &Addr, + collateral_denom: &str, + incentive_denom: &str, + accrued_rewards: Uint128, +) -> StdResult<()> { + USER_UNCLAIMED_REWARDS.update( + storage, + (user_addr, collateral_denom, incentive_denom), + |ur: Option| -> StdResult { + Ok(ur.map_or_else(|| accrued_rewards, |r| r + accrued_rewards)) + }, + )?; + Ok(()) +} + +/// Returns asset incentives, with optional pagination. +/// Caller should make sure that if start_after_incentive_denom is supplied, then +/// start_after_collateral_denom is also supplied. +pub fn paginate_asset_incentives( + storage: &dyn Storage, + start_after_collateral_denom: Option, + start_after_incentive_denom: Option, + limit: Option, +) -> Result, ContractError> { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + Ok(match (start_after_collateral_denom.as_ref(), start_after_incentive_denom.as_ref()) { + (Some(collat_denom), Some(incen_denom)) => { + let start = Bound::exclusive((collat_denom.as_str(), incen_denom.as_str())); + ASSET_INCENTIVES.range(storage, Some(start), None, Order::Ascending) + } + (Some(collat_denom), None) => { + let start = PrefixBound::exclusive(collat_denom.as_str()); + ASSET_INCENTIVES.prefix_range(storage, Some(start), None, Order::Ascending) + } + (None, Some(_)) => return Err(ContractError::InvalidPaginationParams), + _ => ASSET_INCENTIVES.range(storage, None, None, Order::Ascending), + } + .take(limit) + .collect::>>()?) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::MockStorage; + + use super::*; + + #[test] + fn paginate_asset_incentives_works() { + let mut storage = MockStorage::new(); + + //store some incentives + let asset_incentive = AssetIncentive { + duration: 0, + emission_per_second: Uint128::zero(), + index: Decimal::zero(), + last_updated: 0, + start_time: 0, + }; + let incentives = vec![ + (("collat1".to_string(), "incen1".to_string()), asset_incentive.clone()), + (("collat1".to_string(), "incen2".to_string()), asset_incentive.clone()), + (("collat2".to_string(), "incen1".to_string()), asset_incentive.clone()), + (("collat2".to_string(), "incen2".to_string()), asset_incentive.clone()), + ]; + for ((collat, incen), incentive) in incentives.iter() { + ASSET_INCENTIVES + .save(&mut storage, (collat.as_str(), incen.as_str()), &incentive) + .unwrap(); + } + + // No pagination + let res = paginate_asset_incentives(&storage, None, None, None).unwrap(); + assert_eq!(res, incentives); + + // Start after collateral denom + let res = + paginate_asset_incentives(&storage, Some("collat1".to_string()), None, None).unwrap(); + println!("start after collat1: {:?}", res); + println!("expected: {:?}", incentives[2..].to_vec()); + assert_eq!(res, incentives[2..]); + + // Start after collateral denom and incentive denom + let res = paginate_asset_incentives( + &storage, + Some("collat1".to_string()), + Some("incen1".to_string()), + None, + ) + .unwrap(); + assert_eq!(res, incentives[1..]); + let res = paginate_asset_incentives( + &storage, + Some("collat1".to_string()), + Some("incen2".to_string()), + None, + ) + .unwrap(); + assert_eq!(res, incentives[2..]); + + // Limit + let res = paginate_asset_incentives(&storage, None, None, Some(2)).unwrap(); + assert_eq!(res, incentives[..2].to_vec()); + } +} diff --git a/contracts/incentives/tests/test_balance_change.rs b/contracts/incentives/tests/test_balance_change.rs index a601ea06f..4adca4159 100644 --- a/contracts/incentives/tests/test_balance_change.rs +++ b/contracts/incentives/tests/test_balance_change.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{ - attr, + attr, coin, testing::{mock_env, mock_info}, - Addr, Decimal, Response, Timestamp, Uint128, + Addr, Decimal, Event, Response, Timestamp, Uint128, }; use mars_incentives::{ contract::{execute, execute_balance_change, query_user_unclaimed_rewards}, @@ -53,7 +53,15 @@ fn execute_balance_change_noops() { }; let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - assert_eq!(res, Response::default()) + assert_eq!( + res, + Response::default().add_event( + Event::new("mars/incentives/balance_change") + .add_attribute("action", "balance_change") + .add_attribute("denom", "uosmo") + .add_attribute("user", "user") + ) + ) } #[test] @@ -67,7 +75,7 @@ fn balance_change_zero_emission() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second: Uint128::zero(), start_time: env.block.time.seconds(), @@ -97,29 +105,31 @@ fn balance_change_zero_emission() { .unwrap(); assert_eq!( - res.attributes, + res.events[0].attributes, + vec![attr("action", "balance_change"), attr("denom", denom), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", "user"), + attr("incentive_denom", "umars"), attr("rewards_accrued", expected_accrued_rewards), - attr("asset_index", asset_incentive_index.to_string()), + attr("asset_index", asset_incentive_index.to_string()) ] ); // asset incentive index stays the same - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, asset_incentive_index); assert_eq!(asset_incentive.last_updated, 600_000); // user index is set to asset's index let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_asset_index, asset_incentive_index); // rewards get updated let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, &user_addr).unwrap(); + USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_unclaimed_rewards, expected_accrued_rewards) } @@ -140,7 +150,7 @@ fn balance_change_user_with_zero_balance() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second, start_time: time_last_updated, @@ -175,29 +185,32 @@ fn balance_change_user_with_zero_balance() { .unwrap(); assert_eq!( - res.attributes, + res.events[0].attributes, + vec![attr("action", "balance_change"), attr("denom", denom), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", "user"), + attr("incentive_denom", "umars"), attr("rewards_accrued", "0"), - attr("asset_index", expected_index.to_string()), + attr("asset_index", expected_index.to_string()) ] ); // asset incentive gets updated - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, expected_index); assert_eq!(asset_incentive.last_updated, time_contract_call); // user index is set to asset's index let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_asset_index, expected_index); // no new rewards - let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.may_load(deps.as_ref().storage, &user_addr).unwrap(); + let user_unclaimed_rewards = USER_UNCLAIMED_REWARDS + .may_load(deps.as_ref().storage, (&user_addr, denom, "umars")) + .unwrap(); assert_eq!(user_unclaimed_rewards, None) } @@ -216,7 +229,7 @@ fn with_zero_previous_balance_and_asset_with_zero_index_accumulates_rewards() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second, start_time: time_last_updated, @@ -267,8 +280,15 @@ fn with_zero_previous_balance_and_asset_with_zero_index_accumulates_rewards() { ..Default::default() }); let rewards_query = - query_user_unclaimed_rewards(deps.as_ref(), env, String::from("user")).unwrap(); - assert_eq!(Uint128::new(1000).checked_mul(emission_per_second).unwrap(), rewards_query); + query_user_unclaimed_rewards(deps.as_ref(), env, "user".to_string(), None, None, None) + .unwrap(); + assert_eq!( + vec![coin( + Uint128::new(1000).checked_mul(emission_per_second).unwrap().u128(), + "umars" + )], + rewards_query + ); } } @@ -307,7 +327,7 @@ fn set_new_asset_incentive_user_non_zero_balance() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second, start_time: time_last_updated, @@ -329,9 +349,10 @@ fn set_new_asset_incentive_user_non_zero_balance() { }); let unclaimed_rewards = - query_user_unclaimed_rewards(deps.as_ref(), env, "user".to_string()).unwrap(); + query_user_unclaimed_rewards(deps.as_ref(), env, "user".to_string(), None, None, None) + .unwrap(); // 100_000 s * 100 MARS/s * 1/10th of total deposit - let expected_unclaimed_rewards = Uint128::new(1_000_000); + let expected_unclaimed_rewards = vec![coin(1_000_000, "umars")]; assert_eq!(unclaimed_rewards, expected_unclaimed_rewards); } @@ -379,13 +400,15 @@ fn set_new_asset_incentive_user_non_zero_balance() { }); let unclaimed_rewards = - query_user_unclaimed_rewards(deps.as_ref(), env, "user".to_string()).unwrap(); - let expected_unclaimed_rewards = Uint128::new( + query_user_unclaimed_rewards(deps.as_ref(), env, "user".to_string(), None, None, None) + .unwrap(); + let expected_unclaimed_rewards = vec![coin( // 200_000 s * 100 MARS/s * 1/10th of total deposit + 2_000_000 + // 100_000 s * 100 MARS/s * 1/4 of total deposit 2_500_000, - ); + "umars", + )]; assert_eq!(unclaimed_rewards, expected_unclaimed_rewards); } } @@ -407,7 +430,7 @@ fn balance_change_user_non_zero_balance() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second, start_time: expected_time_last_updated, @@ -453,31 +476,35 @@ fn balance_change_user_non_zero_balance() { ) .unwrap(); assert_eq!( - res.attributes, + res.events[0].attributes, + vec![attr("action", "balance_change"), attr("denom", denom), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", "user"), + attr("incentive_denom", "umars"), attr("rewards_accrued", expected_accrued_rewards), - attr("asset_index", expected_asset_incentive_index.to_string()), + attr("asset_index", expected_asset_incentive_index.to_string()) ] ); // asset incentive gets updated expected_time_last_updated = time_contract_call; - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, expected_asset_incentive_index); assert_eq!(asset_incentive.last_updated, expected_time_last_updated); // user index is set to asset's index let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_asset_index, expected_asset_incentive_index); // user gets new rewards - let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, &user_addr).unwrap(); + let user_unclaimed_rewards = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, (&user_addr, denom, "umars")) + .unwrap(); expected_accumulated_rewards += expected_accrued_rewards; assert_eq!(user_unclaimed_rewards, expected_accumulated_rewards) } @@ -516,31 +543,35 @@ fn balance_change_user_non_zero_balance() { ) .unwrap(); assert_eq!( - res.attributes, + res.events[0].attributes, + vec![attr("action", "balance_change"), attr("denom", denom), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", "user"), + attr("incentive_denom", "umars"), attr("rewards_accrued", expected_accrued_rewards), - attr("asset_index", expected_asset_incentive_index.to_string()), + attr("asset_index", expected_asset_incentive_index.to_string()) ] ); // asset incentive gets updated expected_time_last_updated = time_contract_call; - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, expected_asset_incentive_index); assert_eq!(asset_incentive.last_updated, expected_time_last_updated); // user index is set to asset's index let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_asset_index, expected_asset_incentive_index); // user gets new rewards - let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, &user_addr).unwrap(); + let user_unclaimed_rewards = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, (&user_addr, denom, "umars")) + .unwrap(); expected_accumulated_rewards += expected_accrued_rewards; assert_eq!(user_unclaimed_rewards, expected_accumulated_rewards) } @@ -563,29 +594,33 @@ fn balance_change_user_non_zero_balance() { let res = execute(deps.as_mut(), env, info, msg).unwrap(); assert_eq!( - res.attributes, + res.events[0].attributes, + vec![attr("action", "balance_change"), attr("denom", denom), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, vec![ - attr("action", "balance_change"), - attr("denom", denom), - attr("user", "user"), + attr("incentive_denom", "umars"), attr("rewards_accrued", "0"), - attr("asset_index", expected_asset_incentive_index.to_string()), + attr("asset_index", expected_asset_incentive_index.to_string()) ] ); // asset incentive is still the same - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, expected_asset_incentive_index); assert_eq!(asset_incentive.last_updated, expected_time_last_updated); // user index is still the same let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, denom, "umars")).unwrap(); assert_eq!(user_asset_index, expected_asset_incentive_index); // user gets no new rewards - let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, &user_addr).unwrap(); + let user_unclaimed_rewards = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, (&user_addr, denom, "umars")) + .unwrap(); assert_eq!(user_unclaimed_rewards, expected_accumulated_rewards) } } diff --git a/contracts/incentives/tests/test_claim_rewards.rs b/contracts/incentives/tests/test_claim_rewards.rs index 79b342e85..f82e3bca1 100644 --- a/contracts/incentives/tests/test_claim_rewards.rs +++ b/contracts/incentives/tests/test_claim_rewards.rs @@ -91,7 +91,7 @@ fn execute_claim_rewards() { ASSET_INCENTIVES .save( deps.as_mut().storage, - asset_denom, + (asset_denom, "umars"), &AssetIncentive { emission_per_second: Uint128::new(100), start_time: time_start, @@ -104,7 +104,7 @@ fn execute_claim_rewards() { ASSET_INCENTIVES .save( deps.as_mut().storage, - zero_denom, + (zero_denom, "umars"), &AssetIncentive { emission_per_second: Uint128::zero(), start_time: env.block.time.seconds(), @@ -117,7 +117,7 @@ fn execute_claim_rewards() { ASSET_INCENTIVES .save( deps.as_mut().storage, - no_user_denom, + (no_user_denom, "umars"), &AssetIncentive { emission_per_second: Uint128::new(200), start_time: env.block.time.seconds(), @@ -130,16 +130,24 @@ fn execute_claim_rewards() { // user indices USER_ASSET_INDICES - .save(deps.as_mut().storage, (&user_addr, asset_denom), &Decimal::one()) + .save(deps.as_mut().storage, (&user_addr, asset_denom, "umars"), &Decimal::one()) .unwrap(); USER_ASSET_INDICES - .save(deps.as_mut().storage, (&user_addr, zero_denom), &Decimal::from_ratio(1_u128, 2_u128)) + .save( + deps.as_mut().storage, + (&user_addr, zero_denom, "umars"), + &Decimal::from_ratio(1_u128, 2_u128), + ) .unwrap(); // unclaimed_rewards USER_UNCLAIMED_REWARDS - .save(deps.as_mut().storage, &user_addr, &previous_unclaimed_rewards) + .save( + deps.as_mut().storage, + (&user_addr, asset_denom, "umars"), + &previous_unclaimed_rewards, + ) .unwrap(); let expected_asset_incentive_index = compute_asset_incentive_index( @@ -174,28 +182,48 @@ fn execute_claim_rewards() { block_time: Timestamp::from_seconds(time_contract_call), ..Default::default() }); - let msg = ExecuteMsg::ClaimRewards {}; + let msg = ExecuteMsg::ClaimRewards { + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }; // query a bit before gives less rewards let env_before = mars_testing::mock_env(MockEnvParams { block_time: Timestamp::from_seconds(time_contract_call - 10_000), ..Default::default() }); - let rewards_query_before = - query_user_unclaimed_rewards(deps.as_ref(), env_before, String::from("user")).unwrap(); - assert!(rewards_query_before < expected_accrued_rewards); + let rewards_query_before = query_user_unclaimed_rewards( + deps.as_ref(), + env_before, + String::from("user"), + None, + None, + None, + ) + .unwrap(); + assert!(rewards_query_before.len() == 1); + assert!(rewards_query_before[0].amount < expected_accrued_rewards); // query before execution gives expected rewards - let rewards_query = - query_user_unclaimed_rewards(deps.as_ref(), env.clone(), String::from("user")).unwrap(); - assert_eq!(rewards_query, expected_accrued_rewards); + let rewards_query = query_user_unclaimed_rewards( + deps.as_ref(), + env.clone(), + String::from("user"), + None, + None, + None, + ) + .unwrap(); + assert_eq!(rewards_query[0].amount, expected_accrued_rewards); let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); // query after execution gives 0 rewards let rewards_query_after = - query_user_unclaimed_rewards(deps.as_ref(), env, String::from("user")).unwrap(); - assert_eq!(rewards_query_after, Uint128::zero()); + query_user_unclaimed_rewards(deps.as_ref(), env, String::from("user"), None, None, None) + .unwrap(); + assert_eq!(rewards_query_after[0].amount, Uint128::zero()); // ASSERT @@ -208,44 +236,48 @@ fn execute_claim_rewards() { ); assert_eq!( - res.attributes, - vec![ - attr("action", "claim_rewards"), - attr("user", "user"), - attr("mars_rewards", expected_accrued_rewards), - ] + res.events[0].attributes, + vec![attr("action", "claim_rewards"), attr("user", "user"),] + ); + assert_eq!( + res.events[1].attributes, + vec![attr("denom", "umars"), attr("amount", expected_accrued_rewards),] ); - // asset and zero incentives get updated, no_user does not - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, asset_denom).unwrap(); + let asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (asset_denom, "umars")).unwrap(); assert_eq!(asset_incentive.index, expected_asset_incentive_index); assert_eq!(asset_incentive.last_updated, time_contract_call); - let zero_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, zero_denom).unwrap(); + let zero_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (zero_denom, "umars")).unwrap(); assert_eq!(zero_incentive.index, Decimal::one()); assert_eq!(zero_incentive.last_updated, time_contract_call); - let no_user_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, no_user_denom).unwrap(); + let no_user_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, (no_user_denom, "umars")).unwrap(); assert_eq!(no_user_incentive.index, Decimal::one()); assert_eq!(no_user_incentive.last_updated, time_start); // user's asset and zero indices are updated let user_asset_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, asset_denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, asset_denom, "umars")).unwrap(); assert_eq!(user_asset_index, expected_asset_incentive_index); let user_zero_index = - USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, zero_denom)).unwrap(); + USER_ASSET_INDICES.load(deps.as_ref().storage, (&user_addr, zero_denom, "umars")).unwrap(); assert_eq!(user_zero_index, Decimal::one()); // user's no_user does not get updated - let user_no_user_index = - USER_ASSET_INDICES.may_load(deps.as_ref().storage, (&user_addr, no_user_denom)).unwrap(); + let user_no_user_index = USER_ASSET_INDICES + .may_load(deps.as_ref().storage, (&user_addr, no_user_denom, "umars")) + .unwrap(); assert_eq!(user_no_user_index, None); // user rewards are cleared - let user_unclaimed_rewards = - USER_UNCLAIMED_REWARDS.load(deps.as_ref().storage, &user_addr).unwrap(); + let user_unclaimed_rewards = USER_UNCLAIMED_REWARDS + .load(deps.as_ref().storage, (&user_addr, asset_denom, "umars")) + .unwrap(); assert_eq!(user_unclaimed_rewards, Uint128::zero()) } @@ -255,12 +287,16 @@ fn claim_zero_rewards() { let mut deps = th_setup(); let info = mock_info("user", &[]); - let msg = ExecuteMsg::ClaimRewards {}; + let msg = ExecuteMsg::ClaimRewards { + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }; let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); assert_eq!(res.messages.len(), 0); assert_eq!( - res.attributes, - vec![attr("action", "claim_rewards"), attr("user", "user"), attr("mars_rewards", "0"),] + res.events[0].attributes, + vec![attr("action", "claim_rewards"), attr("user", "user"),] ); } diff --git a/contracts/incentives/tests/test_quering.rs b/contracts/incentives/tests/test_quering.rs index 795c7994e..9c6bf985e 100644 --- a/contracts/incentives/tests/test_quering.rs +++ b/contracts/incentives/tests/test_quering.rs @@ -18,7 +18,7 @@ fn query_asset_incentive() { index: Decimal::one(), last_updated: 150, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uosmo", &uosmo_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uosmo", "umars"), &uosmo_incentive).unwrap(); let uatom_incentive = AssetIncentive { emission_per_second: Uint128::zero(), start_time: 0, @@ -26,7 +26,7 @@ fn query_asset_incentive() { index: Decimal::one(), last_updated: 1000, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uatom", &uatom_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uatom", "umars"), &uatom_incentive).unwrap(); let uusdc_incentive = AssetIncentive { emission_per_second: Uint128::new(200), start_time: 12000, @@ -34,15 +34,19 @@ fn query_asset_incentive() { index: Decimal::from_ratio(120u128, 50u128), last_updated: 120000, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uusdc", &uusdc_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uusdc", "umars"), &uusdc_incentive).unwrap(); let res: AssetIncentiveResponse = helpers::th_query( deps.as_ref(), QueryMsg::AssetIncentive { - denom: "uatom".to_string(), + collateral_denom: "uatom".to_string(), + incentive_denom: "umars".to_string(), }, ); - assert_eq!(res, AssetIncentiveResponse::from("uatom".to_string(), uatom_incentive)); + assert_eq!( + res, + AssetIncentiveResponse::from("uatom".to_string(), "umars".to_string(), uatom_incentive) + ); } #[test] @@ -57,7 +61,7 @@ fn query_asset_incentives() { index: Decimal::one(), last_updated: 150, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uosmo", &uosmo_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uosmo", "umars"), &uosmo_incentive).unwrap(); let uatom_incentive = AssetIncentive { emission_per_second: Uint128::zero(), start_time: 0, @@ -65,7 +69,7 @@ fn query_asset_incentives() { index: Decimal::one(), last_updated: 1000, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uatom", &uatom_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uatom", "umars"), &uatom_incentive).unwrap(); let uusdc_incentive = AssetIncentive { emission_per_second: Uint128::new(200), start_time: 12000, @@ -73,22 +77,27 @@ fn query_asset_incentives() { index: Decimal::from_ratio(120u128, 50u128), last_updated: 120000, }; - ASSET_INCENTIVES.save(deps.as_mut().storage, "uusdc", &uusdc_incentive).unwrap(); + ASSET_INCENTIVES.save(deps.as_mut().storage, ("uusdc", "umars"), &uusdc_incentive).unwrap(); // NOTE: responses are ordered alphabetically by denom let res: Vec = helpers::th_query( deps.as_ref(), QueryMsg::AssetIncentives { - start_after: None, + start_after_collateral_denom: None, + start_after_incentive_denom: None, limit: None, }, ); assert_eq!( res, vec![ - AssetIncentiveResponse::from("uatom".to_string(), uatom_incentive), - AssetIncentiveResponse::from("uosmo".to_string(), uosmo_incentive.clone()), - AssetIncentiveResponse::from("uusdc".to_string(), uusdc_incentive), + AssetIncentiveResponse::from("uatom".to_string(), "umars".to_string(), uatom_incentive), + AssetIncentiveResponse::from( + "uosmo".to_string(), + "umars".to_string(), + uosmo_incentive.clone() + ), + AssetIncentiveResponse::from("uusdc".to_string(), "umars".to_string(), uusdc_incentive), ] ); @@ -96,9 +105,17 @@ fn query_asset_incentives() { let res: Vec = helpers::th_query( deps.as_ref(), QueryMsg::AssetIncentives { - start_after: Some("uatom".to_string()), + start_after_collateral_denom: Some("uatom".to_string()), + start_after_incentive_denom: None, limit: Some(1), }, ); - assert_eq!(res, vec![AssetIncentiveResponse::from("uosmo".to_string(), uosmo_incentive)]); + assert_eq!( + res, + vec![AssetIncentiveResponse::from( + "uosmo".to_string(), + "umars".to_string(), + uosmo_incentive + )] + ); } diff --git a/contracts/incentives/tests/test_set_asset_incentive.rs b/contracts/incentives/tests/test_set_asset_incentive.rs index c6d8a68ac..8b42e4a04 100644 --- a/contracts/incentives/tests/test_set_asset_incentive.rs +++ b/contracts/incentives/tests/test_set_asset_incentive.rs @@ -25,7 +25,8 @@ fn only_owner_can_set_asset_incentive() { let info = mock_info("sender", &[]); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(100)), start_time: None, duration: Some(86400), @@ -41,7 +42,8 @@ fn invalid_denom_for_incentives() { let info = mock_info("owner", &[]); let msg = ExecuteMsg::SetAssetIncentive { - denom: "adfnjg&akjsfn!".to_string(), + collateral_denom: "adfnjg&akjsfn!".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(100)), start_time: None, duration: Some(2400u64), @@ -64,7 +66,8 @@ fn cannot_set_new_asset_incentive_with_empty_params() { let env = mock_env(); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: None, @@ -78,7 +81,8 @@ fn cannot_set_new_asset_incentive_with_empty_params() { ); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::from(100u32)), start_time: Some(100), duration: None, @@ -92,7 +96,8 @@ fn cannot_set_new_asset_incentive_with_empty_params() { ); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: Some(100), duration: Some(2400u64), @@ -106,7 +111,8 @@ fn cannot_set_new_asset_incentive_with_empty_params() { ); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::from(100u32)), start_time: None, duration: Some(2400u64), @@ -131,7 +137,8 @@ fn cannot_set_new_asset_incentive_with_invalid_params() { }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::from(100u32)), start_time: Some(block_time.seconds()), duration: Some(0u64), @@ -145,7 +152,8 @@ fn cannot_set_new_asset_incentive_with_invalid_params() { ); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::from(100u32)), start_time: Some(block_time.minus_seconds(1u64).seconds()), duration: Some(100u64), @@ -170,7 +178,8 @@ fn set_new_asset_incentive() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(100)), start_time: Some(block_time.seconds()), duration: Some(86400), @@ -181,14 +190,15 @@ fn set_new_asset_incentive() { res.attributes, vec![ attr("action", "set_asset_incentive"), - attr("denom", "uosmo"), + attr("collateral_denom", "uosmo"), + attr("incentive_denom", "umars"), attr("emission_per_second", "100"), attr("start_time", block_time.seconds().to_string()), attr("duration", "86400"), ] ); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); assert_eq!(asset_incentive.emission_per_second, Uint128::new(100)); assert_eq!(asset_incentive.index, Decimal::zero()); @@ -214,7 +224,7 @@ fn set_existing_asset_incentive_with_different_start_time() { ASSET_INCENTIVES .save( deps.as_mut().storage, - "uosmo", + ("uosmo", "umars"), &AssetIncentive { emission_per_second: Uint128::new(124), start_time, @@ -232,7 +242,8 @@ fn set_existing_asset_incentive_with_different_start_time() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: Some(block_time.seconds() + 10), duration: None, @@ -252,7 +263,8 @@ fn set_existing_asset_incentive_with_different_start_time() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: Some(block_time.seconds() - 1), duration: None, @@ -273,13 +285,14 @@ fn set_existing_asset_incentive_with_different_start_time() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: Some(start_time), duration: None, }; execute(deps.as_mut(), env, info.clone(), msg).unwrap(); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); assert_eq!(asset_incentive.start_time, start_time); assert_eq!(asset_incentive.last_updated, block_time.seconds()); @@ -290,7 +303,8 @@ fn set_existing_asset_incentive_with_different_start_time() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: None, @@ -310,14 +324,16 @@ fn set_existing_asset_incentive_with_different_start_time() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: None, }; - let prev_asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let prev_asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); execute(deps.as_mut(), env, info, msg).unwrap(); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); assert_eq!(asset_incentive.start_time, prev_asset_incentive.start_time); assert_eq!(asset_incentive.last_updated, block_time.seconds()); } @@ -339,7 +355,7 @@ fn set_existing_asset_incentive_with_different_duration() { ASSET_INCENTIVES .save( deps.as_mut().storage, - "uosmo", + ("uosmo", "umars"), &AssetIncentive { emission_per_second: Uint128::new(124), start_time, @@ -357,7 +373,8 @@ fn set_existing_asset_incentive_with_different_duration() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: Some(0), @@ -377,7 +394,8 @@ fn set_existing_asset_incentive_with_different_duration() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: Some(duration - 1), @@ -398,14 +416,16 @@ fn set_existing_asset_incentive_with_different_duration() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: None, start_time: None, duration: Some(duration), }; - let prev_asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let prev_asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); execute(deps.as_mut(), env, info.clone(), msg).unwrap(); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); assert_eq!(asset_incentive.start_time, prev_asset_incentive.start_time); assert_eq!(asset_incentive.duration, duration); assert_eq!(asset_incentive.last_updated, block_time.seconds()); @@ -417,14 +437,16 @@ fn set_existing_asset_incentive_with_different_duration() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: "uosmo".to_string(), + collateral_denom: "uosmo".to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(300)), start_time: None, duration: None, }; - let prev_asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let prev_asset_incentive = + ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); execute(deps.as_mut(), env, info, msg).unwrap(); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, "uosmo").unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, ("uosmo", "umars")).unwrap(); assert_eq!(asset_incentive.emission_per_second, Uint128::new(300)); assert_eq!(asset_incentive.start_time, prev_asset_incentive.start_time); assert_eq!(asset_incentive.duration, prev_asset_incentive.duration); @@ -451,7 +473,7 @@ fn set_existing_asset_incentive_with_index_updated_during_incentive() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second: Uint128::new(100), start_time, @@ -470,7 +492,8 @@ fn set_existing_asset_incentive_with_index_updated_during_incentive() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: denom.to_string(), + collateral_denom: denom.to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(200)), start_time: None, duration: None, @@ -482,14 +505,15 @@ fn set_existing_asset_incentive_with_index_updated_during_incentive() { res.attributes, vec![ attr("action", "set_asset_incentive"), - attr("denom", denom), + attr("collateral_denom", denom), + attr("incentive_denom", "umars"), attr("emission_per_second", "200"), attr("start_time", start_time.to_string()), attr("duration", duration.to_string()), ] ); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); let expected_index = compute_asset_incentive_index( Decimal::from_ratio(1_u128, 2_u128), @@ -527,7 +551,7 @@ fn set_existing_asset_incentive_with_index_updated_after_incentive() { ASSET_INCENTIVES .save( deps.as_mut().storage, - denom, + (denom, "umars"), &AssetIncentive { emission_per_second: Uint128::new(120), start_time, @@ -546,7 +570,8 @@ fn set_existing_asset_incentive_with_index_updated_after_incentive() { ..Default::default() }); let msg = ExecuteMsg::SetAssetIncentive { - denom: denom.to_string(), + collateral_denom: denom.to_string(), + incentive_denom: "umars".to_string(), emission_per_second: Some(Uint128::new(215)), start_time: Some(block_time.seconds()), duration: None, @@ -558,14 +583,15 @@ fn set_existing_asset_incentive_with_index_updated_after_incentive() { res.attributes, vec![ attr("action", "set_asset_incentive"), - attr("denom", denom), + attr("collateral_denom", denom), + attr("incentive_denom", "umars"), attr("emission_per_second", "215"), attr("start_time", block_time.seconds().to_string()), attr("duration", duration.to_string()), ] ); - let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, denom).unwrap(); + let asset_incentive = ASSET_INCENTIVES.load(deps.as_ref().storage, (denom, "umars")).unwrap(); let expected_index = compute_asset_incentive_index( Decimal::from_ratio(1_u128, 4_u128), diff --git a/contracts/rewards-collector/src/contract.rs b/contracts/rewards-collector/src/contract.rs index 155634a05..7a1e3e56d 100644 --- a/contracts/rewards-collector/src/contract.rs +++ b/contracts/rewards-collector/src/contract.rs @@ -108,7 +108,13 @@ impl<'a> Collector<'a> { .add_attribute("amount", stringify_option_amount(amount))) } - fn claim_incentive_rewards(&self, deps: DepsMut) -> ContractResult { + fn claim_incentive_rewards( + &self, + deps: DepsMut, + start_after_collateral_denom: Option, + start_after_incentive_denom: Option, + limit: Option, + ) -> ContractResult { let cfg = self.config.load(deps.storage)?; let incentives_addr = address_provider::helpers::query_contract_addr( @@ -119,7 +125,11 @@ impl<'a> Collector<'a> { let claim_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: incentives_addr.to_string(), - msg: to_binary(&incentives::ExecuteMsg::ClaimRewards {})?, + msg: to_binary(&incentives::ExecuteMsg::ClaimRewards { + start_after_collateral_denom, + start_after_incentive_denom, + limit, + })?, funds: vec![], }); @@ -263,12 +273,11 @@ pub mod entry { use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; - use mars_red_bank_types::rewards_collector::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; - - use crate::ContractResult; use mars_owner::OwnerInit::SetInitialOwner; + use mars_red_bank_types::rewards_collector::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; use super::Collector; + use crate::ContractResult; #[entry_point] pub fn instantiate( @@ -321,7 +330,16 @@ pub mod entry { denom, amount, } => collector.swap_asset(deps, env, denom, amount), - ExecuteMsg::ClaimIncentiveRewards {} => collector.claim_incentive_rewards(deps), + ExecuteMsg::ClaimIncentiveRewards { + start_after_collateral_denom, + start_after_incentive_denom, + limit, + } => collector.claim_incentive_rewards( + deps, + start_after_collateral_denom, + start_after_incentive_denom, + limit, + ), } } diff --git a/integration-tests/tests/test_incentives.rs b/integration-tests/tests/test_incentives.rs index aa907cf06..e7f05ad5a 100644 --- a/integration-tests/tests/test_incentives.rs +++ b/integration-tests/tests/test_incentives.rs @@ -21,7 +21,13 @@ fn rewards_claim() { red_bank.init_asset(&mut mock_env, "uusdc", default_asset_params()); let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 10, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 10, + ONE_WEEK_IN_SEC, + ); let user = Addr::unchecked("user_a"); let funded_amt = 10_000_000_000u128; @@ -38,12 +44,12 @@ fn rewards_claim() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(864000)); + assert_eq!(rewards_balance[0].amount, Uint128::new(864000)); incentives.claim_rewards(&mut mock_env, &user).unwrap(); @@ -53,7 +59,7 @@ fn rewards_claim() { assert_eq!(mars_balance.amount, Uint128::new(9_999_136_000)); //10_000_000_000 - 864_000 let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); } // User A deposited usdc in the redbank when incentives were 5 emissions per second @@ -71,7 +77,13 @@ fn emissions_rates() { red_bank.init_asset(&mut mock_env, "umars", default_asset_params()); let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); let user = Addr::unchecked("user_a"); let funded_amt = 10_000_000_000u128; @@ -89,12 +101,12 @@ fn emissions_rates() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(432000)); // 86400*5 + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // 86400*5 incentives.claim_rewards(&mut mock_env, &user).unwrap(); @@ -102,9 +114,15 @@ fn emissions_rates() { assert_eq!(balance.amount, Uint128::new(432000)); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uosmo", 10, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uosmo", + "umars", + 10, + ONE_WEEK_IN_SEC, + ); red_bank.deposit(&mut mock_env, &user, coin(funded_amt, "uosmo")).unwrap(); let balance = mock_env.query_balance(&user, "uosmo").unwrap(); @@ -115,12 +133,12 @@ fn emissions_rates() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(1296000)); // 432000 + (86400*10) + assert_eq!(rewards_balance[0].amount, Uint128::new(1296000)); // 432000 + (86400*10) incentives.claim_rewards(&mut mock_env, &user).unwrap(); @@ -128,7 +146,7 @@ fn emissions_rates() { assert_eq!(balance.amount, Uint128::new(1728000)); // 1296000 + 432000 let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); } // User A deposits usdc in the redbank and claimed rewards after one day @@ -144,7 +162,13 @@ fn no_incentives_accrued_after_withdraw() { red_bank.init_asset(&mut mock_env, "umars", default_asset_params()); let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); let user = Addr::unchecked("user_a"); let funded_amt = 10_000_000_000u128; @@ -162,12 +186,12 @@ fn no_incentives_accrued_after_withdraw() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(432000)); // 86400 * 5 + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // 86400 * 5 incentives.claim_rewards(&mut mock_env, &user).unwrap(); @@ -175,7 +199,7 @@ fn no_incentives_accrued_after_withdraw() { assert_eq!(balance.amount, Uint128::new(432000)); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); red_bank.withdraw(&mut mock_env, &user, "uusdc", None).unwrap(); let balance = mock_env.query_balance(&user, "uusdc").unwrap(); @@ -186,12 +210,12 @@ fn no_incentives_accrued_after_withdraw() { assert_eq!(user_collateral.amount, Uint128::zero()); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); } // User A deposits usdc, osmo, and atom all with different emissions per second & claims rewards after one day @@ -208,9 +232,27 @@ fn multiple_assets() { // set incentives let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uatom", 10, ONE_WEEK_IN_SEC); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uosmo", 3, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uatom", + "umars", + 10, + ONE_WEEK_IN_SEC, + ); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uosmo", + "umars", + 3, + ONE_WEEK_IN_SEC, + ); // fund user wallet account let user = Addr::unchecked("user_a"); @@ -242,12 +284,12 @@ fn multiple_assets() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(1555200)); + assert_eq!(rewards_balance[0].amount, Uint128::new(1555200)); } // User A holds usdc in the red bank while there are incentives then incentives are stopped and then incentives are restarted @@ -261,7 +303,13 @@ fn stopping_incentives() { // set incentives let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); // fund user wallet account let user = Addr::unchecked("user_a"); @@ -281,28 +329,28 @@ fn stopping_incentives() { assert_eq!(user_collateral.amount.u128(), funded_amt); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(432000)); + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // stop incentives - incentives.update_asset_incentive_emission(&mut mock_env, "uusdc", 0); + incentives.update_asset_incentive_emission(&mut mock_env, "uusdc", "umars", 0); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(432000)); + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // restart incentives - incentives.update_asset_incentive_emission(&mut mock_env, "uusdc", 5); + incentives.update_asset_incentive_emission(&mut mock_env, "uusdc", "umars", 5); mock_env.increment_by_time(43200); // 12 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user); - assert_eq!(rewards_balance, Uint128::new(648000)); // (5*86400) + (5*43200) + assert_eq!(rewards_balance[0].amount, Uint128::new(648000)); // (5*86400) + (5*43200) } // User A deposits half the amount user B deposits in the red bank @@ -317,7 +365,13 @@ fn multiple_users() { // set incentives let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); // fund user wallet account let user_a = Addr::unchecked("user_a"); @@ -348,18 +402,18 @@ fn multiple_users() { assert_eq!(user_collateral.amount.u128(), funded_amt_two); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_b); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::new(144000)); // (86400*5) * (1/3) + assert_eq!(rewards_balance[0].amount, Uint128::new(144000)); // (86400*5) * (1/3) let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_b); - assert_eq!(rewards_balance, Uint128::new(288000)); // (86400*5)/2 * (2/3) + assert_eq!(rewards_balance[0].amount, Uint128::new(288000)); // (86400*5)/2 * (2/3) // User A withdraws, user B holds @@ -368,10 +422,10 @@ fn multiple_users() { mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::new(144000)); // stays the same + assert_eq!(rewards_balance[0].amount, Uint128::new(144000)); // stays the same let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_b); - assert_eq!(rewards_balance, Uint128::new(720000)); // 288000 + (86400*5) + assert_eq!(rewards_balance[0].amount, Uint128::new(720000)); // 288000 + (86400*5) } // User A attempts to claim rewards but there is not enough mars in the incentives contract @@ -385,7 +439,13 @@ fn insufficient_mars() { // set incentives let incentives = mock_env.incentives.clone(); - incentives.init_asset_incentive_from_current_block(&mut mock_env, "uusdc", 5, ONE_WEEK_IN_SEC); + incentives.init_asset_incentive_from_current_block( + &mut mock_env, + "uusdc", + "umars", + 5, + ONE_WEEK_IN_SEC, + ); // fund user wallet account let user_a = Addr::unchecked("user_a"); @@ -406,7 +466,7 @@ fn insufficient_mars() { assert_eq!(user_collateral.amount.u128(), funded_amt_one); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); let balance = mock_env.query_balance(&user_a, "umars").unwrap(); assert_eq!(balance.amount, Uint128::zero()); @@ -414,7 +474,7 @@ fn insufficient_mars() { mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::new(432000)); // (86400*5) + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // (86400*5) let balance = mock_env.query_balance(&user_a, "umars").unwrap(); assert_eq!(balance.amount, Uint128::zero()); @@ -422,7 +482,7 @@ fn insufficient_mars() { incentives.claim_rewards(&mut mock_env, &user_a).unwrap(); let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::zero()); + assert_eq!(rewards_balance[0].amount, Uint128::zero()); let balance = mock_env.query_balance(&user_a, "umars").unwrap(); assert_eq!(balance.amount, Uint128::new(432000)); @@ -430,7 +490,7 @@ fn insufficient_mars() { mock_env.increment_by_time(86400); // 24 hours let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::new(432000)); // (86400*5) + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // (86400*5) let balance = mock_env.query_balance(&user_a, "umars").unwrap(); assert_eq!(balance.amount, Uint128::new(432000)); // balance just claimed @@ -441,7 +501,7 @@ fn insufficient_mars() { assert_eq!(balance.amount, Uint128::new(432000)); // balance previously claimed let rewards_balance = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); - assert_eq!(rewards_balance, Uint128::new(432000)); // newly accrued rewards unable to claim + assert_eq!(rewards_balance[0].amount, Uint128::new(432000)); // newly accrued rewards unable to claim } // Rewards are proportionally distributed among users. @@ -487,18 +547,21 @@ fn rewards_distributed_among_users_and_rewards_collector() { incentives.init_asset_incentive_from_current_block( &mut mock_env, "uusdc", + "umars", umars_eps_for_uusdc, incentive_duration_sec, ); incentives.init_asset_incentive_from_current_block( &mut mock_env, "uosmo", + "umars", umars_eps_for_uosmo, incentive_duration_sec, ); incentives.init_asset_incentive_from_current_block( &mut mock_env, "uatom", + "umars", umars_eps_for_uatom, incentive_duration_sec, ); @@ -544,27 +607,28 @@ fn rewards_distributed_among_users_and_rewards_collector() { // rewards-collector accrue rewards let rewards_balance_rc = incentives.query_unclaimed_rewards(&mut mock_env, &rewards_collector.contract_addr); - assert!(!rewards_balance_rc.is_zero()); + assert!(!rewards_balance_rc.is_empty()); // sum of unclaimed rewards should be equal to total umars available for finished incentive let rewards_balance_user_a = incentives.query_unclaimed_rewards(&mut mock_env, &user_a); let rewards_balance_user_b = incentives.query_unclaimed_rewards(&mut mock_env, &user_b); - let total_claimed_rewards = - rewards_balance_rc + rewards_balance_user_a + rewards_balance_user_b; + let total_claimed_rewards = rewards_balance_rc[0].amount + + rewards_balance_user_a[0].amount + + rewards_balance_user_b[0].amount; // ~ values very close (small difference due to rounding errors for index calculation) assert_eq!(total_claimed_rewards.u128(), umars_incentives_amt - 1); // users claim rewards incentives.claim_rewards(&mut mock_env, &user_a).unwrap(); let umars_balance_user_a = mock_env.query_balance(&user_a, "umars").unwrap(); - assert_eq!(umars_balance_user_a.amount, rewards_balance_user_a); + assert_eq!(vec![umars_balance_user_a], rewards_balance_user_a); incentives.claim_rewards(&mut mock_env, &user_b).unwrap(); let umars_balance_user_b = mock_env.query_balance(&user_b, "umars").unwrap(); - assert_eq!(umars_balance_user_b.amount, rewards_balance_user_b); + assert_eq!(vec![umars_balance_user_b], rewards_balance_user_b); // rewards-collector claims rewards rewards_collector.claim_incentive_rewards(&mut mock_env).unwrap(); let umars_balance_rc = mock_env.query_balance(&rewards_collector.contract_addr, "umars").unwrap(); - assert_eq!(umars_balance_rc.amount, rewards_balance_rc); + assert_eq!(vec![umars_balance_rc], rewards_balance_rc); } diff --git a/packages/testing/src/incentives_querier.rs b/packages/testing/src/incentives_querier.rs index 7ff6f7914..a873138b2 100644 --- a/packages/testing/src/incentives_querier.rs +++ b/packages/testing/src/incentives_querier.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; -use cosmwasm_std::{to_binary, Addr, Binary, ContractResult, QuerierResult, Uint128}; +use cosmwasm_std::{to_binary, Addr, Binary, Coin, ContractResult, QuerierResult, Uint128}; use mars_red_bank_types::incentives::QueryMsg; pub struct IncentivesQuerier { /// incentives contract address to be used in queries pub incentives_addr: Addr, - /// maps human address to a specific unclaimed Mars rewards balance (which will be staked with the staking contract and distributed as xMars) - pub unclaimed_rewards_at: HashMap, + /// maps human address and incentive denom to a specific unclaimed rewards balance + pub unclaimed_rewards_at: HashMap<(Addr, String), Uint128>, } impl Default for IncentivesQuerier { @@ -31,12 +31,22 @@ impl IncentivesQuerier { let ret: ContractResult = match query { QueryMsg::UserUnclaimedRewards { - user, - } => match self.unclaimed_rewards_at.get(&(Addr::unchecked(user.clone()))) { - Some(balance) => to_binary(balance).into(), - None => Err(format!("[mock]: no unclaimed rewards for account address {}", &user)) - .into(), - }, + user: _, + start_after_collateral_denom: _, + start_after_incentive_denom: _, + limit: _, + } => { + // TODO: implement pagination + let unclaimed_rewards = self + .unclaimed_rewards_at + .iter() + .map(|((_, denom), amount)| Coin { + denom: denom.clone(), + amount: *amount, + }) + .collect::>(); + to_binary(&unclaimed_rewards).into() + } _ => Err("[mock]: query not supported").into(), }; diff --git a/packages/testing/src/integration/mock_env.rs b/packages/testing/src/integration/mock_env.rs index 0f5b9278b..370f251cb 100644 --- a/packages/testing/src/integration/mock_env.rs +++ b/packages/testing/src/integration/mock_env.rs @@ -92,7 +92,8 @@ impl Incentives { pub fn init_asset_incentive_from_current_block( &self, env: &mut MockEnv, - denom: &str, + collateral_denom: &str, + incentive_denom: &str, emission_per_second: u128, duration: u64, ) { @@ -102,7 +103,8 @@ impl Incentives { env.owner.clone(), self.contract_addr.clone(), &incentives::ExecuteMsg::SetAssetIncentive { - denom: denom.to_string(), + collateral_denom: collateral_denom.to_string(), + incentive_denom: incentive_denom.to_string(), emission_per_second: Some(emission_per_second.into()), start_time: Some(current_block_time), duration: Some(duration), @@ -115,7 +117,8 @@ impl Incentives { pub fn init_asset_incentive( &self, env: &mut MockEnv, - denom: &str, + collateral_denom: &str, + incentive_denom: &str, emission_per_second: u128, start_time: u64, duration: u64, @@ -125,7 +128,8 @@ impl Incentives { env.owner.clone(), self.contract_addr.clone(), &incentives::ExecuteMsg::SetAssetIncentive { - denom: denom.to_string(), + collateral_denom: collateral_denom.to_string(), + incentive_denom: incentive_denom.to_string(), emission_per_second: Some(emission_per_second.into()), start_time: Some(start_time), duration: Some(duration), @@ -138,7 +142,8 @@ impl Incentives { pub fn update_asset_incentive_emission( &self, env: &mut MockEnv, - denom: &str, + collateral_denom: &str, + incentive_denom: &str, emission_per_second: u128, ) { env.app @@ -146,7 +151,8 @@ impl Incentives { env.owner.clone(), self.contract_addr.clone(), &incentives::ExecuteMsg::SetAssetIncentive { - denom: denom.to_string(), + collateral_denom: collateral_denom.to_string(), + incentive_denom: incentive_denom.to_string(), emission_per_second: Some(emission_per_second.into()), start_time: None, duration: None, @@ -160,18 +166,25 @@ impl Incentives { env.app.execute_contract( sender.clone(), self.contract_addr.clone(), - &incentives::ExecuteMsg::ClaimRewards {}, + &incentives::ExecuteMsg::ClaimRewards { + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }, &[], ) } - pub fn query_unclaimed_rewards(&self, env: &mut MockEnv, user: &Addr) -> Uint128 { + pub fn query_unclaimed_rewards(&self, env: &mut MockEnv, user: &Addr) -> Vec { env.app .wrap() .query_wasm_smart( self.contract_addr.clone(), &incentives::QueryMsg::UserUnclaimedRewards { user: user.to_string(), + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, }, ) .unwrap() @@ -430,7 +443,11 @@ impl RewardsCollector { env.app.execute_contract( Addr::unchecked("anyone"), self.contract_addr.clone(), - &mars_red_bank_types::rewards_collector::ExecuteMsg::ClaimIncentiveRewards {}, + &mars_red_bank_types::rewards_collector::ExecuteMsg::ClaimIncentiveRewards { + start_after_collateral_denom: None, + start_after_incentive_denom: None, + limit: None, + }, &[], ) } diff --git a/packages/testing/src/mars_mock_querier.rs b/packages/testing/src/mars_mock_querier.rs index 79894e8e4..9bb655e1b 100644 --- a/packages/testing/src/mars_mock_querier.rs +++ b/packages/testing/src/mars_mock_querier.rs @@ -70,10 +70,16 @@ impl MarsMockQuerier { self.incentives_querier.incentives_addr = address; } - pub fn set_unclaimed_rewards(&mut self, user_address: String, unclaimed_rewards: Uint128) { - self.incentives_querier - .unclaimed_rewards_at - .insert(Addr::unchecked(user_address), unclaimed_rewards); + pub fn set_unclaimed_rewards( + &mut self, + user_address: String, + incentive_denom: &str, + unclaimed_rewards: Uint128, + ) { + self.incentives_querier.unclaimed_rewards_at.insert( + (Addr::unchecked(user_address), incentive_denom.to_string()), + unclaimed_rewards, + ); } pub fn set_query_pool_response(&mut self, pool_id: u64, pool_response: QueryPoolResponse) { diff --git a/packages/types/src/address_provider.rs b/packages/types/src/address_provider.rs index 0ed2301ec..50581c70e 100644 --- a/packages/types/src/address_provider.rs +++ b/packages/types/src/address_provider.rs @@ -3,7 +3,6 @@ use std::{any::type_name, fmt, str::FromStr}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::StdError; use mars_owner::OwnerUpdate; - use strum::EnumIter; #[cw_serde] diff --git a/packages/types/src/incentives.rs b/packages/types/src/incentives.rs index 1ec39a761..56fce763e 100644 --- a/packages/types/src/incentives.rs +++ b/packages/types/src/incentives.rs @@ -14,13 +14,13 @@ pub struct Config { /// Incentive Metadata for a given incentive #[cw_serde] pub struct AssetIncentive { - /// How much MARS per second is emitted to be then distributed to all Red Bank depositors + /// How many incentive tokens per second is emitted to be then distributed to all Red Bank depositors pub emission_per_second: Uint128, /// Start time of the incentive (in seconds) since the UNIX epoch (00:00:00 on 1970-01-01 UTC) pub start_time: u64, /// How many seconds the incentives last pub duration: u64, - /// Total MARS assigned for distribution since the start of the incentive + /// Total incentive tokens assigned for distribution since the start of the incentive pub index: Decimal, /// Last time (in seconds) index was updated pub last_updated: u64, @@ -29,24 +29,31 @@ pub struct AssetIncentive { /// Incentive Metadata for a given incentive denom #[cw_serde] pub struct AssetIncentiveResponse { - /// Asset denom - pub denom: String, - /// How much MARS per second is emitted to be then distributed to all Red Bank depositors + /// The denom for which users get the incentive if they provide collateral in the Red Bank + pub collateral_denom: String, + /// The denom of the token these incentives are paid with + pub incentive_denom: String, + /// How many incentive tokens per second is emitted to be then distributed to all Red Bank depositors pub emission_per_second: Uint128, /// Start time of the incentive (in seconds) since the UNIX epoch (00:00:00 on 1970-01-01 UTC) pub start_time: u64, /// How many seconds the incentives last pub duration: u64, - /// Total MARS assigned for distribution since the start of the incentive + /// Total incentive tokens assigned for distribution since the start of the incentive pub index: Decimal, /// Last time (in seconds) index was updated pub last_updated: u64, } impl AssetIncentiveResponse { - pub fn from(denom: String, ai: AssetIncentive) -> Self { + pub fn from( + collateral_denom: impl Into, + incentive_denom: impl Into, + ai: AssetIncentive, + ) -> Self { Self { - denom, + collateral_denom: collateral_denom.into(), + incentive_denom: incentive_denom.into(), emission_per_second: ai.emission_per_second, start_time: ai.start_time, duration: ai.duration, @@ -73,10 +80,12 @@ pub enum ExecuteMsg { /// If there is no incentive for the asset, all params are required. /// New incentive can be set (rescheduled) if current one has finished (current_block_time > start_time + duration). SetAssetIncentive { - /// Asset denom associated with the incentives - denom: String, - /// How many MARS will be assigned per second to be distributed among all Red Bank - /// depositors + /// The denom of the collatearal token to receive incentives + collateral_denom: String, + /// The denom of the token to give incentives with + incentive_denom: String, + /// How many `incentive_denom` tokens will be assigned per second to be distributed among + /// all Red Bank depositors emission_per_second: Option, /// Start time of the incentive (in seconds) since the UNIX epoch (00:00:00 on 1970-01-01 UTC). start_time: Option, @@ -101,7 +110,16 @@ pub enum ExecuteMsg { /// Claim rewards. MARS rewards accrued by the user will be staked into xMARS before /// being sent. - ClaimRewards {}, + ClaimRewards { + /// Start pagination after this collateral denom + start_after_collateral_denom: Option, + /// Start pagination after this incentive denom. If supplied you must also supply + /// start_after_collateral_denom. + start_after_incentive_denom: Option, + /// The maximum number of results to return. If not set, 5 is used. If larger than 10, + /// 10 is used. + limit: Option, + }, /// Update contract config (only callable by owner) UpdateConfig { @@ -120,23 +138,41 @@ pub enum QueryMsg { #[returns(ConfigResponse)] Config {}, - /// Query info about asset incentive for a given denom + /// Query info about asset incentive for a given collateral and incentive denom pair #[returns(AssetIncentiveResponse)] AssetIncentive { - denom: String, + /// The denom of the token that users supply as collateral to receive incentives + collateral_denom: String, + /// The denom of the token which is used to give incentives with + incentive_denom: String, }, /// Enumerate asset incentives with pagination #[returns(Vec)] AssetIncentives { - start_after: Option, + /// Start pagination after this collateral denom + start_after_collateral_denom: Option, + /// Start pagination after this incentive denom. If supplied you must also supply + /// start_after_collateral_denom. + start_after_incentive_denom: Option, + /// The maximum number of results to return. If not set, 5 is used. If larger than 10, + /// 10 is used. limit: Option, }, /// Query user current unclaimed rewards #[returns(Uint128)] UserUnclaimedRewards { + /// The user address for which to query unclaimed rewards user: String, + /// Start pagination after this collateral denom + start_after_collateral_denom: Option, + /// Start pagination after this incentive denom. If supplied you must also supply + /// start_after_collateral_denom. + start_after_incentive_denom: Option, + /// The maximum number of results to return. If not set, 5 is used. If larger than 10, + /// 10 is used. + limit: Option, }, } diff --git a/packages/types/src/rewards_collector.rs b/packages/types/src/rewards_collector.rs index 721bf72db..3ba98a534 100644 --- a/packages/types/src/rewards_collector.rs +++ b/packages/types/src/rewards_collector.rs @@ -134,7 +134,16 @@ pub enum ExecuteMsg { /// /// We wanted to leave protocol rewards in the red-bank so they continue to work as liquidity (until the bot invokes WithdrawFromRedBank). /// As an side effect to this, if the market is incentivised with MARS tokens, the contract will also accrue MARS token incentives. - ClaimIncentiveRewards {}, + ClaimIncentiveRewards { + /// Start pagination after this collateral denom + start_after_collateral_denom: Option, + /// Start pagination after this incentive denom. If supplied you must also supply + /// start_after_collateral_denom. + start_after_incentive_denom: Option, + /// The maximum number of results to return. If not set, 5 is used. If larger than 10, + /// 10 is used. + limit: Option, + }, } #[cw_serde]