🎁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
Key | Type | Description |
---|---|---|
| String | The owner of the contract. |
| String | The epoch manager address, where the epochs are managed. |
| String | The address of the fee collector, where protocol fees go. |
| String | The address of the pool manager, used to verify the LP tokens locked in the contract. |
| Coin | The fee that must be paid to create a farm. |
| u32 | The maximum amount of farms that can exist for a single LP token at a time. |
| u32 | New farms are allowed to start up to |
| u64 | The minimum amount of time that a user can lock their tokens for. In seconds. |
| u64 | The maximum amount of time that a user can lock their tokens for. In seconds. |
| 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. |
| 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.
Key | Type | Description |
---|---|---|
| String | The LP asset denom to create the farm for. |
| Option<u64> | The epoch at which the farm will start. If unspecified, it will start at the current 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. |
| Option<Curve> | The type of distribution curve. If unspecified, the distribution will be linear. |
| Coin | The asset to be distributed in this farm. |
| 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.
Key | Type | Description |
---|---|---|
| Option<String> | The identifier of the position. |
| u64 | The time it takes in seconds to unlock this position. |
| 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.
Key | Type | Description |
---|---|---|
| Option<String> | The address to of the fee collector, to send fees to. |
| Option<String> | The epoch manager address, where the epochs are managed. |
| Option<String> | The pool manager address. |
| Option<Coin> | The fee that must be paid to create a farm. |
| Option<u32> | The maximum amount of farms that can exist for a single LP token at a time. |
| Option<u32> | The maximum amount of epochs in the future a new farm is allowed to start in. |
| Option<u64> | The minimum amount of time that a user can lock their tokens for. In seconds. |
| Option<u64> | The maximum amount of time that a user can lock their tokens for. In seconds. |
| 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. |
| 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.
Key | Type | Description |
---|---|---|
| String | The new owner proposed, |
| Option<Expiration> | Optional expiration time parameter. |
QueryMsg
Config
Returns the configuration of the contract.
Farms
Retrieves the configuration of the manager.
Key | Type | Description |
---|---|---|
| Option<FarmsBy> | An optional parameter specifying what to filter farms by. Can be either the farm identifier, lp denom or the farm asset. |
| Option<String> | An optional parameter specifying what farm (identifier) to start searching after. |
| 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.
Key | Type | Description |
---|---|---|
| Option<PositionBy> | An optional parameter specifying what to filter positions by. Can be either the position identifier or receiver. |
| 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. |
| Option<String> | An optional parameter specifying what position (identifier) to start searching after. |
| 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.
Key | Type | Description |
---|---|---|
| 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.
Key | Type | Description |
---|---|---|
| String | The address to get the LP weight for. |
| String | The denom to get the total LP weight for. |
| 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