cw20_vesting/
msg.rs

1use cosmwasm_std::{Binary, BlockInfo, Uint128};
2use cw20::Logo;
3use cw_utils::Expiration;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::ContractError;
8use wynd_utils::Curve;
9
10#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
11pub struct InstantiateMarketingInfo {
12    pub project: Option<String>,
13    pub description: Option<String>,
14    pub marketing: Option<String>,
15    pub logo: Option<Logo>,
16}
17
18#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
19pub struct InstantiateMsg {
20    pub name: String,
21    pub symbol: String,
22    pub decimals: u8,
23    pub initial_balances: Vec<InitBalance>,
24    pub mint: Option<MinterInfo>,
25    pub marketing: Option<InstantiateMarketingInfo>,
26    pub allowed_vesters: Option<Vec<String>>,
27}
28
29#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
30pub struct InitBalance {
31    pub address: String,
32    pub amount: Uint128,
33    /// Optional vesting schedule
34    /// It must be a decreasing curve, ending at 0, and never exceeding amount
35    pub vesting: Option<Curve>,
36}
37
38#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
39pub struct MinterInfo {
40    pub minter: String,
41    /// cap is a hard cap on total supply that can be achieved by minting.
42    /// This can be a monotonically increasing curve based on block time
43    /// (constant value being a special case of this).
44    ///
45    /// Note that cap refers to total_supply.
46    /// If None, there is unlimited cap.
47    pub cap: Option<Curve>,
48}
49
50impl InstantiateMsg {
51    pub fn get_curve(&self) -> Option<&Curve> {
52        self.mint.as_ref().and_then(|v| v.cap.as_ref())
53    }
54
55    pub fn get_cap(&self, block: &BlockInfo) -> Option<Uint128> {
56        self.get_curve().map(|v| v.value(block.time.seconds()))
57    }
58
59    pub fn validate(&self) -> Result<(), ContractError> {
60        // Check name, symbol, decimals
61        if !is_valid_name(&self.name) {
62            return Err(ContractError::InvalidName);
63        }
64        if !is_valid_symbol(&self.symbol) {
65            return Err(ContractError::InvalidSymbol);
66        }
67        if self.decimals > 18 {
68            return Err(ContractError::TooManyDecimals);
69        }
70        if let Some(curve) = self.get_curve() {
71            curve.validate_monotonic_increasing()?;
72        }
73        Ok(())
74    }
75}
76
77fn is_valid_name(name: &str) -> bool {
78    let bytes = name.as_bytes();
79    if bytes.len() < 3 || bytes.len() > 50 {
80        return false;
81    }
82    true
83}
84
85fn is_valid_symbol(symbol: &str) -> bool {
86    let bytes = symbol.as_bytes();
87    if bytes.len() < 3 || bytes.len() > 12 {
88        return false;
89    }
90    for byte in bytes.iter() {
91        if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
92            return false;
93        }
94    }
95    true
96}
97
98/// Asserts the vesting schedule decreases to 0 eventually, and is never more than the
99/// amount being sent. If it doesn't match these conditions, returns an error.
100pub fn assert_schedule_vests_amount(
101    schedule: &Curve,
102    amount: Uint128,
103) -> Result<(), ContractError> {
104    schedule.validate_monotonic_decreasing()?;
105    let (low, high) = schedule.range();
106    if low != 0 {
107        Err(ContractError::NeverFullyVested)
108    } else if high > amount.u128() {
109        Err(ContractError::VestsMoreThanSent)
110    } else {
111        Ok(())
112    }
113}
114
115/// Returns true if curve is already at 0
116pub fn fully_vested(schedule: &Curve, block: &BlockInfo) -> bool {
117    schedule.value(block.time.seconds()).is_zero()
118}
119
120#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
121#[serde(rename_all = "snake_case")]
122pub enum ExecuteMsg {
123    /// Transfer is a base message to move tokens to another account without triggering actions
124    Transfer { recipient: String, amount: Uint128 },
125    /// TransferVesting is a base message to move tokens to another account without triggering actions.
126    /// The sent tokens will be slowly released based on the attached schedule.
127    /// If the recipient already has an existing vesting schedule, this will fail.
128    TransferVesting {
129        recipient: String,
130        amount: Uint128,
131        /// VestingSchedule.
132        /// It must be a decreasing curve, ending at 0, and never exceeding amount
133        schedule: Curve,
134    },
135    /// Burn is a base message to destroy tokens forever
136    Burn { amount: Uint128 },
137    /// Send is a base message to transfer tokens to a contract and trigger an action
138    /// on the receiving contract.
139    Send {
140        contract: String,
141        amount: Uint128,
142        msg: Binary,
143    },
144    /// Only with "approval" extension. Allows spender to access an additional amount tokens
145    /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
146    /// expiration with this one.
147    IncreaseAllowance {
148        spender: String,
149        amount: Uint128,
150        expires: Option<Expiration>,
151    },
152    /// Only with "approval" extension. Lowers the spender's access of tokens
153    /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
154    /// allowance expiration with this one.
155    DecreaseAllowance {
156        spender: String,
157        amount: Uint128,
158        expires: Option<Expiration>,
159    },
160    /// Only with "approval" extension. Transfers amount tokens from owner -> recipient
161    /// if `env.sender` has sufficient pre-approval.
162    TransferFrom {
163        owner: String,
164        recipient: String,
165        amount: Uint128,
166    },
167    /// Only with "approval" extension. Sends amount tokens from owner -> contract
168    /// if `env.sender` has sufficient pre-approval.
169    SendFrom {
170        owner: String,
171        contract: String,
172        amount: Uint128,
173        msg: Binary,
174    },
175    /// Only with "approval" extension. Destroys tokens forever
176    BurnFrom { owner: String, amount: Uint128 },
177    /// Only with the "mintable" extension. If authorized, creates amount new tokens
178    /// and adds to the recipient balance.
179    Mint { recipient: String, amount: Uint128 },
180    /// Only with the "mintable" extension. If minter set and authorized by current
181    /// minter, makes the new address the minter.
182    UpdateMinter { minter: String },
183    /// Only with the "marketing" extension. If authorized, updates marketing metadata.
184    /// Setting None/null for any of these will leave it unchanged.
185    /// Setting Some("") will clear this field on the contract storage
186    UpdateMarketing {
187        /// A URL pointing to the project behind this token.
188        project: Option<String>,
189        /// A longer description of the token and it's utility. Designed for tooltips or such
190        description: Option<String>,
191        /// The address (if any) who can update this data structure
192        marketing: Option<String>,
193    },
194    /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token
195    UploadLogo(Logo),
196    /// If set, it will add an address to a permission list on TransferVesting
197    AllowVester { address: String },
198    /// If set, it will remove an address to a permission list on TransferVesting
199    DenyVester { address: String },
200}
201
202#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
203#[serde(rename_all = "snake_case")]
204pub enum QueryMsg {
205    /// Returns the current balance of the given address, 0 if unset.
206    /// Return type: BalanceResponse.
207    Balance { address: String },
208    /// Returns the current vesting schedule for the given account.
209    /// Return type: VestingResponse.
210    Vesting { address: String },
211    /// Returns the allow list who can transfer vesting tokens.
212    /// Return type: VestingAllowListResponse.
213    VestingAllowList {},
214    /// Returns metadata on the contract - name, decimals, supply, etc.
215    /// Return type: TokenInfoResponse.
216    TokenInfo {},
217    /// Only with "mintable" extension.
218    /// Returns who can mint and the hard cap on maximum tokens after minting.
219    /// Return type: MinterResponse.
220    Minter {},
221    /// Only with "allowance" extension.
222    /// Returns how much spender can use from owner account, 0 if unset.
223    /// Return type: AllowanceResponse.
224    Allowance { owner: String, spender: String },
225    /// Only with "enumerable" extension (and "allowances")
226    /// Returns all allowances this owner has approved. Supports pagination.
227    /// Return type: AllAllowancesResponse.
228    AllAllowances {
229        owner: String,
230        start_after: Option<String>,
231        limit: Option<u32>,
232    },
233    /// Only with "enumerable" extension
234    /// Returns all accounts that have balances. Supports pagination.
235    /// Return type: AllAccountsResponse.
236    AllAccounts {
237        start_after: Option<String>,
238        limit: Option<u32>,
239    },
240    /// Only with "marketing" extension
241    /// Returns more metadata on the contract to display in the client:
242    /// - description, logo, project url, etc.
243    /// Return type: MarketingInfoResponse
244    MarketingInfo {},
245    /// Only with "marketing" extension
246    /// Downloads the mbeded logo data (if stored on chain). Errors if no logo data ftored for this
247    /// contract.
248    /// Return type: DownloadLogoResponse.
249    DownloadLogo {},
250}
251
252#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
253pub struct MinterResponse {
254    pub minter: String,
255    /// cap is a hard cap on total supply that can be achieved by minting.
256    /// This can be a monotonically increasing curve based on block time
257    /// (constant value being a special case of this).
258    ///
259    /// Note that cap refers to total_supply.
260    /// If None, there is unlimited cap.
261    pub cap: Option<Curve>,
262    /// This is cap evaluated at the current time
263    pub current_cap: Option<Uint128>,
264}
265
266#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
267pub struct VestingResponse {
268    /// The total vesting schedule
269    pub schedule: Option<Curve>,
270    /// The current amount locked. Always 0 if schedule is None
271    pub locked: Uint128,
272}
273
274#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
275pub struct VestingAllowListResponse {
276    pub allow_list: Vec<String>,
277}