levana_perpswap_cosmos/contracts/cw20/entry.rs
1//! Entrypoint messages for the CW20 contract.
2use super::Cw20Coin;
3use crate::prelude::*;
4use cosmwasm_schema::{cw_serde, QueryResponses};
5use cosmwasm_std::{Addr, Binary, Uint128};
6use cw_utils::Expiration;
7
8#[cw_serde]
9pub struct InstantiateMsg {
10 /************** Cw20 spec *******************/
11 pub name: String,
12 pub symbol: String,
13 pub decimals: u8,
14 pub initial_balances: Vec<Cw20Coin>,
15 /// We make this mandatory since we always need an owner for these CW20s.
16 pub minter: InstantiateMinter,
17 pub marketing: Option<InstantiateMarketingInfo>,
18}
19
20impl InstantiateMsg {
21 pub fn get_cap(&self) -> Option<Uint128> {
22 self.minter.cap
23 }
24
25 pub fn validate(&self) -> anyhow::Result<()> {
26 // Check name, symbol, decimals
27 if !self.has_valid_name() {
28 bail!(PerpError::new(
29 ErrorId::MsgValidation,
30 ErrorDomain::Cw20,
31 "Name is not in the expected format (3-50 UTF-8 bytes)"
32 ))
33 }
34 if !self.has_valid_symbol() {
35 bail!(PerpError::new(
36 ErrorId::MsgValidation,
37 ErrorDomain::Cw20,
38 "Ticker symbol is not in expected format [a-zA-Z\\-]{{3,12}}"
39 ))
40 }
41 if self.decimals > 18 {
42 bail!(PerpError::new(
43 ErrorId::MsgValidation,
44 ErrorDomain::Cw20,
45 "Decimals must not exceed 18"
46 ))
47 }
48 if !self.has_valid_balances() {
49 bail!(PerpError::new(
50 ErrorId::MsgValidation,
51 ErrorDomain::Cw20,
52 "duplicate account balances"
53 ))
54 }
55 Ok(())
56 }
57
58 fn has_valid_name(&self) -> bool {
59 let bytes = self.name.as_bytes();
60 if bytes.len() < 3 || bytes.len() > 50 {
61 return false;
62 }
63 true
64 }
65
66 fn has_valid_symbol(&self) -> bool {
67 let bytes = self.symbol.as_bytes();
68 if bytes.len() < 3 || bytes.len() > 12 {
69 return false;
70 }
71 for byte in bytes.iter() {
72 if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
73 return false;
74 }
75 }
76 true
77 }
78
79 fn has_valid_balances(&self) -> bool {
80 let mut addresses = self
81 .initial_balances
82 .iter()
83 .map(|c| &c.address)
84 .collect::<Vec<_>>();
85 addresses.sort();
86 addresses.dedup();
87
88 // check for duplicates
89 addresses.len() == self.initial_balances.len()
90 }
91}
92
93#[cw_serde]
94pub enum ExecuteMsg {
95 /************** Cw20 spec *******************/
96 /// Transfer is a base message to move tokens to another account without triggering actions
97 Transfer { recipient: RawAddr, amount: Uint128 },
98 /// Burn is a base message to destroy tokens forever
99 Burn { amount: Uint128 },
100 /// Send is a base message to transfer tokens to a contract and trigger an action
101 /// on the receiving contract.
102 Send {
103 contract: RawAddr,
104 amount: Uint128,
105 msg: Binary,
106 },
107 /// Allows spender to access an additional amount tokens
108 /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
109 /// expiration with this one.
110 IncreaseAllowance {
111 spender: RawAddr,
112 amount: Uint128,
113 expires: Option<Expiration>,
114 },
115 /// Lowers the spender's access of tokens
116 /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
117 /// allowance expiration with this one.
118 DecreaseAllowance {
119 spender: RawAddr,
120 amount: Uint128,
121 expires: Option<Expiration>,
122 },
123 /// Transfers amount tokens from owner -> recipient
124 /// if `env.sender` has sufficient pre-approval.
125 TransferFrom {
126 owner: RawAddr,
127 recipient: RawAddr,
128 amount: Uint128,
129 },
130 /// Sends amount tokens from owner -> contract
131 /// if `env.sender` has sufficient pre-approval.
132 SendFrom {
133 owner: RawAddr,
134 contract: RawAddr,
135 amount: Uint128,
136 msg: Binary,
137 },
138 /// Destroys tokens forever
139 BurnFrom { owner: RawAddr, amount: Uint128 },
140 /// If authorized, creates amount new tokens
141 /// and adds to the recipient balance.
142 Mint { recipient: RawAddr, amount: Uint128 },
143 /// This variant is according to spec. The current minter may set
144 /// a new minter. Setting the minter to None will remove the
145 /// token's minter forever.
146 /// there is deliberately *not* a way to set the proprietary MinterKind
147 /// so the only way to set the minter to MinterKind::MarketId is at
148 /// instantiation
149 ///
150 /// Note: we require that there always be a minter, so this is not optional!
151 UpdateMinter { new_minter: RawAddr },
152 /// If authorized, updates marketing metadata.
153 /// Setting None/null for any of these will leave it unchanged.
154 /// Setting Some("") will clear this field on the contract storage
155 UpdateMarketing {
156 /// A URL pointing to the project behind this token.
157 project: Option<String>,
158 /// A longer description of the token and it's utility. Designed for tooltips or such
159 description: Option<String>,
160 /// The address (if any) who can update this data structure
161 marketing: Option<String>,
162 },
163 /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token
164 UploadLogo(Logo),
165 /************** Proprietary *******************/
166 /// Set factory addr
167 SetMarket { addr: RawAddr },
168}
169
170#[cw_serde]
171#[derive(QueryResponses)]
172pub enum QueryMsg {
173 /************** Cw20 spec *******************/
174 /// * returns [BalanceResponse]
175 ///
176 /// The current balance of the given address, 0 if unset.
177 #[returns(BalanceResponse)]
178 Balance { address: RawAddr },
179
180 /// * returns [TokenInfoResponse]
181 ///
182 /// Returns metadata on the contract - name, decimals, supply, etc.
183 #[returns(TokenInfoResponse)]
184 TokenInfo {},
185
186 /// * returns [MinterResponse]
187 ///
188 /// Returns who can mint and the hard cap on maximum tokens after minting.
189 #[returns(Option<MinterResponse>)]
190 Minter {},
191
192 /// * returns [AllowanceResponse]
193 ///
194 /// Returns how much spender can use from owner account, 0 if unset.
195 #[returns(AllowanceResponse)]
196 Allowance { owner: RawAddr, spender: RawAddr },
197
198 /// * returns [AllAllowancesResponse]
199 ///
200 /// Returns all allowances this owner has approved. Supports pagination.
201 #[returns(AllAllowancesResponse)]
202 AllAllowances {
203 owner: RawAddr,
204 start_after: Option<RawAddr>,
205 limit: Option<u32>,
206 },
207
208 /// * returns [AllSpenderAllowancesResponse]
209 ///
210 /// Returns all allowances this spender has been granted. Supports pagination.
211 #[returns(AllSpenderAllowancesResponse)]
212 AllSpenderAllowances {
213 spender: RawAddr,
214 start_after: Option<RawAddr>,
215 limit: Option<u32>,
216 },
217
218 /// * returns [AllAccountsResponse]
219 ///
220 /// Returns all accounts that have balances. Supports pagination.
221 #[returns(AllAccountsResponse)]
222 AllAccounts {
223 start_after: Option<RawAddr>,
224 limit: Option<u32>,
225 },
226
227 /// * returns [MarketingInfoResponse]
228 ///
229 /// Returns more metadata on the contract to display in the client:
230 /// - description, logo, project url, etc.
231 #[returns(MarketingInfoResponse)]
232 MarketingInfo {},
233
234 /// * returns [DownloadLogoResponse]
235 ///
236 /// Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this
237 /// contract.
238 #[returns(DownloadLogoResponse)]
239 DownloadLogo {},
240
241 /************** Proprietary *******************/
242 /// * returns [cw2::ContractVersion]
243 #[returns(cw2::ContractVersion)]
244 Version {},
245}
246
247/// Placeholder migration message
248#[cw_serde]
249pub struct MigrateMsg {}
250
251#[cw_serde]
252#[derive(Eq)]
253pub struct InstantiateMinter {
254 pub minter: RawAddr,
255 pub cap: Option<Uint128>,
256}
257
258/************** Proprietary but doesn't affect interop *******************/
259/************** since queries are according to spec *******************/
260/************** and only return addresses *******************/
261#[cw_serde]
262pub struct InstantiateMarketingInfo {
263 pub project: Option<String>,
264 pub description: Option<String>,
265 pub marketing: Option<Addr>,
266 pub logo: Option<Logo>,
267}
268
269#[cw_serde]
270#[derive(Eq)]
271pub struct BalanceResponse {
272 pub balance: Uint128,
273}
274
275#[cw_serde]
276#[derive(Eq)]
277pub struct TokenInfoResponse {
278 pub name: String,
279 pub symbol: String,
280 pub decimals: u8,
281 pub total_supply: Uint128,
282}
283
284#[cw_serde]
285#[derive(Default)]
286pub struct AllowanceResponse {
287 pub allowance: Uint128,
288 pub expires: Expiration,
289}
290
291#[cw_serde]
292#[derive(Eq)]
293pub struct MinterResponse {
294 pub minter: Addr,
295 /// cap is a hard cap on total supply that can be achieved by minting.
296 /// Note that this refers to total_supply.
297 /// If None, there is unlimited cap.
298 pub cap: Option<Uint128>,
299}
300
301#[cw_serde]
302#[derive(Default)]
303pub struct MarketingInfoResponse {
304 /// A URL pointing to the project behind this token.
305 pub project: Option<String>,
306 /// A longer description of the token and it's utility. Designed for tooltips or such
307 pub description: Option<String>,
308 /// A link to the logo, or a comment there is an on-chain logo stored
309 pub logo: Option<LogoInfo>,
310 /// The address (if any) who can update this data structure
311 pub marketing: Option<Addr>,
312}
313
314/// When we download an embedded logo, we get this response type.
315/// We expect a SPA to be able to accept this info and display it.
316#[cw_serde]
317pub struct DownloadLogoResponse {
318 pub mime_type: String,
319 pub data: Binary,
320}
321
322#[cw_serde]
323pub struct AllowanceInfo {
324 pub spender: Addr,
325 pub allowance: Uint128,
326 pub expires: Expiration,
327}
328
329#[cw_serde]
330#[derive(Default)]
331pub struct AllAllowancesResponse {
332 pub allowances: Vec<AllowanceInfo>,
333}
334
335#[cw_serde]
336pub struct SpenderAllowanceInfo {
337 pub owner: Addr,
338 pub allowance: Uint128,
339 pub expires: Expiration,
340}
341
342#[cw_serde]
343#[derive(Default)]
344pub struct AllSpenderAllowancesResponse {
345 pub allowances: Vec<SpenderAllowanceInfo>,
346}
347
348#[cw_serde]
349#[derive(Default)]
350pub struct AllAccountsResponse {
351 pub accounts: Vec<Addr>,
352}
353
354/// This is used for uploading logo data, or setting it in InstantiateData
355#[cw_serde]
356pub enum Logo {
357 /// A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.
358 Url(String),
359 /// Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants
360 Embedded(EmbeddedLogo),
361}
362
363/// This is used to store the logo on the blockchain in an accepted format.
364/// Enforce maximum size of 5KB on all variants.
365#[cw_serde]
366pub enum EmbeddedLogo {
367 /// Store the Logo as an SVG file. The content must conform to the spec
368 /// at <https://en.wikipedia.org/wiki/Scalable_Vector_Graphics>
369 ///
370 /// (The contract should do some light-weight sanity-check validation)
371 Svg(Binary),
372 /// Store the Logo as a PNG file. This will likely only support up to 64x64 or so
373 /// within the 5KB limit.
374 Png(Binary),
375}
376
377/// This is used to display logo info, provide a link or inform there is one
378/// that can be downloaded from the blockchain itself
379#[cw_serde]
380pub enum LogoInfo {
381 /// A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.
382 Url(String),
383 /// There is an embedded logo on the chain, make another call to download it.
384 Embedded,
385}
386
387impl From<&Logo> for LogoInfo {
388 fn from(logo: &Logo) -> Self {
389 match logo {
390 Logo::Url(url) => LogoInfo::Url(url.clone()),
391 Logo::Embedded(_) => LogoInfo::Embedded,
392 }
393 }
394}