Skip to main content

ve3_shared/
msgs_voting_escrow.rs

1use crate::adapters::eris::ErisHub;
2use crate::helpers::time::Time;
3use cosmwasm_schema::{cw_serde, QueryResponses};
4use cosmwasm_std::{Addr, Binary, Decimal, Empty, QuerierWrapper, StdResult, Uint128};
5use cw20::{Cw20ReceiveMsg, Expiration};
6#[allow(unused_imports)]
7use cw721::{
8  AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, NftInfoResponse,
9  NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse,
10};
11#[allow(unused_imports)]
12use cw721_base::MinterResponse;
13use cw721_base::QueryMsg as CW721QueryMsg;
14use cw721_base::{state::TokenInfo, ExecuteMsg as CW721ExecuteMsg};
15use cw_address_like::AddressLike;
16use cw_asset::{Asset, AssetInfoBase};
17use std::fmt;
18
19/// This structure stores general parameters for the voting escrow contract.
20#[cw_serde]
21pub struct InstantiateMsg {
22  // global address config
23  pub global_config_addr: String,
24  // assets that are allowed to be locked including a config of how to calculate base power
25  pub deposit_assets: Vec<DepositAsset<String>>,
26}
27
28#[cw_serde]
29pub struct DepositAsset<T: AddressLike> {
30  pub info: AssetInfoBase<T>,
31  pub config: AssetInfoConfig,
32}
33
34/// This structure describes the execute functions in the contract.
35#[cw_serde]
36pub enum ExecuteMsg {
37  /// USER
38  /// Create a vAMP position and lock ampLP for `time` amount of time
39  CreateLock {
40    time: Option<u64>,
41    recipient: Option<String>,
42  },
43  MergeLock {
44    token_id: String,
45    token_id_add: String,
46  },
47  SplitLock {
48    token_id: String,
49    amount: Uint128,
50    recipient: Option<String>,
51  },
52  /// Extend the lockup time for your staked ampLP. For an expired lock, it will always start from the current period.
53  ExtendLockTime {
54    time: u64,
55    token_id: String,
56  },
57  /// Add more ampLP to your vAMP position
58  ExtendLockAmount {
59    token_id: String,
60  },
61
62  LockPermanent {
63    token_id: String,
64  },
65
66  UnlockPermanent {
67    token_id: String,
68  },
69
70  /// Withdraw ampLP from the voting escrow contract
71  Withdraw {
72    token_id: String,
73  },
74  /// Implements the Cw20 receiver interface
75  Receive(Cw20ReceiveMsg),
76
77  // OPERATOR
78  /// Add or remove accounts from the blacklist
79  UpdateBlacklist {
80    append_addrs: Option<Vec<String>>,
81    remove_addrs: Option<Vec<String>>,
82  },
83  /// Update config
84  UpdateConfig {
85    // assets that are allowed to be locked including a config of how to calculate base power
86    // for now removal is not supported
87    append_deposit_assets: Option<Vec<DepositAsset<String>>>,
88
89    push_update_contracts: Option<Vec<String>>,
90    // allows withdrawals of tokens.
91    decommissioned: Option<bool>,
92  },
93
94  /// CW721 standard message
95  /// Transfer is a base message to move a token to another account without triggering actions
96  TransferNft {
97    recipient: String,
98    token_id: String,
99  },
100  /// Send is a base message to transfer a token to a contract and trigger an action
101  /// on the receiving contract.
102  SendNft {
103    contract: String,
104    token_id: String,
105    msg: Binary,
106  },
107  /// Burn an NFT the sender has access to
108  Burn {
109    token_id: String,
110  },
111
112  /// Allows operator to transfer / send the token from the owner's account.
113  /// If expiration is set, then this allowance has a time/height limit
114  Approve {
115    spender: String,
116    token_id: String,
117    expires: Option<Expiration>,
118  },
119  /// Remove previously granted Approval
120  Revoke {
121    spender: String,
122    token_id: String,
123  },
124  /// Allows operator to transfer / send any token from the owner's account.
125  /// If expiration is set, then this allowance has a time/height limit
126  ApproveAll {
127    operator: String,
128    expires: Option<Expiration>,
129  },
130  /// Remove previously granted ApproveAll permission
131  RevokeAll {
132    operator: String,
133  },
134}
135
136#[cw_serde]
137pub enum ReceiveMsg {
138  ExtendLockAmount {
139    token_id: String,
140  },
141  CreateLock {
142    time: Option<u64>,
143    recipient: Option<String>,
144  },
145}
146
147pub type VeNftCollection<'a> = cw721_base::Cw721Contract<'a, Extension, Empty, Empty, Empty>;
148pub type VeNftInfo = TokenInfo<Metadata>;
149
150#[cw_serde]
151pub struct Trait {
152  pub display_type: Option<String>,
153  pub trait_type: String,
154  pub value: String,
155}
156
157pub type Extension = Metadata;
158
159// see: https://docs.opensea.io/docs/metadata-standards
160#[cw_serde]
161pub struct Metadata {
162  pub image: Option<String>,
163  // pub image_data: Option<String>,
164  // pub external_url: Option<String>,
165  pub description: Option<String>,
166  pub name: Option<String>,
167  pub attributes: Option<Vec<Trait>>,
168  // pub background_color: Option<String>,
169  // pub animation_url: Option<String>,
170  // pub youtube_url: Option<String>,
171}
172
173impl From<ExecuteMsg> for CW721ExecuteMsg<Metadata, Empty> {
174  fn from(msg: ExecuteMsg) -> CW721ExecuteMsg<Metadata, Empty> {
175    match msg {
176      ExecuteMsg::Approve {
177        spender,
178        token_id,
179        expires,
180      } => CW721ExecuteMsg::Approve {
181        spender,
182        token_id,
183        expires,
184      },
185      ExecuteMsg::Revoke {
186        spender,
187        token_id,
188      } => CW721ExecuteMsg::Revoke {
189        spender,
190        token_id,
191      },
192      ExecuteMsg::ApproveAll {
193        operator,
194        expires,
195      } => CW721ExecuteMsg::ApproveAll {
196        operator,
197        expires,
198      },
199      ExecuteMsg::RevokeAll {
200        operator,
201      } => CW721ExecuteMsg::RevokeAll {
202        operator,
203      },
204      _ => panic!("cannot covert {:?} to CW721ExecuteMsg", msg),
205    }
206  }
207}
208
209/// This enum describes voters status.
210#[cw_serde]
211pub enum BlacklistedVotersResponse {
212  /// Voters are blacklisted
213  VotersBlacklisted {},
214  /// Returns a voter that is not blacklisted.
215  VotersNotBlacklisted {
216    voter: String,
217  },
218}
219
220impl fmt::Display for BlacklistedVotersResponse {
221  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222    match self {
223      BlacklistedVotersResponse::VotersBlacklisted {} => write!(f, "Voters are blacklisted!"),
224      BlacklistedVotersResponse::VotersNotBlacklisted {
225        voter,
226      } => {
227        write!(f, "Voter is not blacklisted: {}", voter)
228      },
229    }
230  }
231}
232
233/// This structure describes the query messages available in the contract.
234#[cw_serde]
235#[derive(QueryResponses)]
236pub enum QueryMsg {
237  /// Return the blacklisted voters
238  #[returns(Vec<Addr>)]
239  BlacklistedVoters {
240    start_after: Option<String>,
241    limit: Option<u32>,
242  },
243
244  /// Return the current total amount of vAMP
245  #[returns(VotingPowerResponse)]
246  TotalVamp {
247    time: Option<Time>,
248  },
249
250  /// Return the current total amount of vAMP
251  #[returns(VotingPowerFixedResponse)]
252  TotalFixed {
253    time: Option<Time>,
254  },
255
256  /// Return the user's current voting power (vAMP balance)
257  #[returns(VotingPowerResponse)]
258  LockVamp {
259    token_id: String,
260    time: Option<Time>,
261  },
262
263  /// Return information about a user's lock position
264  #[returns(LockInfoResponse)]
265  LockInfo {
266    token_id: String,
267    time: Option<Time>,
268  },
269  /// Return the vAMP contract configuration
270  #[returns(Config)]
271  Config {},
272
273  /// With MetaData Extension.
274  /// Returns metadata about one particular token,
275  /// based on *ERC721 Metadata JSON Schema*
276  /// https://docs.opensea.io/docs/metadata-standards
277  ///
278  /// {    
279  ///    "name": "AllianceNFT # 1",
280  ///    "token_uri": null,
281  ///    "extension": {
282  ///      "image": "https://ipfs.io/ipfs/{hash}",
283  ///      "description": "Received for participating on Game Of Alliance",
284  ///      "name": "AllianceNFT # 1",
285  ///      "attributes": [{
286  ///              "display_type" : null,
287  ///              "trait_type": "x",
288  ///              "value": "1"
289  ///          },{
290  ///              "display_type" : null,
291  ///              "trait_type": "y",
292  ///              "value": "1"
293  ///          },{
294  ///              "display_type" : null,
295  ///              "trait_type": "width",
296  ///              "value": "120"
297  ///          },{
298  ///              "display_type" : null,
299  ///              "trait_type": "height",
300  ///              "value": "120"
301  ///          },{
302  ///              "display_type" : null,
303  ///              "trait_type": "rarity",
304  ///              "value": 11
305  ///          }],
306  ///      "image_data": null,
307  ///      "external_url": null,
308  ///      "background_color": null,
309  ///      "animation_url": null,
310  ///      "youtube_url": null
311  ///    }
312  ///  }
313  #[returns(NftInfoResponse<Extension>)]
314  NftInfo {
315    token_id: String,
316  },
317
318  /// With MetaData Extension.
319  /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization
320  #[returns(AllNftInfoResponse<Extension>)]
321  AllNftInfo {
322    token_id: String,
323    /// unset or false will filter out expired approvals, you must set to true to see them
324    include_expired: Option<bool>,
325  },
326
327  /// CW721 Queries
328
329  /// Return the owner of the given token, error if token does not exist
330  #[returns(OwnerOfResponse)]
331  OwnerOf {
332    token_id: String,
333    /// unset or false will filter out expired approvals, you must set to true to see them
334    include_expired: Option<bool>,
335  },
336  /// Return operator that can access all of the owner's tokens.
337  /// Return the owner of the given token, error if token does not exist
338  #[returns(ApprovalResponse)]
339  Approval {
340    token_id: String,
341    spender: String,
342    include_expired: Option<bool>,
343  },
344  /// Return approvals that a token has
345  #[returns(ApprovalsResponse)]
346  Approvals {
347    token_id: String,
348    include_expired: Option<bool>,
349  },
350  /// List all operators that can access all of the owner's tokens
351  #[returns(OperatorsResponse)]
352  AllOperators {
353    owner: String,
354    /// unset or false will filter out expired items, you must set to true to see them
355    include_expired: Option<bool>,
356    start_after: Option<String>,
357    limit: Option<u32>,
358  },
359  /// Total number of tokens issued
360  #[returns(NumTokensResponse)]
361  NumTokens {},
362
363  /// With MetaData Extension.
364  #[returns(ContractInfoResponse)]
365  ContractInfo {},
366
367  /// With Enumerable extension.
368  /// Returns all tokens owned by the given address, [] if unset.
369  #[returns(TokensResponse)]
370  Tokens {
371    owner: String,
372    start_after: Option<String>,
373    limit: Option<u32>,
374  },
375  /// With Enumerable extension.
376  /// Requires pagination. Lists all token_ids controlled by the contract.
377  #[returns(TokensResponse)]
378  AllTokens {
379    start_after: Option<String>,
380    limit: Option<u32>,
381  },
382
383  // Return the minter
384  #[returns(MinterResponse)]
385  Minter {},
386}
387
388impl From<QueryMsg> for CW721QueryMsg<Empty> {
389  fn from(msg: QueryMsg) -> CW721QueryMsg<Empty> {
390    match msg {
391      QueryMsg::OwnerOf {
392        token_id,
393        include_expired,
394      } => CW721QueryMsg::OwnerOf {
395        token_id,
396        include_expired,
397      },
398      QueryMsg::Approval {
399        token_id,
400        spender,
401        include_expired,
402      } => CW721QueryMsg::Approval {
403        token_id,
404        spender,
405        include_expired,
406      },
407      QueryMsg::Approvals {
408        token_id,
409        include_expired,
410      } => CW721QueryMsg::Approvals {
411        token_id,
412        include_expired,
413      },
414      QueryMsg::AllOperators {
415        owner,
416        include_expired,
417        start_after,
418        limit,
419      } => CW721QueryMsg::AllOperators {
420        owner,
421        include_expired,
422        start_after,
423        limit,
424      },
425      QueryMsg::NumTokens {} => CW721QueryMsg::NumTokens {},
426      QueryMsg::ContractInfo {} => CW721QueryMsg::ContractInfo {},
427      QueryMsg::NftInfo {
428        token_id,
429      } => CW721QueryMsg::NftInfo {
430        token_id,
431      },
432      QueryMsg::AllNftInfo {
433        token_id,
434        include_expired,
435      } => CW721QueryMsg::AllNftInfo {
436        token_id,
437        include_expired,
438      },
439      QueryMsg::Tokens {
440        owner,
441        start_after,
442        limit,
443      } => CW721QueryMsg::Tokens {
444        owner,
445        start_after,
446        limit,
447      },
448      QueryMsg::AllTokens {
449        start_after,
450        limit,
451      } => CW721QueryMsg::AllTokens {
452        start_after,
453        limit,
454      },
455      QueryMsg::Minter {} => CW721QueryMsg::Minter {},
456      _ => panic!("cannot covert {:?} to CW721QueryMsg", msg),
457    }
458  }
459}
460
461/// This structure is used to return a user's amount of vAMP.
462#[cw_serde]
463pub struct VotingPowerResponse {
464  pub fixed: Uint128,
465  pub voting_power: Uint128,
466  /// The total vp balance (fixed + voting_power)
467  pub vp: Uint128,
468}
469
470#[cw_serde]
471pub struct VotingPowerFixedResponse {
472  pub fixed: Uint128,
473}
474
475/// This structure is used to return the lock information for a vAMP position.
476#[cw_serde]
477pub struct LockInfoResponse {
478  pub owner: Addr,
479
480  pub from_period: u64,
481
482  pub asset: Asset,
483  /// The underlying_amount locked in the position
484  pub underlying_amount: Uint128,
485  /// This is the initial boost for the lock position
486  pub coefficient: Decimal,
487  /// Start time for the vAMP position decay
488  pub start: u64,
489  /// End time for the vAMP position decay
490  pub end: End,
491  /// Slope at which a staker's vAMP balance decreases over time
492  pub slope: Uint128,
493
494  /// fixed sockel
495  pub fixed_amount: Uint128,
496  /// includes only decreasing voting_power, it is the current voting power of the period currently queried.
497  pub voting_power: Uint128,
498}
499
500impl LockInfoResponse {
501  pub fn has_vp(&self) -> bool {
502    !self.fixed_amount.is_zero() || !self.voting_power.is_zero()
503  }
504
505  pub fn end_string(&self) -> String {
506    self.end.to_string()
507  }
508}
509
510#[cw_serde]
511pub enum End {
512  Permanent,
513  Period(u64),
514}
515
516impl fmt::Display for End {
517  fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
518    match self {
519      End::Permanent => fmt.write_str("permanent")?,
520      End::Period(period) => fmt.write_str(&period.to_string())?,
521    }
522
523    Ok(())
524  }
525}
526
527/// This structure stores the main parameters for the voting escrow contract.
528#[cw_serde]
529pub struct Config {
530  // global address config
531  pub global_config_addr: Addr,
532  // assets that are allowed to be locked including a config of how to calculate base power
533  pub deposit_assets: Vec<DepositAsset<Addr>>,
534  /// The list of contracts to receive updates on user's lock info changes
535  pub push_update_contracts: Vec<Addr>,
536  /// Address that can only blacklist vAMP stakers and remove their governance power
537  pub decommissioned: Option<bool>,
538}
539
540#[cw_serde]
541pub enum AssetInfoConfig {
542  Default,
543  ExchangeRate {
544    contract: Addr,
545  },
546}
547
548impl AssetInfoConfig {
549  pub fn get_exchange_rate(&self, querier: &QuerierWrapper) -> StdResult<Option<Decimal>> {
550    match self {
551      AssetInfoConfig::Default => Ok(None),
552      AssetInfoConfig::ExchangeRate {
553        contract,
554      } => Ok(Some(ErisHub(contract).query_exchange_rate(querier)?)),
555    }
556  }
557
558  pub fn get_underlying_amount(
559    &self,
560    querier: &QuerierWrapper,
561    amount: Uint128,
562  ) -> StdResult<Uint128> {
563    Ok(self.get_exchange_rate(querier)?.map_or(amount, |e| e * amount))
564  }
565}
566
567/// This structure describes a Migration message.
568#[cw_serde]
569pub struct MigrateMsg {}