Skip to content

Commit

Permalink
fix(farm_manager): change criteria for expired farms while allowing u…
Browse files Browse the repository at this point in the history
…sers to claim regardless
  • Loading branch information
mantricjavier authored Oct 10, 2024
1 parent 64c14ae commit 49f9878
Show file tree
Hide file tree
Showing 11 changed files with 633 additions and 93 deletions.
12 changes: 10 additions & 2 deletions contracts/farm-manager/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use amm::farm_manager::{
use mantra_utils::validate_contract;

use crate::error::ContractError;
use crate::helpers::{validate_emergency_unlock_penalty, validate_unlocking_duration};
use crate::helpers::{
validate_emergency_unlock_penalty, validate_farm_expiration_time, validate_unlocking_duration,
};
use crate::state::{CONFIG, FARM_COUNTER};
use crate::{farm, manager, position, queries};

Expand All @@ -34,6 +36,9 @@ pub fn instantiate(
// ensure the unlocking duration range is valid
validate_unlocking_duration(msg.min_unlocking_duration, msg.max_unlocking_duration)?;

// ensure the farm expiration time is at least [MONTH_IN_SECONDS]
validate_farm_expiration_time(msg.farm_expiration_time)?;

// due to the circular dependency between the pool manager and the farm manager,
// do not validate the pool manager address here, it has to be updated via the UpdateConfig msg
// once the pool manager is instantiated
Expand All @@ -46,6 +51,7 @@ pub fn instantiate(
max_farm_epoch_buffer: msg.max_farm_epoch_buffer,
min_unlocking_duration: msg.min_unlocking_duration,
max_unlocking_duration: msg.max_unlocking_duration,
farm_expiration_time: msg.farm_expiration_time,
emergency_unlock_penalty: validate_emergency_unlock_penalty(msg.emergency_unlock_penalty)?,
};

Expand Down Expand Up @@ -91,7 +97,7 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::ManageFarm { action } => match action {
FarmAction::Fill { params } => manager::commands::fill_farm(deps, info, params),
FarmAction::Fill { params } => manager::commands::fill_farm(deps, env, info, params),
FarmAction::Close { farm_identifier } => {
manager::commands::close_farm(deps, info, farm_identifier)
}
Expand Down Expand Up @@ -134,6 +140,7 @@ pub fn execute(
max_farm_epoch_buffer,
min_unlocking_duration,
max_unlocking_duration,
farm_expiration_time,
emergency_unlock_penalty,
} => {
cw_utils::nonpayable(&info)?;
Expand All @@ -148,6 +155,7 @@ pub fn execute(
max_farm_epoch_buffer,
min_unlocking_duration,
max_unlocking_duration,
farm_expiration_time,
emergency_unlock_penalty,
)
}
Expand Down
3 changes: 3 additions & 0 deletions contracts/farm-manager/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub enum ContractError {
#[error("The farm has already expired, can't be expanded")]
FarmAlreadyExpired,

#[error("The expiration time for the farm is invalid, must be at least {min} seconds")]
FarmExpirationTimeInvalid { min: u64 },

#[error("The farm doesn't have enough funds to pay out the reward")]
FarmExhausted,

Expand Down
4 changes: 2 additions & 2 deletions contracts/farm-manager/src/farm/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ pub(crate) fn calculate_rewards(
let mut modified_farms: HashMap<String, Uint128> = HashMap::new();

for farm in farms {
// skip expired farms
if farm.is_expired(current_epoch_id) || farm.start_epoch > current_epoch_id {
// skip farms that have not started
if farm.start_epoch > current_epoch_id {
continue;
}

Expand Down
48 changes: 45 additions & 3 deletions contracts/farm-manager/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use std::cmp::Ordering;
use std::collections::HashSet;

use cosmwasm_std::{
ensure, BankMsg, Coin, CosmosMsg, Decimal, MessageInfo, OverflowError, OverflowOperation,
Uint128,
ensure, BankMsg, Coin, CosmosMsg, Decimal, Deps, Env, MessageInfo, OverflowError,
OverflowOperation, Uint128,
};

use amm::coin::{get_factory_token_creator, is_factory_token};
use amm::farm_manager::{Config, FarmParams, Position, DEFAULT_FARM_DURATION};
use amm::constants::MONTH_IN_SECONDS;
use amm::epoch_manager::{EpochResponse, QueryMsg};
use amm::farm_manager::{Config, Farm, FarmParams, Position, DEFAULT_FARM_DURATION};

use crate::ContractError;

Expand Down Expand Up @@ -197,6 +199,20 @@ pub(crate) fn validate_unlocking_duration(
Ok(())
}

/// Validates the farm expiration time
pub(crate) fn validate_farm_expiration_time(
farm_expiration_time: u64,
) -> Result<(), ContractError> {
ensure!(
farm_expiration_time >= MONTH_IN_SECONDS,
ContractError::FarmExpirationTimeInvalid {
min: MONTH_IN_SECONDS
}
);

Ok(())
}

/// Gets the unique LP asset denoms from a list of positions
pub(crate) fn get_unique_lp_asset_denoms_from_positions(positions: Vec<Position>) -> Vec<String> {
positions
Expand All @@ -206,3 +222,29 @@ pub(crate) fn get_unique_lp_asset_denoms_from_positions(positions: Vec<Position>
.into_iter()
.collect()
}

/// Checks if the farm is expired. A farm is considered to be expired if there's no more assets to claim
/// or if there has passed the config.farm_expiration_time since the farm ended.
pub(crate) fn is_farm_expired(
farm: &Farm,
deps: Deps,
env: &Env,
config: &Config,
) -> Result<bool, ContractError> {
let epoch_response: EpochResponse = deps
.querier
// query preliminary_end_epoch + 1 because the farm is preliminary ending at that epoch, including it.
.query_wasm_smart(
config.epoch_manager_addr.to_string(),
&QueryMsg::Epoch {
id: farm.preliminary_end_epoch + 1u64,
},
)?;

let farm_ending_at = epoch_response.epoch.start_time;

Ok(
farm.farm_asset.amount.saturating_sub(farm.claimed_amount) == Uint128::zero()
|| farm_ending_at.plus_seconds(config.farm_expiration_time) < env.block.time,
)
}
40 changes: 25 additions & 15 deletions contracts/farm-manager/src/manager/commands.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
use cosmwasm_std::{
ensure, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, StdError, Storage,
Uint128, Uint64,
ensure, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, StdError,
Storage, Uint128, Uint64,
};

use amm::farm_manager::MIN_FARM_AMOUNT;
use amm::farm_manager::{Curve, Farm, FarmParams};

use crate::helpers::{
assert_farm_asset, process_farm_creation_fee, validate_emergency_unlock_penalty,
validate_farm_epochs, validate_lp_denom, validate_unlocking_duration,
assert_farm_asset, is_farm_expired, process_farm_creation_fee,
validate_emergency_unlock_penalty, validate_farm_epochs, validate_farm_expiration_time,
validate_lp_denom, validate_unlocking_duration,
};
use crate::state::{get_farm_by_identifier, get_farms_by_lp_denom, CONFIG, FARMS, FARM_COUNTER};
use crate::ContractError;

pub(crate) fn fill_farm(
deps: DepsMut,
env: Env,
info: MessageInfo,
params: FarmParams,
) -> Result<Response, ContractError> {
// if a farm_identifier was passed in the params, check if a farm with such identifier
// exists and if the sender is allow to refill it, otherwise create a new farm
if let Some(farm_indentifier) = params.clone().farm_identifier {
let farm_result = get_farm_by_identifier(deps.storage, &farm_indentifier);
// exists and if the sender is allowed to refill it, otherwise create a new farm
if let Some(farm_identifier) = params.clone().farm_identifier {
let farm_result = get_farm_by_identifier(deps.storage, &farm_identifier);

if let Ok(farm) = farm_result {
// the farm exists, try to expand it
return expand_farm(deps, info, farm, params);
return expand_farm(deps, env, info, farm, params);
}
// the farm does not exist, try to create it
}

// if no identifier was passed in the params or if the farm does not exist, try to create the farm
create_farm(deps, info, params)
create_farm(deps, env, info, params)
}

/// Creates a farm with the given params
fn create_farm(
deps: DepsMut,
env: Env,
info: MessageInfo,
params: FarmParams,
) -> Result<Response, ContractError> {
Expand All @@ -60,7 +63,7 @@ fn create_farm(

let (expired_farms, farms): (Vec<_>, Vec<_>) = farms
.into_iter()
.partition(|farm| farm.is_expired(current_epoch.id));
.partition(|farm| is_farm_expired(farm, deps.as_ref(), &env, &config).unwrap_or(false));

let mut messages: Vec<CosmosMsg> = vec![];

Expand Down Expand Up @@ -217,6 +220,7 @@ fn close_farms(
/// Expands a farm with the given params
fn expand_farm(
deps: DepsMut,
env: Env,
info: MessageInfo,
mut farm: Farm,
params: FarmParams,
Expand All @@ -225,14 +229,10 @@ fn expand_farm(
ensure!(farm.owner == info.sender, ContractError::Unauthorized);

let config = CONFIG.load(deps.storage)?;
let current_epoch = amm::epoch_manager::get_current_epoch(
deps.as_ref(),
config.epoch_manager_addr.into_string(),
)?;

// check if the farm has already expired, can't be expanded
ensure!(
!farm.is_expired(current_epoch.id),
!is_farm_expired(&farm, deps.as_ref(), &env, &config)?,
ContractError::FarmAlreadyExpired
);

Expand Down Expand Up @@ -292,6 +292,7 @@ pub(crate) fn update_config(
max_farm_epoch_buffer: Option<u32>,
min_unlocking_duration: Option<u64>,
max_unlocking_duration: Option<u64>,
farm_expiration_time: Option<u64>,
emergency_unlock_penalty: Option<Decimal>,
) -> Result<Response, ContractError> {
cw_ownable::assert_owner(deps.storage, &info.sender)?;
Expand Down Expand Up @@ -337,6 +338,11 @@ pub(crate) fn update_config(
config.min_unlocking_duration = min_unlocking_duration;
}

if let Some(farm_expiration_time) = farm_expiration_time {
validate_farm_expiration_time(farm_expiration_time)?;
config.farm_expiration_time = farm_expiration_time;
}

if let Some(emergency_unlock_penalty) = emergency_unlock_penalty {
config.emergency_unlock_penalty =
validate_emergency_unlock_penalty(emergency_unlock_penalty)?;
Expand Down Expand Up @@ -366,6 +372,10 @@ pub(crate) fn update_config(
"max_unlocking_duration",
config.max_unlocking_duration.to_string(),
),
(
"farm_expiration_time",
config.farm_expiration_time.to_string(),
),
(
"emergency_unlock_penalty",
config.emergency_unlock_penalty.to_string(),
Expand Down
8 changes: 8 additions & 0 deletions contracts/farm-manager/tests/common/suite.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use amm::constants::MONTH_IN_SECONDS;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::{coin, Addr, Coin, Decimal, Empty, StdResult, Timestamp, Uint128, Uint64};
use cw_multi_test::{
Expand Down Expand Up @@ -141,6 +142,7 @@ impl TestingSuite {
14,
86_400,
31_556_926,
MONTH_IN_SECONDS,
Decimal::percent(10), //10% penalty
);

Expand Down Expand Up @@ -208,6 +210,7 @@ impl TestingSuite {
max_farm_epoch_buffer: u32,
min_unlocking_duration: u64,
max_unlocking_duration: u64,
farm_expiration_time: u64,
emergency_unlock_penalty: Decimal,
) -> &mut Self {
let msg = InstantiateMsg {
Expand All @@ -220,6 +223,7 @@ impl TestingSuite {
max_farm_epoch_buffer,
min_unlocking_duration,
max_unlocking_duration,
farm_expiration_time,
emergency_unlock_penalty,
};

Expand Down Expand Up @@ -253,6 +257,7 @@ impl TestingSuite {
max_farm_epoch_buffer: u32,
min_unlocking_duration: u64,
max_unlocking_duration: u64,
farm_expiration_time: u64,
emergency_unlock_penalty: Decimal,
result: impl Fn(anyhow::Result<Addr>),
) -> &mut Self {
Expand All @@ -266,6 +271,7 @@ impl TestingSuite {
max_farm_epoch_buffer,
min_unlocking_duration,
max_unlocking_duration,
farm_expiration_time,
emergency_unlock_penalty,
};

Expand Down Expand Up @@ -320,6 +326,7 @@ impl TestingSuite {
max_farm_epoch_buffer: Option<u32>,
min_unlocking_duration: Option<u64>,
max_unlocking_duration: Option<u64>,
farm_expiration_time: Option<u64>,
emergency_unlock_penalty: Option<Decimal>,
funds: Vec<Coin>,
result: impl Fn(Result<AppResponse, anyhow::Error>),
Expand All @@ -333,6 +340,7 @@ impl TestingSuite {
max_farm_epoch_buffer,
min_unlocking_duration,
max_unlocking_duration,
farm_expiration_time,
emergency_unlock_penalty,
};

Expand Down
Loading

0 comments on commit 49f9878

Please sign in to comment.