use hermit_toolkit_storage::{AppendStore, AppendStoreMut};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{
Api, CanonicalAddr, Coin, HumanAddr, ReadonlyStorage, StdError, StdResult, Storage, Uint128,
};
use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage};
use crate::state::Config;
const PREFIX_TXS: &[u8] = b"transactions";
const PREFIX_TRANSFERS: &[u8] = b"transfers";
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
pub struct Tx {
pub id: u64,
pub from: HumanAddr,
pub sender: HumanAddr,
pub receiver: HumanAddr,
pub coins: Coin,
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
pub block_time: Option<u64>,
pub block_height: Option<u64>,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TxAction {
Transfer {
from: HumanAddr,
sender: HumanAddr,
recipient: HumanAddr,
},
Mint {
minter: HumanAddr,
recipient: HumanAddr,
},
Burn {
burner: HumanAddr,
owner: HumanAddr,
},
Deposit {},
Redeem {},
}
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct RichTx {
pub id: u64,
pub action: TxAction,
pub coins: Coin,
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<String>,
pub block_time: u64,
pub block_height: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
struct StoredLegacyTransfer {
id: u64,
from: CanonicalAddr,
sender: CanonicalAddr,
receiver: CanonicalAddr,
coins: Coin,
memo: Option<String>,
block_time: u64,
block_height: u64,
}
impl StoredLegacyTransfer {
pub fn into_humanized<A: Api>(self, api: &A) -> StdResult<Tx> {
let tx = Tx {
id: self.id,
from: api.human_address(&self.from)?,
sender: api.human_address(&self.sender)?,
receiver: api.human_address(&self.receiver)?,
coins: self.coins,
memo: self.memo,
block_time: Some(self.block_time),
block_height: Some(self.block_height),
};
Ok(tx)
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum TxCode {
Transfer = 0,
Mint = 1,
Burn = 2,
Deposit = 3,
Redeem = 4,
}
impl TxCode {
fn to_u8(self) -> u8 {
self as u8
}
fn from_u8(n: u8) -> StdResult<Self> {
use TxCode::*;
match n {
0 => Ok(Transfer),
1 => Ok(Mint),
2 => Ok(Burn),
3 => Ok(Deposit),
4 => Ok(Redeem),
other => Err(StdError::generic_err(format!(
"Unexpected Tx code in transaction history: {} Storage is corrupted.",
other
))),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
struct StoredTxAction {
tx_type: u8,
address1: Option<CanonicalAddr>,
address2: Option<CanonicalAddr>,
address3: Option<CanonicalAddr>,
}
impl StoredTxAction {
fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self {
Self {
tx_type: TxCode::Transfer.to_u8(),
address1: Some(from),
address2: Some(sender),
address3: Some(recipient),
}
}
fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self {
Self {
tx_type: TxCode::Mint.to_u8(),
address1: Some(minter),
address2: Some(recipient),
address3: None,
}
}
fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self {
Self {
tx_type: TxCode::Burn.to_u8(),
address1: Some(burner),
address2: Some(owner),
address3: None,
}
}
fn deposit() -> Self {
Self {
tx_type: TxCode::Deposit.to_u8(),
address1: None,
address2: None,
address3: None,
}
}
fn redeem() -> Self {
Self {
tx_type: TxCode::Redeem.to_u8(),
address1: None,
address2: None,
address3: None,
}
}
fn into_humanized<A: Api>(self, api: &A) -> StdResult<TxAction> {
let transfer_addr_err = || {
StdError::generic_err(
"Missing address in stored Transfer transaction. Storage is corrupt",
)
};
let mint_addr_err = || {
StdError::generic_err("Missing address in stored Mint transaction. Storage is corrupt")
};
let burn_addr_err = || {
StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt")
};
let action = match TxCode::from_u8(self.tx_type)? {
TxCode::Transfer => {
let from = self.address1.ok_or_else(transfer_addr_err)?;
let sender = self.address2.ok_or_else(transfer_addr_err)?;
let recipient = self.address3.ok_or_else(transfer_addr_err)?;
let from = api.human_address(&from)?;
let sender = api.human_address(&sender)?;
let recipient = api.human_address(&recipient)?;
TxAction::Transfer {
from,
sender,
recipient,
}
}
TxCode::Mint => {
let minter = self.address1.ok_or_else(mint_addr_err)?;
let recipient = self.address2.ok_or_else(mint_addr_err)?;
let minter = api.human_address(&minter)?;
let recipient = api.human_address(&recipient)?;
TxAction::Mint { minter, recipient }
}
TxCode::Burn => {
let burner = self.address1.ok_or_else(burn_addr_err)?;
let owner = self.address2.ok_or_else(burn_addr_err)?;
let burner = api.human_address(&burner)?;
let owner = api.human_address(&owner)?;
TxAction::Burn { burner, owner }
}
TxCode::Deposit => TxAction::Deposit {},
TxCode::Redeem => TxAction::Redeem {},
};
Ok(action)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
struct StoredRichTx {
id: u64,
action: StoredTxAction,
coins: Coin,
memo: Option<String>,
block_time: u64,
block_height: u64,
}
impl StoredRichTx {
fn new(
id: u64,
action: StoredTxAction,
coins: Coin,
memo: Option<String>,
block: &cosmwasm_std::BlockInfo,
) -> Self {
Self {
id,
action,
coins,
memo,
block_time: block.time,
block_height: block.height,
}
}
fn into_humanized<A: Api>(self, api: &A) -> StdResult<RichTx> {
Ok(RichTx {
id: self.id,
action: self.action.into_humanized(api)?,
coins: self.coins,
memo: self.memo,
block_time: self.block_time,
block_height: self.block_height,
})
}
fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self {
let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver);
Self {
id: transfer.id,
action,
coins: transfer.coins,
memo: transfer.memo,
block_time: transfer.block_time,
block_height: transfer.block_height,
}
}
}
fn increment_tx_count<S: Storage>(store: &mut S) -> StdResult<u64> {
let mut config = Config::from_storage(store);
let id = config.tx_count() + 1;
config.set_tx_count(id)?;
Ok(id)
}
#[allow(clippy::too_many_arguments)] pub fn store_transfer<S: Storage>(
store: &mut S,
owner: &CanonicalAddr,
sender: &CanonicalAddr,
receiver: &CanonicalAddr,
amount: Uint128,
denom: String,
memo: Option<String>,
block: &cosmwasm_std::BlockInfo,
) -> StdResult<()> {
let id = increment_tx_count(store)?;
let coins = Coin { denom, amount };
let transfer = StoredLegacyTransfer {
id,
from: owner.clone(),
sender: sender.clone(),
receiver: receiver.clone(),
coins,
memo,
block_time: block.time,
block_height: block.height,
};
let tx = StoredRichTx::from_stored_legacy_transfer(transfer.clone());
if owner != sender && owner != receiver {
append_tx(store, &tx, owner)?;
append_transfer(store, &transfer, owner)?;
}
if sender != receiver {
append_tx(store, &tx, sender)?;
append_transfer(store, &transfer, sender)?;
}
append_tx(store, &tx, receiver)?;
append_transfer(store, &transfer, receiver)?;
Ok(())
}
pub fn store_mint<S: Storage>(
store: &mut S,
minter: &CanonicalAddr,
recipient: &CanonicalAddr,
amount: Uint128,
denom: String,
memo: Option<String>,
block: &cosmwasm_std::BlockInfo,
) -> StdResult<()> {
let id = increment_tx_count(store)?;
let coins = Coin { denom, amount };
let action = StoredTxAction::mint(minter.clone(), recipient.clone());
let tx = StoredRichTx::new(id, action, coins, memo, block);
if minter != recipient {
append_tx(store, &tx, recipient)?;
}
append_tx(store, &tx, minter)?;
Ok(())
}
pub fn store_burn<S: Storage>(
store: &mut S,
owner: &CanonicalAddr,
burner: &CanonicalAddr,
amount: Uint128,
denom: String,
memo: Option<String>,
block: &cosmwasm_std::BlockInfo,
) -> StdResult<()> {
let id = increment_tx_count(store)?;
let coins = Coin { denom, amount };
let action = StoredTxAction::burn(owner.clone(), burner.clone());
let tx = StoredRichTx::new(id, action, coins, memo, block);
if burner != owner {
append_tx(store, &tx, owner)?;
}
append_tx(store, &tx, burner)?;
Ok(())
}
pub fn store_deposit<S: Storage>(
store: &mut S,
recipient: &CanonicalAddr,
amount: Uint128,
denom: String,
block: &cosmwasm_std::BlockInfo,
) -> StdResult<()> {
let id = increment_tx_count(store)?;
let coins = Coin { denom, amount };
let action = StoredTxAction::deposit();
let tx = StoredRichTx::new(id, action, coins, None, block);
append_tx(store, &tx, recipient)?;
Ok(())
}
pub fn store_redeem<S: Storage>(
store: &mut S,
redeemer: &CanonicalAddr,
amount: Uint128,
denom: String,
block: &cosmwasm_std::BlockInfo,
) -> StdResult<()> {
let id = increment_tx_count(store)?;
let coins = Coin { denom, amount };
let action = StoredTxAction::redeem();
let tx = StoredRichTx::new(id, action, coins, None, block);
append_tx(store, &tx, redeemer)?;
Ok(())
}
fn append_tx<S: Storage>(
store: &mut S,
tx: &StoredRichTx,
for_address: &CanonicalAddr,
) -> StdResult<()> {
let mut store = PrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], store);
let mut store = AppendStoreMut::attach_or_create(&mut store)?;
store.push(tx)
}
fn append_transfer<S: Storage>(
store: &mut S,
tx: &StoredLegacyTransfer,
for_address: &CanonicalAddr,
) -> StdResult<()> {
let mut store = PrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], store);
let mut store = AppendStoreMut::attach_or_create(&mut store)?;
store.push(tx)
}
pub fn get_txs<A: Api, S: ReadonlyStorage>(
api: &A,
storage: &S,
for_address: &CanonicalAddr,
page: u32,
page_size: u32,
) -> StdResult<(Vec<RichTx>, u64)> {
let store = ReadonlyPrefixedStorage::multilevel(&[PREFIX_TXS, for_address.as_slice()], storage);
let store = AppendStore::<StoredRichTx, _, _>::attach(&store);
let store = if let Some(result) = store {
result?
} else {
return Ok((vec![], 0));
};
let tx_iter = store
.iter()
.rev()
.skip((page * page_size) as _)
.take(page_size as _);
let txs: StdResult<Vec<RichTx>> = tx_iter
.map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x))
.collect();
txs.map(|txs| (txs, store.len() as u64))
}
pub fn get_transfers<A: Api, S: ReadonlyStorage>(
api: &A,
storage: &S,
for_address: &CanonicalAddr,
page: u32,
page_size: u32,
) -> StdResult<(Vec<Tx>, u64)> {
let store =
ReadonlyPrefixedStorage::multilevel(&[PREFIX_TRANSFERS, for_address.as_slice()], storage);
let store = AppendStore::<StoredLegacyTransfer, _, _>::attach(&store);
let store = if let Some(result) = store {
result?
} else {
return Ok((vec![], 0));
};
let transfer_iter = store
.iter()
.rev()
.skip((page * page_size) as _)
.take(page_size as _);
let transfers: StdResult<Vec<Tx>> = transfer_iter
.map(|tx| tx.map(|tx| tx.into_humanized(api)).and_then(|x| x))
.collect();
transfers.map(|txs| (txs, store.len() as u64))
}