🎁Farm Manager
The Farm Manager is a monolithic contract that handles all the farms-related logic for the pools.
How it works
The Farm Manager has two main concepts; a Farm
, containing a reward to be distributed and a Position
, defined as a user's liquidity in a pool locked in the contract.
Users of the Liquidity Hub, when providing liquidity, can opt to lock their LP shares which will in turn send them to the Farm Manager until they are unlocked.
Farms
Creating farms is permissionless, and farms can be perpetual. This means they can be expanded forever. Anyone can create a farm by calling the ManageFarm
message with the FarmAction::Fill
action and paying the farm creation fee, which is sent to the Fee Collector.
Users can decide to provide an identifier, which they can later use to top up or close the farm. If no identifier is provided, the contract will generate one.
The farm identifiers have a prefix, if the farm is created without an identifier it will get the prefix f-
, followed by a number. On the other hand, if the farm is created with a custom identifier, the prefix will be m-
, followed by the custom identifier provided.
Topping up a Farm
To top up a farm, the owner of the farm must call ManageFarm
with the FarmAction::Fill
action. The user must provide the identifier of the farm it intends to top up, including the prefix assigned by the contract. The farm can only be topped up with the same token as the original reward, and the amount must be a multiple of the original reward's amount.
A farm is considered expired after the time defined in the contract configuration as farm_expiration_time
has passed once the farm has ended. Once a farm is expired it cannot be expanded, and expired farms are closed automatically when creating new farms.
Closing a Farm
To close a farm, the owner of the farm or the owner of the contract must call ManageFarm
with the FarmAction::Close
action with the identifier of the farm to be closed, including the prefix assigned by the contract. The farm will be closed, and the remaining tokens will be sent to the owner of the farm.
Reward Distribution
Farm rewards are distributed every epoch, and the user can claim rewards for as long as the farm is live. Based on the parameters used when creating the farm, the rewards will get an emission rate per epoch. The emission rate is the amount of tokens distributed per epoch.
The user rewards are calculated as follows:
where lp
are the unique LP denoms the user has locked in the contract, f
are the active farms, e
the epochs, ranging from the start epoch, which is either the epoch at which the farm starts, or the subsequent epoch after the user's last claimed epoch, until the current epoch which is the one at the moment the claim or query is made. ulpw
is the user's LP weight, tlpw
is the total LP weight, and r
is the reward allocation for a given epoch.
The maximum number of concurrent farms for a given LP denom is defined when the contract is instantiated, and it is stored in the config as max_concurrent_farms
.
Note: If a user has pending rewards the moment a farm is closed, the user loses the possibility to claim those tokens as all unclaimed tokens are reimbursed to the farm owner.
Positions
Positions can be created, expanded (topped up), or withdrawn. This is done via the ManagePosition
message, followed by the desired action, i.e. PositionAction::Create
, PositionAction::Expand
, PositionAction::Close
or PositionAction::Withdraw
.
When a user creates a position, it must provide an unlocking duration and an identifier (optional). The longer the unlocking duration, the higher the weight of the LP position, which equates to higher rewards. Choosing an unlocking duration of one day gives the user a multiplier of 1x, while the maximum period of 365 days gives the user a multiplier of 16x.
Like the farm identifiers, the position identifiers also get a prefix assigned by the contract. If the position is created without a custom identifier, it will get the prefix p-
, followed by a number. On the other hand, if the position is created with a custom identifier, the prefix will be u-
, followed by the custom identifier provided.
The unlocking duration is the time it takes in seconds to unlock the position, which is necessary to withdraw the LP tokens from the contract.
There's a limit of 100 open positions, as well as 100 closed positions per user.
Note: The unlocking duration is defined as how long the user needs to wait after hitting the Close
action to be able to withdraw the LP tokens from the contract. Not to be confused with how long the LP tokens are locked in the farm. The LP tokens are locked in the farm for as long as the user has a position in the farm manager.
Topping up a Position
To expand a position, the user must call ManagePosition
with the PositionAction::Expand
action using the position identifier of the position to expand. Expanding a position only involves adding more LP tokens to the position. The unlocking period of the original position is kept.
Only open positions can be expanded.
Closing a Position
When a user creates a position, the LP tokens are locked in the contract. The user can't withdraw them until the unlocking duration is complete. The clock starts ticking the moment the user closes the position.
Closing a position is done by calling ManagePosition
with the PositionAction::Close
action. The user must provide the identifier of the position to be closed. Once this action is triggered, the Position.open
state is set to false, and expiring_at
is set to the block height after which the position will be able to be withdrawn.
It is possible to close a position partially by providing the amount of LP tokens to remove from the position.
Withdrawing a Position
Once the unlocking duration is complete after closing a position, the user can withdraw the LP tokens from the contract by calling the ManagePosition
with the PositionAction::Withdraw
action. Alternatively, if the user doesn't want to wait for the unlocking duration to complete, it is possible to do an emergency withdrawal by passing true
on the emergency_unlock
parameter. This will unlock and withdraw the position immediately, and the user will pay a penalty fee in case the unlocking period has not passed. Half of the penalty is distributed among the farm owners of the given LP, while the other half goes to the Fee Collector.
Once the user closes and withdraws the position, they receive their LP tokens back.
Note: The emergency unlock feature can be used on both open and closed positions. However, if this feature is used, the user renounces all unclaimed rewards for the given position.
Claiming Farm Rewards
Users can claim farm rewards from active farms for their LP tokens, only if they have a position in the farm manager. Users can only claim rewards for future epochs, i.e. after the epoch in which the position was created.
Farm rewards are distributed based on the user's share of the total LP tokens in the contract. So if there's a total of 100 LP tokens in the contract, and a user has 10 LP tokens, the user will receive 10% of the rewards for that epoch, for that given farm.
To claim rewards, the user must call the Claim
message. Once that's done, the contract will save the epoch in which the claim was made in LAST_CLAIMED_EPOCH
, and will sync the user's LP weight history saved in LP_WEIGHT_HISTORY
. This helps computing the rewards for the user.
Instantiate
Instantiates an instance of the farm manager contract
owner
String
The owner of the contract.
epoch_manager_addr
String
The epoch manager address, where the epochs are managed.
fee_collector_addr
String
The address of the fee collector, where protocol fees go.
pool_manager_addr
String
The address of the pool manager, used to verify the LP tokens locked in the contract.
create_farm_fee
Coin
The fee that must be paid to create a farm.
max_concurrent_farms
u32
The maximum amount of farms that can exist for a single LP token at a time.
max_farm_epoch_buffer
u32
New farms are allowed to start up to current_epoch + start_epoch_buffer
into the future.
min_unlocking_duration
u64
The minimum amount of time that a user can lock their tokens for. In seconds.
max_unlocking_duration
u64
The maximum amount of time that a user can lock their tokens for. In seconds.
farm_expiration_time
u64
The amount of time after which a farm is considered to be expired after it ended. In seconds. Minimum a month. Once a farm is expired it cannot be expanded, and expired farms can be closed.
emergency_unlock_penalty
Decimal
The penalty for unlocking a position before the unlocking duration finishes. In percentage.
ExecuteMsg
ManageFarm
Manages a farm based on the action, which can be:
Fill: Fills a farm. If the farm doesn't exist, it creates a new one. If it exists already, it expands it given the sender created the original farm and the params are correct.
Close: Closes a farm with the given identifier. If the farm has expired, anyone can close it. Otherwise, only the farm creator or the owner of the contract can close a farm.
Once created, farms are stored in the FARMS
map.
lp_denom
String
The LP asset denom to create the farm for.
start_epoch
Option<u64>
The epoch at which the farm will start. If unspecified, it will start at the current epoch.
preliminary_end_epoch
Option<u64>
The epoch at which the farm should preliminarily end (if it's not expanded). If unspecified, the farm will default to end at 14 epochs from the current one.
curve
Option<Curve>
The type of distribution curve. If unspecified, the distribution will be linear.
farm_asset
Coin
The asset to be distributed in this farm.
farm_identifier
Option<String>
If set, it will be used to identify the farm.
ManagePosition
Manages a position based on the action, which can be:
Create: Creates a position.
Expand: Expands a position.
Close: Closes an existing position. The position stops earning farm rewards.
Withdraw: Withdraws the LP tokens from a position after the position has been closed and the unlocking duration has passed.
Positions are stored in the POSITIONS
map.
identifier
Option<String>
The identifier of the position.
unlocking_duration
u64
The time it takes in seconds to unlock this position.
receiver
Option<String>
The receiver for the position. If left empty, defaults to the message sender.
Claim
Claims the rewards for the user.
UpdateConfig
Updates the contract configuration.
fee_collector_addr
Option<String>
The address to of the fee collector, to send fees to.
epoch_manager_addr
Option<String>
The epoch manager address, where the epochs are managed.
pool_manager_addr
Option<String>
The pool manager address.
create_farm_fee
Option<Coin>
The fee that must be paid to create a farm.
max_concurrent_farms
Option<u32>
The maximum amount of farms that can exist for a single LP token at a time.
max_farm_epoch_buffer
Option<u32>
The maximum amount of epochs in the future a new farm is allowed to start in.
min_unlocking_duration
Option<u64>
The minimum amount of time that a user can lock their tokens for. In seconds.
max_unlocking_duration
Option<u64>
The maximum amount of time that a user can lock their tokens for. In seconds.
farm_expiration_time
Option<u64>
The amount of time after which a farm is considered to be expired after it ended. In seconds. Minimum a month. Once a farm is expired it cannot be expanded, and expired farms can be closed.
emergency_unlock_penalty
Option<Decimal>
The penalty for unlocking a position before the unlocking duration finishes. In percentage.
UpdateOwnership(::cw_ownable::Action)
Implements cw_ownable
. Updates the contract's ownership. ::cw_ownable::Action
can be TransferOwnership
, AcceptOwnership
and RenounceOwnership
.
Note: This is a cw_ownable
message.
Propose to transfer the contract's ownership to another account, optionally with an expiry time. Can only be called by the contract's current owner. Any existing pending ownership transfer is overwritten.
new_owner
String
The new owner proposed,
expiry
Option<Expiration>
Optional expiration time parameter.
QueryMsg
Config
Returns the configuration of the contract.
Farms
Retrieves the configuration of the manager.
filter_by
Option<FarmsBy>
An optional parameter specifying what to filter farms by. Can be either the farm identifier, lp denom or the farm asset.
start_after
Option<String>
An optional parameter specifying what farm (identifier) to start searching after.
limit
Option<u32>
The amount of farms to return. If unspecified, will default to a value specified by the contract.
Positions
Retrieves the positions for an address.
filter_by
Option<PositionBy>
An optional parameter specifying what to filter positions by. Can be either the position identifier or receiver.
open_state
Option<bool>
An optional parameter specifying to return only positions that match the given open state. If true, it will return open positions. If false, it will return closed positions.
start_after
Option<String>
An optional parameter specifying what position (identifier) to start searching after.
limit
Option<u32>
The amount of positions to return. If unspecified, will default to a value specified by the contract.
Rewards
Retrieves the rewards for an address.
address
String
The address to get all the farm rewards for.
LpWeight
Retrieves the total LP weight in the contract for a given denom on a given epoch.
address
String
The address to get the LP weight for.
denom
String
The denom to get the total LP weight for.
epoch_id
u64
The epoch id to get the LP weight for.
Ownership
Returns the ownership of the contract.
Note: This is a cw_ownable
query.
MigrateMsg
Message to migrate the contract to a new code ID.
Last updated