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}