use crate::serialization::SigmaSerializeResult;
use crate::serialization::{
sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
SigmaSerializable,
};
use std::convert::TryFrom;
use super::ergo_box::BoxId;
use derive_more::From;
use derive_more::Into;
use ergo_chain_types::{Digest32, DigestNError};
use sigma_ser::ScorexSerializable;
use thiserror::Error;
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, From, Into)]
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
pub struct TokenId(Digest32);
impl TokenId {
pub const SIZE: usize = Digest32::SIZE;
pub fn from_base64(s: &str) -> Result<TokenId, DigestNError> {
Digest32::from_base64(s).map(Into::into)
}
}
impl From<BoxId> for TokenId {
fn from(i: BoxId) -> Self {
TokenId(i.into())
}
}
impl From<TokenId> for Vec<i8> {
fn from(v: TokenId) -> Self {
v.0.into()
}
}
impl From<TokenId> for Vec<u8> {
fn from(v: TokenId) -> Self {
v.0.into()
}
}
impl AsRef<[u8]> for TokenId {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl From<TokenId> for String {
fn from(v: TokenId) -> Self {
v.0.into()
}
}
impl SigmaSerializable for TokenId {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
Ok(self.0.scorex_serialize(w)?)
}
fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
Ok(Self(Digest32::scorex_parse(r)?))
}
}
#[cfg(not(feature = "json"))]
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct TokenAmount(u64);
#[cfg(feature = "json")]
#[derive(
serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord,
)]
#[serde(
try_from = "crate::chain::json::token::TokenAmountJson",
into = "crate::chain::json::token::TokenAmountJson"
)]
pub struct TokenAmount(pub(crate) u64);
impl TokenAmount {
pub const MIN_RAW: u64 = 1;
pub const MAX_RAW: u64 = i64::MAX as u64;
pub const MIN: TokenAmount = TokenAmount(Self::MIN_RAW);
pub fn checked_add(&self, rhs: &Self) -> Result<Self, TokenAmountError> {
let raw = self
.0
.checked_add(rhs.0)
.ok_or(TokenAmountError::Overflow)?;
if raw > Self::MAX_RAW {
Err(TokenAmountError::OutOfBounds(raw as i64))
} else {
Ok(Self(raw))
}
}
pub fn checked_sub(&self, rhs: &Self) -> Result<Self, TokenAmountError> {
let raw = self
.0
.checked_sub(rhs.0)
.ok_or(TokenAmountError::Overflow)?;
if raw < Self::MIN_RAW {
Err(TokenAmountError::OutOfBounds(raw as i64))
} else {
Ok(Self(raw))
}
}
pub fn as_u64(&self) -> &u64 {
&self.0
}
}
#[derive(Error, Eq, PartialEq, Debug, Clone)]
pub enum TokenAmountError {
#[error("Token amount is out of bounds: {0}")]
OutOfBounds(i64),
#[error("Overflow")]
Overflow,
}
impl TryFrom<u64> for TokenAmount {
type Error = TokenAmountError;
fn try_from(v: u64) -> Result<Self, Self::Error> {
if (TokenAmount::MIN_RAW..=TokenAmount::MAX_RAW).contains(&v) {
Ok(TokenAmount(v))
} else {
Err(TokenAmountError::OutOfBounds(v as i64))
}
}
}
impl From<TokenAmount> for u64 {
fn from(ta: TokenAmount) -> Self {
ta.0
}
}
impl From<TokenAmount> for i64 {
fn from(ta: TokenAmount) -> Self {
ta.0 as i64
}
}
impl From<Token> for (Vec<i8>, i64) {
fn from(t: Token) -> Self {
(t.token_id.into(), t.amount.into())
}
}
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Token {
#[cfg_attr(feature = "json", serde(rename = "tokenId"))]
pub token_id: TokenId,
#[cfg_attr(feature = "json", serde(rename = "amount"))]
pub amount: TokenAmount,
}
impl From<(TokenId, TokenAmount)> for Token {
fn from(token_pair: (TokenId, TokenAmount)) -> Self {
Token {
token_id: token_pair.0,
amount: token_pair.1,
}
}
}
#[allow(clippy::unwrap_used)]
#[cfg(feature = "arbitrary")]
pub mod arbitrary {
use ergo_chain_types::Digest32;
use proptest::prelude::*;
use super::Token;
use super::TokenAmount;
use super::TokenId;
use std::convert::TryFrom;
pub enum ArbTokenIdParam {
Predef,
Arbitrary,
}
impl Default for ArbTokenIdParam {
fn default() -> Self {
ArbTokenIdParam::Predef
}
}
impl Arbitrary for TokenId {
type Parameters = ArbTokenIdParam;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
match args {
ArbTokenIdParam::Predef => prop_oneof![
Just(TokenId::from(
Digest32::try_from(
"3130a82e45842aebb888742868e055e2f554ab7d92f233f2c828ed4a43793710"
.to_string()
)
.unwrap()
)),
Just(TokenId::from(
Digest32::try_from(
"e7321ffb4ec5d71deb3110eb1ac09612b9cf57445acab1e0e3b1222d5b5a6c60"
.to_string()
)
.unwrap()
)),
Just(TokenId::from(
Digest32::try_from(
"ad62f6dd92e7dc850bc406770dfac9a943dd221a7fb440b7b2bcc7d3149c1792"
.to_string()
)
.unwrap()
))
]
.boxed(),
ArbTokenIdParam::Arbitrary => (any::<Digest32>()).prop_map_into().boxed(),
}
}
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for TokenAmount {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(TokenAmount::MIN_RAW..=TokenAmount::MAX_RAW / 100000)
.prop_map(Self)
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl Default for TokenAmount {
fn default() -> Self {
TokenAmount::MIN
}
}
impl Arbitrary for Token {
type Parameters = ArbTokenIdParam;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(any_with::<TokenId>(args), any::<TokenAmount>())
.prop_map(Self::from)
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
}
#[allow(clippy::panic)]
#[cfg(test)]
mod tests {
use crate::chain::token::TokenId;
use crate::serialization::sigma_serialize_roundtrip;
use proptest::prelude::*;
proptest! {
#[test]
fn token_id_roundtrip(v in any::<TokenId>()) {
prop_assert_eq![sigma_serialize_roundtrip(&v), v];
}
}
}