1use std::any::type_name;
2
3use cosmwasm_std::{Api, BlockInfo, CanonicalAddr, ReadonlyStorage, StdError, StdResult, Storage};
4use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage};
5use hermit_toolkit_serialization::{Json, Bincode2, Serde};
6use hermit_toolkit_storage::{AppendStore, AppendStoreMut};
7use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use crate::expiration::Expiration;
10use crate::msg::{Tx, TxAction};
11
12pub const CONFIG_KEY: &[u8] = b"config";
14pub const BLOCK_KEY: &[u8] = b"blockinfo";
16pub const MINTERS_KEY: &[u8] = b"minters";
18pub const MY_ADDRESS_KEY: &[u8] = b"myaddr";
20pub const PRNG_SEED_KEY: &[u8] = b"prngseed";
22pub const CREATOR_KEY: &[u8] = b"creator";
24pub const DEFAULT_ROYALTY_KEY: &[u8] = b"defaultroy";
26pub const PREFIX_MAP_TO_INDEX: &[u8] = b"map2idx";
28pub const PREFIX_MAP_TO_ID: &[u8] = b"idx2id";
30pub const PREFIX_INFOS: &[u8] = b"infos";
32pub const PREFIX_PUB_META: &[u8] = b"publicmeta";
34pub const PREFIX_PRIV_META: &[u8] = b"privatemeta";
36pub const PREFIX_ROYALTY_INFO: &[u8] = b"royalty";
38pub const PREFIX_MINT_RUN: &[u8] = b"mintrun";
40pub const PREFIX_TXS: &[u8] = b"rawtxs";
42pub const PREFIX_TX_IDS: &[u8] = b"txids";
44pub const PREFIX_ALL_PERMISSIONS: &[u8] = b"allpermissions";
46pub const PREFIX_AUTHLIST: &[u8] = b"authlist";
48pub const PREFIX_OWNER_PRIV: &[u8] = b"ownerpriv";
50pub const PREFIX_VIEW_KEY: &[u8] = b"viewkeys";
52pub const PREFIX_RECEIVERS: &[u8] = b"receivers";
54pub const PREFIX_MINT_RUN_NUM: &[u8] = b"runnum";
56pub const PREFIX_REVOKED_PERMITS: &str = "revoke";
58
59#[derive(Serialize, Debug, Deserialize, Clone, PartialEq)]
61pub struct Config {
62 pub name: String,
64 pub symbol: String,
66 pub admin: CanonicalAddr,
68 pub mint_cnt: u32,
70 pub tx_cnt: u64,
72 pub token_cnt: u32,
74 pub status: u8,
76 pub token_supply_is_public: bool,
78 pub owner_is_public: bool,
80 pub sealed_metadata_is_enabled: bool,
82 pub unwrap_to_private: bool,
84 pub minter_may_update_metadata: bool,
86 pub owner_may_update_metadata: bool,
88 pub burn_is_enabled: bool,
90}
91
92#[derive(Serialize, Deserialize, Clone, Debug)]
94#[serde(rename_all = "snake_case")]
95pub enum StoredTxAction {
96 Transfer {
98 from: CanonicalAddr,
100 sender: Option<CanonicalAddr>,
102 recipient: CanonicalAddr,
104 },
105 Mint {
107 minter: CanonicalAddr,
109 recipient: CanonicalAddr,
111 },
112 Burn {
114 owner: CanonicalAddr,
116 burner: Option<CanonicalAddr>,
118 },
119}
120
121#[derive(Serialize, Deserialize, Clone, Debug)]
123#[serde(rename_all = "snake_case")]
124pub struct StoredTx {
125 pub tx_id: u64,
127 pub block_height: u64,
129 pub block_time: u64,
131 pub token_id: String,
133 pub action: StoredTxAction,
135 pub memo: Option<String>,
137}
138
139impl StoredTx {
140 pub fn into_humanized<A: Api>(self, api: &A) -> StdResult<Tx> {
146 let action = match self.action {
147 StoredTxAction::Transfer {
148 from,
149 sender,
150 recipient,
151 } => {
152 let sndr = if let Some(s) = sender {
153 Some(api.human_address(&s)?)
154 } else {
155 None
156 };
157 TxAction::Transfer {
158 from: api.human_address(&from)?,
159 sender: sndr,
160 recipient: api.human_address(&recipient)?,
161 }
162 }
163 StoredTxAction::Mint { minter, recipient } => TxAction::Mint {
164 minter: api.human_address(&minter)?,
165 recipient: api.human_address(&recipient)?,
166 },
167 StoredTxAction::Burn { owner, burner } => {
168 let bnr = if let Some(b) = burner {
169 Some(api.human_address(&b)?)
170 } else {
171 None
172 };
173 TxAction::Burn {
174 owner: api.human_address(&owner)?,
175 burner: bnr,
176 }
177 }
178 };
179 let tx = Tx {
180 tx_id: self.tx_id,
181 block_height: self.block_height,
182 block_time: self.block_time,
183 token_id: self.token_id,
184 action,
185 memo: self.memo,
186 };
187
188 Ok(tx)
189 }
190}
191
192#[allow(clippy::too_many_arguments)]
205pub fn store_transfer<S: Storage>(
206 storage: &mut S,
207 config: &mut Config,
208 block: &BlockInfo,
209 token_id: String,
210 from: CanonicalAddr,
211 sender: Option<CanonicalAddr>,
212 recipient: CanonicalAddr,
213 memo: Option<String>,
214) -> StdResult<()> {
215 let action = StoredTxAction::Transfer {
216 from,
217 sender,
218 recipient,
219 };
220 let tx = StoredTx {
221 tx_id: config.tx_cnt,
222 block_height: block.height,
223 block_time: block.time,
224 token_id,
225 action,
226 memo,
227 };
228 let mut tx_store = PrefixedStorage::new(PREFIX_TXS, storage);
229 json_save(&mut tx_store, &config.tx_cnt.to_le_bytes(), &tx)?;
230 if let StoredTxAction::Transfer {
231 from,
232 sender,
233 recipient,
234 } = tx.action
235 {
236 append_tx_for_addr(storage, config.tx_cnt, &from)?;
237 append_tx_for_addr(storage, config.tx_cnt, &recipient)?;
238 if let Some(sndr) = sender.as_ref() {
239 if *sndr != recipient {
240 append_tx_for_addr(storage, config.tx_cnt, sndr)?;
241 }
242 }
243 }
244 config.tx_cnt += 1;
245 Ok(())
246}
247
248pub fn store_mint<S: Storage>(
260 storage: &mut S,
261 config: &mut Config,
262 block: &BlockInfo,
263 token_id: String,
264 minter: CanonicalAddr,
265 recipient: CanonicalAddr,
266 memo: Option<String>,
267) -> StdResult<()> {
268 let action = StoredTxAction::Mint { minter, recipient };
269 let tx = StoredTx {
270 tx_id: config.tx_cnt,
271 block_height: block.height,
272 block_time: block.time,
273 token_id,
274 action,
275 memo,
276 };
277 let mut tx_store = PrefixedStorage::new(PREFIX_TXS, storage);
278 json_save(&mut tx_store, &config.tx_cnt.to_le_bytes(), &tx)?;
279 if let StoredTxAction::Mint { minter, recipient } = tx.action {
280 append_tx_for_addr(storage, config.tx_cnt, &recipient)?;
281 if recipient != minter {
282 append_tx_for_addr(storage, config.tx_cnt, &minter)?;
283 }
284 }
285 config.tx_cnt += 1;
286 Ok(())
287}
288
289pub fn store_burn<S: Storage>(
301 storage: &mut S,
302 config: &mut Config,
303 block: &BlockInfo,
304 token_id: String,
305 owner: CanonicalAddr,
306 burner: Option<CanonicalAddr>,
307 memo: Option<String>,
308) -> StdResult<()> {
309 let action = StoredTxAction::Burn { owner, burner };
310 let tx = StoredTx {
311 tx_id: config.tx_cnt,
312 block_height: block.height,
313 block_time: block.time,
314 token_id,
315 action,
316 memo,
317 };
318 let mut tx_store = PrefixedStorage::new(PREFIX_TXS, storage);
319 json_save(&mut tx_store, &config.tx_cnt.to_le_bytes(), &tx)?;
320 if let StoredTxAction::Burn { owner, burner } = tx.action {
321 append_tx_for_addr(storage, config.tx_cnt, &owner)?;
322 if let Some(bnr) = burner.as_ref() {
323 append_tx_for_addr(storage, config.tx_cnt, bnr)?;
324 }
325 }
326 config.tx_cnt += 1;
327 Ok(())
328}
329
330fn append_tx_for_addr<S: Storage>(
338 storage: &mut S,
339 tx_id: u64,
340 address: &CanonicalAddr,
341) -> StdResult<()> {
342 let mut store = PrefixedStorage::multilevel(&[PREFIX_TX_IDS, address.as_slice()], storage);
343 let mut store = AppendStoreMut::attach_or_create(&mut store)?;
344 store.push(&tx_id)
345}
346
347pub fn get_txs<A: Api, S: ReadonlyStorage>(
357 api: &A,
358 storage: &S,
359 address: &CanonicalAddr,
360 page: u32,
361 page_size: u32,
362) -> StdResult<(Vec<Tx>, u64)> {
363 let id_store =
364 ReadonlyPrefixedStorage::multilevel(&[PREFIX_TX_IDS, address.as_slice()], storage);
365
366 let id_store = if let Some(result) = AppendStore::<u64, _>::attach(&id_store) {
369 result?
370 } else {
371 return Ok((vec![], 0));
372 };
373 let count = id_store.len() as u64;
374 let tx_store = ReadonlyPrefixedStorage::new(PREFIX_TXS, storage);
376 let txs: StdResult<Vec<Tx>> = id_store
379 .iter()
380 .rev()
381 .skip((page * page_size) as usize)
382 .take(page_size as usize)
383 .map(|id| {
384 id.map(|id| {
385 json_load(&tx_store, &id.to_le_bytes())
386 .and_then(|tx: StoredTx| tx.into_humanized(api))
387 })
388 .and_then(|x| x)
389 })
390 .collect();
391
392 txs.map(|t| (t, count))
393}
394
395#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
397pub struct Permission {
398 pub address: CanonicalAddr,
400 pub expirations: [Option<Expiration>; 3],
402}
403
404#[derive(Serialize, Deserialize, Debug)]
406pub enum PermissionType {
407 ViewOwner,
408 ViewMetadata,
409 Transfer,
410}
411
412impl PermissionType {
413 pub fn to_usize(&self) -> usize {
415 match self {
416 PermissionType::ViewOwner => 0,
417 PermissionType::ViewMetadata => 1,
418 PermissionType::Transfer => 2,
419 }
420 }
421
422 pub fn num_types(&self) -> usize {
424 3
425 }
426}
427
428#[derive(Serialize, Deserialize, Debug)]
430pub struct AuthList {
431 pub address: CanonicalAddr,
433 pub tokens: [Vec<u32>; 3],
435}
436
437#[derive(Serialize, Deserialize, Debug, Clone)]
439pub struct ReceiveRegistration {
440 pub code_hash: String,
442 pub impl_batch: bool,
444}
445
446pub fn save<T: Serialize, S: Storage>(storage: &mut S, key: &[u8], value: &T) -> StdResult<()> {
454 storage.set(key, &Bincode2::serialize(value)?);
455 Ok(())
456}
457
458pub fn remove<S: Storage>(storage: &mut S, key: &[u8]) {
465 storage.remove(key);
466}
467
468pub fn load<T: DeserializeOwned, S: ReadonlyStorage>(storage: &S, key: &[u8]) -> StdResult<T> {
476 Bincode2::deserialize(
477 &storage
478 .get(key)
479 .ok_or_else(|| StdError::not_found(type_name::<T>()))?,
480 )
481}
482
483pub fn may_load<T: DeserializeOwned, S: ReadonlyStorage>(
491 storage: &S,
492 key: &[u8],
493) -> StdResult<Option<T>> {
494 match storage.get(key) {
495 Some(value) => Bincode2::deserialize(&value).map(Some),
496 None => Ok(None),
497 }
498}
499
500pub fn json_save<T: Serialize, S: Storage>(
509 storage: &mut S,
510 key: &[u8],
511 value: &T,
512) -> StdResult<()> {
513 storage.set(key, &Json::serialize(value)?);
514 Ok(())
515}
516
517pub fn json_load<T: DeserializeOwned, S: ReadonlyStorage>(storage: &S, key: &[u8]) -> StdResult<T> {
526 Json::deserialize(
527 &storage
528 .get(key)
529 .ok_or_else(|| StdError::not_found(type_name::<T>()))?,
530 )
531}
532
533pub fn json_may_load<T: DeserializeOwned, S: ReadonlyStorage>(
542 storage: &S,
543 key: &[u8],
544) -> StdResult<Option<T>> {
545 match storage.get(key) {
546 Some(value) => Json::deserialize(&value).map(Some),
547 None => Ok(None),
548 }
549}