1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
use std::collections::HashMap;
use std::fmt::Display;
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use cw_ownable::{cw_ownable_execute, cw_ownable_query};
/// The instantiation message
#[cw_serde]
pub struct InstantiateMsg {
/// The owner of the contract
pub owner: String,
/// The epoch manager address, where the epochs are managed
pub epoch_manager_addr: String,
/// The fee collector address, where protocol fees are stored
pub fee_collector_addr: String,
/// The pool manager address, where pools are created
pub pool_manager_addr: String,
/// The fee that must be paid to create a farm.
pub create_farm_fee: Coin,
/// The maximum amount of farms that can exist for a single LP token at a time.
pub max_concurrent_farms: u32,
/// New farms are allowed to start up to `current_epoch + start_epoch_buffer` into the future.
pub max_farm_epoch_buffer: u32,
/// The minimum amount of time that a user can lock their tokens for. In seconds.
pub min_unlocking_duration: u64,
/// The maximum amount of time that a user can lock their tokens for. In seconds.
pub max_unlocking_duration: u64,
/// The amount of time after which a farm is considered to be expired after it ended. In seconds.
/// Once a farm is expired it cannot be expanded, and expired farms can be closed
pub farm_expiration_time: u64,
/// The penalty for unlocking a position before the unlocking duration finishes. In percentage.
pub emergency_unlock_penalty: Decimal,
}
/// The execution messages
#[cw_ownable_execute]
#[cw_serde]
pub enum ExecuteMsg {
/// Manages a farm based on the action, which can be:
/// - Fill: Creates or expands a farm.
/// - Close: Closes an existing farm.
ManageFarm { action: FarmAction },
/// Manages a position based on the action, which can be:
/// - Fill: Creates or expands a position.
/// - Close: Closes an existing position.
ManagePosition { action: PositionAction },
/// Claims the rewards for the user
Claim {
/// The epoch until which the rewards should be claimed. If none is provided,
/// it will claim until the current epoch.
until_epoch: Option<EpochId>,
},
/// Updates the config of the contract
UpdateConfig {
/// The fee collector address, where protocol fees are stored
fee_collector_addr: Option<String>,
/// The epoch manager address, where the epochs are managed
epoch_manager_addr: Option<String>,
/// The pool manager address, where pools are created
pool_manager_addr: Option<String>,
/// The fee that must be paid to create a farm.
create_farm_fee: Option<Coin>,
/// The maximum amount of farms that can exist for a single LP token at a time.
max_concurrent_farms: Option<u32>,
/// The maximum amount of epochs in the future a new farm is allowed to start in.
max_farm_epoch_buffer: Option<u32>,
/// The minimum amount of time that a user can lock their tokens for. In seconds.
min_unlocking_duration: Option<u64>,
/// The maximum amount of time that a user can lock their tokens for. In seconds.
max_unlocking_duration: Option<u64>,
/// The amount of time after which a farm is considered to be expired after it ended. In seconds.
/// Once a farm is expired it cannot be expanded, and expired farms can be closed
farm_expiration_time: Option<u64>,
/// The penalty for unlocking a position before the unlocking duration finishes. In percentage.
emergency_unlock_penalty: Option<Decimal>,
},
}
/// The migrate message
#[cw_serde]
pub struct MigrateMsg {}
/// The query messages
#[cw_ownable_query]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
/// Retrieves the configuration of the manager.
#[returns(Config)]
Config {},
/// Retrieves farms in the contract. It is possible to filter by [FarmsBy] and to paginate the results.
#[returns(FarmsResponse)]
Farms {
/// An optional parameter specifying what to filter farms by.
/// Can be either the farm identifier, lp denom or the farm asset.
filter_by: Option<FarmsBy>,
/// An optional parameter specifying what farm (identifier) to start searching after.
start_after: Option<String>,
/// The amount of farms to return.
/// If unspecified, will default to a value specified by the contract.
limit: Option<u32>,
},
/// Retrieves the positions for an address.
#[returns(PositionsResponse)]
Positions {
/// An optional parameter specifying what to filter positions by.
filter_by: Option<PositionsBy>,
/// 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.
open_state: Option<bool>,
/// An optional parameter specifying what position (identifier) to start searching after.
start_after: Option<String>,
/// The amount of positions to return.
/// If unspecified, will default to a value specified by the contract.
limit: Option<u32>,
},
/// Retrieves the rewards for an address.
#[returns(RewardsResponse)]
Rewards {
/// The address to get all the farm rewards for.
address: String,
/// The epoch until which the rewards should be queried. If none is provided,
/// it will query until the current epoch.
until_epoch: Option<EpochId>,
},
/// Retrieves the total LP weight in the contract for a given denom on a given epoch.
#[returns(LpWeightResponse)]
LpWeight {
/// The address to get the LP weight for.
address: String,
/// The denom to get the total LP weight for.
denom: String,
/// The epoch id to get the LP weight for.
epoch_id: EpochId,
},
}
/// Enum to filter farms by identifier, lp denom or the farm asset. Used in the farms query.
#[cw_serde]
pub enum FarmsBy {
Identifier(String),
LpDenom(String),
FarmAsset(String),
}
/// Enum to filter positions by identifier or receiver. Used in the positions query.
#[cw_serde]
pub enum PositionsBy {
Identifier(String),
Receiver(String),
}
/// Configuration for the contract (manager)
#[cw_serde]
pub struct Config {
/// The fee collector address, where protocol fees are stored
pub fee_collector_addr: Addr,
/// The epoch manager address, where the epochs are managed
pub epoch_manager_addr: Addr,
/// The pool manager address, where pools are created
pub pool_manager_addr: Addr,
/// The fee that must be paid to create a farm.
pub create_farm_fee: Coin,
/// The maximum amount of farms that can exist for a single LP token at a time.
pub max_concurrent_farms: u32,
/// The maximum amount of epochs in the future a new farm is allowed to start in.
pub max_farm_epoch_buffer: u32,
/// The minimum amount of time that a user can lock their tokens for. In seconds.
pub min_unlocking_duration: u64,
/// The maximum amount of time that a user can lock their tokens for. In seconds.
pub max_unlocking_duration: u64,
/// The amount of time after which a farm is considered to be expired after it ended. In seconds.
/// Once a farm is expired it cannot be expanded, and expired farms can be closed
pub farm_expiration_time: u64,
/// The penalty for unlocking a position before the unlocking duration finishes. In percentage.
pub emergency_unlock_penalty: Decimal,
}
/// Parameters for creating farms
#[cw_serde]
pub struct FarmParams {
/// The LP asset denom to create the farm for.
pub lp_denom: String,
/// The epoch at which the farm will start. If unspecified, it will start at the
/// current epoch.
pub start_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.
pub preliminary_end_epoch: Option<u64>,
/// The type of distribution curve. If unspecified, the distribution will be linear.
pub curve: Option<Curve>,
/// The asset to be distributed in this farm.
pub farm_asset: Coin,
/// If set, it will be used to identify the farm.
pub farm_identifier: Option<String>,
}
#[cw_serde]
pub enum FarmAction {
/// 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.
Fill {
/// The parameters for the farm to fill.
params: FarmParams,
},
//// 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.
Close {
/// The farm identifier to close.
farm_identifier: String,
},
}
#[cw_serde]
pub enum PositionAction {
/// Creates a position.
Create {
/// The identifier of the position.
identifier: Option<String>,
/// The time it takes in seconds to unlock this position. This is used to identify the position to fill.
unlocking_duration: u64,
/// The receiver for the position.
/// If left empty, defaults to the message sender.
receiver: Option<String>,
},
/// Expands a position.
Expand {
/// The identifier of the position.
identifier: String,
},
/// Closes an existing position. The position stops earning farm rewards.
Close {
/// The identifier of the position.
identifier: String,
/// The asset to add to the position. If not set, the position will be closed in full. If not, it could be partially closed.
lp_asset: Option<Coin>,
},
/// Withdraws the LP tokens from a position after the position has been closed and the unlocking duration has passed.
Withdraw {
/// The identifier of the position.
identifier: String,
/// Whether to unlock the position in an emergency. If set to true, the position will be
/// unlocked immediately. If the position has not expired, it will pay a penalty.
emergency_unlock: Option<bool>,
},
}
// type for the epoch id
pub type EpochId = u64;
/// Represents a farm.
#[cw_serde]
pub struct Farm {
/// The ID of the farm.
pub identifier: String,
/// The account which opened the farm and can manage it.
pub owner: Addr,
/// The LP asset denom to create the farm for.
pub lp_denom: String,
/// The asset the farm was created to distribute.
pub farm_asset: Coin,
/// The amount of the `farm_asset` that has been claimed so far.
pub claimed_amount: Uint128,
/// The amount of the `farm_asset` that is to be distributed every epoch.
pub emission_rate: Uint128,
/// The type of curve the farm has.
pub curve: Curve,
/// The epoch at which the farm starts.
pub start_epoch: EpochId,
/// The epoch at which the farm will preliminary end (in case it's not expanded).
pub preliminary_end_epoch: EpochId,
}
#[cw_serde]
pub enum Curve {
/// A linear curve that releases assets uniformly over time.
Linear,
}
impl std::fmt::Display for Curve {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Curve::Linear => write!(f, "linear"),
}
}
}
/// Represents an LP position.
#[cw_serde]
pub struct Position {
/// The identifier of the position.
pub identifier: String,
/// The amount of LP tokens that are put up to farm rewards.
pub lp_asset: Coin,
/// Represents the amount of time in seconds the user must wait after unlocking for the LP tokens to be released.
pub unlocking_duration: u64,
/// If true, the position is open. If false, the position is closed.
pub open: bool,
/// The block height at which the position, after being closed, can be withdrawn.
pub expiring_at: Option<u64>,
/// The owner of the position.
pub receiver: Addr,
}
impl Position {
pub fn is_expired(&self, current_time: u64) -> bool {
self.expiring_at.is_some() && self.expiring_at.unwrap() <= current_time
}
}
impl Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Position: {} - LP Asset: {} - Unlocking Duration: {} - Open: {} - Receiver: {} - Expiring At: {}",
self.identifier, self.lp_asset, self.unlocking_duration, self.open, self.receiver, self.expiring_at.unwrap_or(u64::MAX)
)
}
}
#[cw_serde]
pub enum RewardsResponse {
/// The rewards response
RewardsResponse {
/// The rewards that is available to a user if they executed the `claim` function at this point.
total_rewards: Vec<Coin>,
/// The rewards per LP denom that is available to a user if they executed the `claim` function at this point.
rewards_per_lp_denom: Vec<(String, Vec<Coin>)>,
},
/// Rewards response used internally when querying the rewards
QueryRewardsResponse {
/// The rewards that is available to a user if they executed the `claim` function at this point.
rewards: Vec<Coin>,
},
/// Returned when claiming rewards
ClaimRewards {
/// The rewards that is available to a user if they executed the `claim` function at this point.
rewards: Vec<Coin>,
/// The rewards that were claimed on each farm, if any.
modified_farms: HashMap<String, Uint128>,
},
}
/// Minimum amount of an asset to create a farm with
pub const MIN_FARM_AMOUNT: Uint128 = Uint128::new(1_000u128);
/// Default farm duration in epochs
pub const DEFAULT_FARM_DURATION: u64 = 14u64;
/// The response for the farms query
#[cw_serde]
pub struct FarmsResponse {
/// The list of farms
pub farms: Vec<Farm>,
}
#[cw_serde]
pub struct PositionsResponse {
/// All the positions a user has.
pub positions: Vec<Position>,
}
/// The response for the LP weight query
#[cw_serde]
pub struct LpWeightResponse {
/// The total lp weight in the contract
pub lp_weight: Uint128,
/// The epoch id corresponding to the lp weight in the contract
pub epoch_id: EpochId,
}