use crate::{constants::*, Address, BlockHeight, CoinValue};
use std::{
collections::HashMap,
convert::TryInto,
fmt::{Display, Formatter},
num::ParseIntError,
str::FromStr,
};
use bytes::Bytes;
use derive_more::{Display, From, Into};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::serde_as;
use thiserror::Error;
use tmelcrypt::{Ed25519SK, HashVal, Hashable};
#[derive(
Clone,
Copy,
IntoPrimitive,
TryFromPrimitive,
Eq,
PartialEq,
Debug,
Serialize_repr,
Deserialize_repr,
Hash,
)]
#[repr(u8)]
pub enum TxKind {
DoscMint = 0x50,
Faucet = 0xff,
LiqDeposit = 0x52,
LiqWithdraw = 0x53,
Normal = 0x00,
Stake = 0x10,
Swap = 0x51,
}
impl Default for TxKind {
fn default() -> Self {
Self::Normal
}
}
impl Display for TxKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TxKind::Normal => "Normal".fmt(f),
TxKind::Stake => "Stake".fmt(f),
TxKind::DoscMint => "DoscMint".fmt(f),
TxKind::Swap => "Swap".fmt(f),
TxKind::LiqDeposit => "LiqDeposit".fmt(f),
TxKind::LiqWithdraw => "LiqWithdraw".fmt(f),
TxKind::Faucet => "Faucet".fmt(f),
}
}
}
#[derive(Error, Debug, Clone)]
#[error("Unable to parse {0} into TxKind")]
pub struct ParseTxKindError(String);
impl FromStr for TxKind {
type Err = ParseTxKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Normal" => Ok(TxKind::Normal),
"Stake" => Ok(TxKind::Stake),
"DoscMint" => Ok(TxKind::DoscMint),
"Swap" => Ok(TxKind::Swap),
"LiqDeposit" => Ok(TxKind::LiqDeposit),
"LiqWithdraw" => Ok(TxKind::LiqWithdraw),
"Faucet" => Ok(TxKind::Faucet),
_ => Err(ParseTxKindError(s.into())),
}
}
}
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Hash,
PartialOrd,
Ord,
From,
Into,
Serialize,
Deserialize,
Display,
)]
#[serde(transparent)]
pub struct TxHash(pub HashVal);
impl FromStr for TxHash {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(TxHash(s.parse()?))
}
}
#[serde_as]
#[derive(Default, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Transaction {
#[serde(with = "stdcode::try_asstr")]
pub kind: TxKind,
pub inputs: Vec<CoinID>,
pub outputs: Vec<CoinData>,
pub fee: CoinValue,
#[serde_as(as = "Vec<stdcode::HexBytes>")]
pub covenants: Vec<Bytes>,
#[serde_as(as = "stdcode::HexBytes")]
pub data: Bytes,
#[serde_as(as = "Vec<stdcode::HexBytes>")]
pub sigs: Vec<Bytes>,
}
impl Transaction {
pub fn new(kind: TxKind) -> Self {
Self {
kind,
..Default::default()
}
}
pub fn is_well_formed(&self) -> bool {
let mut output: bool = true;
self.outputs.iter().for_each(|out| {
if out.value > MAX_COINVAL {
output = false;
}
});
if self.fee > MAX_COINVAL {
output = false;
}
if self.outputs.len() > 255 {
output = false;
}
output
}
pub fn hash_nosigs(&self) -> TxHash {
let mut s = self.clone();
s.sigs = Vec::new();
let self_bytes = stdcode::serialize(&s).unwrap();
tmelcrypt::hash_single(&self_bytes).into()
}
pub fn signed_ed25519(mut self, sk: Ed25519SK) -> Self {
self.sigs.push(sk.sign(&self.hash_nosigs().0).into());
self
}
pub fn total_outputs(&self) -> HashMap<Denom, CoinValue> {
let mut toret: HashMap<Denom, CoinValue> = HashMap::new();
self.outputs.iter().for_each(|output| {
let old = toret.get(&output.denom).copied().unwrap_or_default();
toret.insert(output.denom, old + output.value);
});
let old = toret.get(&Denom::Mel).copied().unwrap_or_default();
toret.insert(Denom::Mel, old + self.fee);
toret
}
pub fn covenants_as_map(&self) -> HashMap<Address, Bytes> {
self.covenants
.iter()
.map(|script| (Address(script.hash()), script.clone()))
.collect()
}
pub fn base_fee(
&self,
fee_multiplier: u128,
ballast: u128,
cov_to_weight: impl Fn(&[u8]) -> u128,
) -> CoinValue {
((self.weight(cov_to_weight).saturating_add(ballast)).saturating_mul(fee_multiplier) >> 16)
.into()
}
pub fn weight(&self, cov_to_weight: impl Fn(&[u8]) -> u128) -> u128 {
let raw_length = stdcode::serialize(self).unwrap().len() as u128;
let script_weights: u128 = self.covenants.iter().map(|scr| cov_to_weight(scr)).sum();
let output_penalty = self.outputs.len() as u128 * 1000;
let input_boon = self.inputs.len() as u128 * 1000;
raw_length
.saturating_add(script_weights)
.saturating_add(output_penalty)
.saturating_sub(input_boon)
}
fn raw_weight(&self) -> i128 {
let raw_length = stdcode::serialize(self).unwrap().len() as i128;
let output_penalty = self.outputs.len() as i128 * 1000;
let input_boon = self.inputs.len() as i128 * 1000;
raw_length + output_penalty - input_boon
}
pub fn usable_fees(&self, fee_multiplier: u128) -> CoinValue {
let raw_weight = self.raw_weight();
eprintln!("raw_weight = {raw_weight}");
let fees_consumed_by_tx_itself = (raw_weight * (fee_multiplier as i128)) >> 16;
CoinValue((self.fee.0 as i128).saturating_sub(fees_consumed_by_tx_itself) as u128)
}
pub fn output_coinid(&self, index: u8) -> CoinID {
CoinID {
txhash: self.hash_nosigs(),
index,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct CoinID {
pub txhash: TxHash,
pub index: u8,
}
impl CoinID {
pub fn new(txhash: TxHash, index: u8) -> Self {
CoinID { txhash, index }
}
}
#[derive(Error, Debug, Clone)]
pub enum ParseCoinIDError {
#[error("could not split into txhash-index")]
CannotSplit,
#[error("hex error ({0})")]
HexError(#[from] hex::FromHexError),
#[error("parse int error ({0})")]
ParseIntError(#[from] ParseIntError),
}
impl FromStr for CoinID {
type Err = ParseCoinIDError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let splitted = s.split('-').collect::<Vec<_>>();
if splitted.len() != 2 {
return Err(ParseCoinIDError::CannotSplit);
}
let txhash: HashVal = splitted[0].parse()?;
let index: u8 = splitted[1].parse()?;
Ok(CoinID {
txhash: txhash.into(),
index,
})
}
}
impl Display for CoinID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.txhash.fmt(f)?;
'-'.fmt(f)?;
self.index.fmt(f)
}
}
impl CoinID {
pub fn zero_zero() -> Self {
Self {
txhash: tmelcrypt::HashVal::default().into(),
index: 0,
}
}
pub fn proposer_reward(height: BlockHeight) -> Self {
CoinID {
txhash: tmelcrypt::hash_keyed(b"reward_coin_pseudoid", height.0.to_be_bytes()).into(),
index: 0,
}
}
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct CoinData {
#[serde(with = "stdcode::try_asstr")]
pub covhash: Address,
pub value: CoinValue,
#[serde(with = "stdcode::try_asstr")]
pub denom: Denom,
#[serde_as(as = "stdcode::HexBytes")]
pub additional_data: Bytes,
}
impl CoinData {
pub fn additional_data_hex(&self) -> String {
hex::encode(&self.additional_data)
}
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy)]
pub enum Denom {
Mel,
Sym,
Erg,
NewCustom,
Custom(TxHash),
}
impl Display for Denom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s: String = match self {
Denom::Mel => "MEL".into(),
Denom::Sym => "SYM".into(),
Denom::Erg => "ERG".into(),
Denom::NewCustom => "(NEWCUSTOM)".into(),
Denom::Custom(hash) => format!("CUSTOM-{}", hash.0),
};
s.fmt(f)
}
}
#[derive(Error, Debug, Clone)]
pub enum ParseDenomError {
#[error("Invalid denom name")]
Invalid,
#[error("hex error ({0})")]
HexError(#[from] hex::FromHexError),
}
impl FromStr for Denom {
type Err = ParseDenomError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"MEL" => Ok(Denom::Mel),
"SYM" => Ok(Denom::Sym),
"ERG" => Ok(Denom::Erg),
"(NEWCUSTOM)" => Ok(Denom::NewCustom),
other => {
let splitted = other.split('-').collect::<Vec<_>>();
if splitted.len() != 2 || splitted[0] != "CUSTOM" {
return Err(ParseDenomError::Invalid);
}
let hv: HashVal = splitted[1].parse()?;
Ok(Denom::Custom(TxHash(hv)))
}
}
}
}
impl Denom {
pub fn to_bytes(self) -> Bytes {
match self {
Self::Mel => Bytes::from_static(b"m"),
Self::Sym => Bytes::from_static(b"s"),
Self::Erg => Bytes::from_static(b"d"),
Self::NewCustom => Bytes::new(),
Self::Custom(hash) => Bytes::copy_from_slice(&hash.0),
}
}
pub fn from_bytes(vec: &[u8]) -> Option<Self> {
Some(match vec {
b"m" => Self::Mel,
b"s" => Self::Sym,
b"d" => Self::Erg,
b"" => Self::NewCustom,
other => Self::Custom(HashVal(other.try_into().ok()?).into()),
})
}
}
impl Serialize for Denom {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
DenomInner(self.to_bytes().into()).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Denom {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let inner = <DenomInner>::deserialize(deserializer)?;
Denom::from_bytes(&inner.0)
.ok_or_else(|| serde::de::Error::custom("not the right format for a Denom"))
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
struct DenomInner(#[serde(with = "stdcode::hex")] Vec<u8>);
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
pub struct CoinDataHeight {
pub coin_data: CoinData,
pub height: BlockHeight,
}