diff --git a/contracts/alliance-hub/src/contract.rs b/contracts/alliance-hub/src/contract.rs index d0d31bb..60b8ab2 100644 --- a/contracts/alliance-hub/src/contract.rs +++ b/contracts/alliance-hub/src/contract.rs @@ -238,7 +238,7 @@ fn unstake(deps: DepsMut, info: MessageInfo, asset: Asset) -> Result Err(ContractError::InsufficientBalance {}), + None => Err(ContractError::AssetNotStaked {}), } }, )?; @@ -308,6 +308,14 @@ fn _claim_reward( asset: AssetInfo, ) -> Result { let asset_key = AssetInfoKey::from(&asset); + // If the user did not stake the asset, do nothing and return 0 + if BALANCES + .load(storage, (user.clone(), asset_key.clone())) + .is_err() + { + return Ok(Uint128::zero()); + } + let user_reward_rate = USER_ASSET_REWARD_RATE.load(storage, (user.clone(), asset_key.clone())); let asset_reward_rate = ASSET_REWARD_RATE.load(storage, asset_key.clone())?; diff --git a/contracts/alliance-hub/src/query.rs b/contracts/alliance-hub/src/query.rs index 63da2f8..df6b871 100644 --- a/contracts/alliance-hub/src/query.rs +++ b/contracts/alliance-hub/src/query.rs @@ -76,10 +76,10 @@ fn get_pending_rewards(deps: Deps, asset_query: AssetQuery) -> StdResult let config = CONFIG.load(deps.storage)?; let addr = deps.api.addr_validate(&asset_query.address)?; let key = (addr, AssetInfoKey::from(asset_query.asset.clone())); - let user_reward_rate = USER_ASSET_REWARD_RATE.load(deps.storage, key.clone())?; let asset_reward_rate = ASSET_REWARD_RATE.load(deps.storage, AssetInfoKey::from(asset_query.asset.clone()))?; - let user_balance = BALANCES.load(deps.storage, key.clone())?; + let user_reward_rate = USER_ASSET_REWARD_RATE.load(deps.storage, key.clone()).unwrap_or(asset_reward_rate); + let user_balance = BALANCES.load(deps.storage, key.clone()).unwrap_or(Uint128::zero()); let unclaimed_rewards = UNCLAIMED_REWARDS .load(deps.storage, key) .unwrap_or(Uint128::zero()); @@ -131,7 +131,7 @@ fn get_all_pending_rewards(deps: Deps, query: AllPendingRewardsQuery) -> StdResu let user_balance = BALANCES.load( deps.storage, (addr.clone(), AssetInfoKey::from(asset.clone())), - )?; + ).unwrap_or(Uint128::zero()); let unclaimed_rewards = UNCLAIMED_REWARDS .load( deps.storage, diff --git a/contracts/alliance-hub/src/tests/rewards.rs b/contracts/alliance-hub/src/tests/rewards.rs index cf8be61..40a5c47 100644 --- a/contracts/alliance-hub/src/tests/rewards.rs +++ b/contracts/alliance-hub/src/tests/rewards.rs @@ -334,6 +334,86 @@ fn claim_user_rewards() { ); } +#[test] +fn claim_user_rewards_without_stake() { + let mut deps = mock_dependencies_with_balance(&[coin(2000000, "uluna")]); + setup_contract(deps.as_mut()); + set_alliance_asset(deps.as_mut()); + whitelist_assets( + deps.as_mut(), + HashMap::from([( + "chain-1".to_string(), + vec![AssetInfo::Native("aWHALE".to_string())], + )]), + ); + stake(deps.as_mut(), "user1", 1000000, "aWHALE"); + + ASSET_REWARD_DISTRIBUTION + .save( + deps.as_mut().storage, + &vec![ + AssetDistribution { + asset: AssetInfo::Native("aWHALE".to_string()), + distribution: Decimal::percent(50), + }, + AssetDistribution { + asset: AssetInfo::Native("bWHALE".to_string()), + distribution: Decimal::percent(50), + }, + ], + ) + .unwrap(); + TEMP_BALANCE + .save(deps.as_mut().storage, &Uint128::new(1000000)) + .unwrap(); + execute( + deps.as_mut(), + mock_env(), + mock_info("cosmos2contract", &[]), + ExecuteMsg::UpdateRewardsCallback {}, + ) + .unwrap(); + + let res = claim_rewards(deps.as_mut(), "user2", "aWHALE"); + assert_eq!( + res, + Response::new() + .add_attributes(vec![ + ("action", "claim_rewards"), + ("user", "user2"), + ("asset", "native:aWHALE"), + ("reward_amount", "0"), + ]) + ); + + USER_ASSET_REWARD_RATE + .load( + deps.as_ref().storage, + ( + Addr::unchecked("user2"), + AssetInfoKey::from(AssetInfo::Native("aWHALE".to_string())), + ), + ) + .unwrap_err(); + + let rewards = query_rewards(deps.as_ref(), "user2", "aWHALE"); + assert_eq!( + rewards, + PendingRewardsRes { + rewards: Uint128::new(0), + reward_asset: AssetInfo::Native("uluna".to_string()), + staked_asset: AssetInfo::Native("aWHALE".to_string()), + } + ); + + let all_rewards = query_all_rewards(deps.as_ref(), "user2"); + assert_eq!( + all_rewards, + vec![] + ); +} + + #[test] fn claim_user_rewards_after_staking() { let mut deps = mock_dependencies_with_balance(&[coin(2000000, "uluna")]); diff --git a/packages/alliance-protocol/src/error.rs b/packages/alliance-protocol/src/error.rs index 244b935..7b3f795 100644 --- a/packages/alliance-protocol/src/error.rs +++ b/packages/alliance-protocol/src/error.rs @@ -39,4 +39,7 @@ pub enum ContractError { #[error("Invalid total distribution: {0}")] InvalidTotalDistribution(Decimal), + + #[error("Asset not staked")] + AssetNotStaked {}, }