From 74e161382d9cf52c9cd99b707f4334170cc85484 Mon Sep 17 00:00:00 2001 From: Yevhenii Babichenko Date: Wed, 24 Nov 2021 22:22:06 +0200 Subject: [PATCH] chain-impl-mockchain: split tokens modules into separate sub-modules --- .../src/accounting/account/account_state.rs | 2 +- .../src/accounting/account/mod.rs | 2 +- .../src/certificate/mint_token.rs | 2 +- chain-impl-mockchain/src/ledger/ledger.rs | 2 +- chain-impl-mockchain/src/tokens.rs | 287 ------------------ chain-impl-mockchain/src/tokens/identifier.rs | 131 ++++++++ .../src/tokens/minting_policy.rs | 94 ++++++ chain-impl-mockchain/src/tokens/mod.rs | 4 + chain-impl-mockchain/src/tokens/name.rs | 66 ++++ .../src/tokens/policy_hash.rs | 55 ++++ 10 files changed, 354 insertions(+), 291 deletions(-) delete mode 100644 chain-impl-mockchain/src/tokens.rs create mode 100644 chain-impl-mockchain/src/tokens/identifier.rs create mode 100644 chain-impl-mockchain/src/tokens/minting_policy.rs create mode 100644 chain-impl-mockchain/src/tokens/mod.rs create mode 100644 chain-impl-mockchain/src/tokens/name.rs create mode 100644 chain-impl-mockchain/src/tokens/policy_hash.rs diff --git a/chain-impl-mockchain/src/accounting/account/account_state.rs b/chain-impl-mockchain/src/accounting/account/account_state.rs index d4b6aacfe..b79a55383 100644 --- a/chain-impl-mockchain/src/accounting/account/account_state.rs +++ b/chain-impl-mockchain/src/accounting/account/account_state.rs @@ -1,6 +1,6 @@ use crate::date::Epoch; use crate::value::*; -use crate::{certificate::PoolId, tokens::TokenIdentifier}; +use crate::{certificate::PoolId, tokens::identifier::TokenIdentifier}; use imhamt::{Hamt, HamtIter}; use std::collections::hash_map::DefaultHasher; diff --git a/chain-impl-mockchain/src/accounting/account/mod.rs b/chain-impl-mockchain/src/accounting/account/mod.rs index 953e40f50..7e4bb3e00 100644 --- a/chain-impl-mockchain/src/accounting/account/mod.rs +++ b/chain-impl-mockchain/src/accounting/account/mod.rs @@ -8,7 +8,7 @@ pub mod account_state; pub mod last_rewards; pub mod spending; -use crate::tokens::TokenIdentifier; +use crate::tokens::identifier::TokenIdentifier; use crate::{date::Epoch, value::*}; use imhamt::{Hamt, InsertError, UpdateError}; use std::collections::hash_map::DefaultHasher; diff --git a/chain-impl-mockchain/src/certificate/mint_token.rs b/chain-impl-mockchain/src/certificate/mint_token.rs index e9bbe74f9..d60882458 100644 --- a/chain-impl-mockchain/src/certificate/mint_token.rs +++ b/chain-impl-mockchain/src/certificate/mint_token.rs @@ -1,7 +1,7 @@ use crate::{ account::Identifier, certificate::CertificateSlice, - tokens::{MintingPolicy, TokenIdentifier}, + tokens::{identifier::TokenIdentifier, minting_policy::MintingPolicy}, transaction::{Payload, PayloadAuthData, PayloadData, PayloadSlice}, value::Value, }; diff --git a/chain-impl-mockchain/src/ledger/ledger.rs b/chain-impl-mockchain/src/ledger/ledger.rs index 748b31b89..a68f34631 100644 --- a/chain-impl-mockchain/src/ledger/ledger.rs +++ b/chain-impl-mockchain/src/ledger/ledger.rs @@ -20,7 +20,7 @@ use crate::setting::ActiveSlotsCoeffError; use crate::stake::{ PercentStake, PoolError, PoolStakeInformation, PoolsState, StakeControl, StakeDistribution, }; -use crate::tokens::MintingPolicyViolation; +use crate::tokens::minting_policy::MintingPolicyViolation; use crate::transaction::*; use crate::treasury::Treasury; use crate::value::*; diff --git a/chain-impl-mockchain/src/tokens.rs b/chain-impl-mockchain/src/tokens.rs deleted file mode 100644 index d436d9497..000000000 --- a/chain-impl-mockchain/src/tokens.rs +++ /dev/null @@ -1,287 +0,0 @@ -use chain_core::mempack::{ReadBuf, ReadError, Readable}; -use cryptoxide::{blake2b::Blake2b, digest::Digest}; -use thiserror::Error; -use typed_bytes::ByteBuilder; - -use std::{convert::TryInto, fmt, str::FromStr}; - -pub const POLICY_HASH_SIZE: usize = 28; -pub const TOKEN_NAME_MAX_SIZE: usize = 32; - -/// The unique identifier of a token. -/// -/// It is represented either as two hex strings separated by a dot or just a hex string when the -/// name is empty. -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct TokenIdentifier { - pub policy_hash: PolicyHash, - pub token_name: TokenName, -} - -/// blake2b_224 hash of a serialized minting policy -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PolicyHash([u8; POLICY_HASH_SIZE]); - -/// A sequence of bytes serving as a token name. Tokens that share the same name but have different -/// voting policies hashes are different tokens. A name can be empty. The maximum length of a token -/// name is 32 bytes. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TokenName(Vec); - -/// A minting policy consists of multiple entries defining different -/// constraints on the minting process. An empty policy means that new tokens -/// cannot be minted during the chain run. -/// -/// Minting policies are meant to be ignored in block0 fragments. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MintingPolicy(Vec); - -/// An entry of a minting policy. Currently there are no entries available. -/// This is reserved for the future use. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MintingPolicyEntry {} - -#[derive(Debug, Clone, PartialEq, Eq, Error)] -pub enum MintingPolicyViolation { - #[error("the policy of this token does not allow minting")] - AdditionalMintingNotAllowed, -} - -#[derive(Debug, Error)] -#[error("Token name can be no more that {} bytes long; got {} bytes", TOKEN_NAME_MAX_SIZE, .actual)] -pub struct TokenNameTooLong { - actual: usize, -} - -#[derive(Debug, Error)] -pub enum TokenIdentifierParseError { - #[error("got an empty str")] - EmptyStr, - - #[error(transparent)] - Hex(#[from] hex::FromHexError), - - #[error(transparent)] - PolicyHash(#[from] ReadError), - - #[error("expected a token name after the `.`")] - ExpectedTokenName, - - #[error(transparent)] - TokenName(#[from] TokenNameTooLong), - - #[error("unexpected data after the token name")] - UnexpectedData, -} - -impl MintingPolicy { - pub fn new() -> Self { - Self(Vec::new()) - } - - pub fn check_minting_tx(&self) -> Result<(), MintingPolicyViolation> { - if self.0.is_empty() { - return Err(MintingPolicyViolation::AdditionalMintingNotAllowed); - } - - for _entry in &self.0 { - unreachable!("implement this when we have actual minting policies"); - } - - Ok(()) - } - - pub fn entries(&self) -> &[MintingPolicyEntry] { - &self.0 - } - - pub fn bytes(&self) -> Vec { - let bb: ByteBuilder = ByteBuilder::new(); - bb.u8(0).finalize_as_vec() - } - - pub fn hash(&self) -> PolicyHash { - let mut result = [0u8; POLICY_HASH_SIZE]; - if !self.0.is_empty() { - let mut hasher = Blake2b::new(POLICY_HASH_SIZE); - hasher.input(&self.bytes()); - hasher.result(&mut result); - } - PolicyHash(result) - } -} - -impl Default for MintingPolicy { - fn default() -> Self { - Self::new() - } -} - -impl Readable for MintingPolicy { - fn read(buf: &mut ReadBuf) -> Result { - let no_entries = buf.get_u8()?; - if no_entries != 0 { - return Err(ReadError::InvalidData( - "non-zero number of minting policy entries, but they are currently unimplemented" - .to_string(), - )); - } - Ok(Self::new()) - } -} - -impl PolicyHash { - pub fn bytes(&self) -> &[u8; POLICY_HASH_SIZE] { - &self.0 - } -} - -impl Readable for PolicyHash { - fn read(buf: &mut ReadBuf) -> Result { - let bytes = buf - .get_slice(POLICY_HASH_SIZE)? - .try_into() - .expect(&format!("already read {} bytes", POLICY_HASH_SIZE)); - Ok(Self(bytes)) - } -} - -impl TokenName { - pub fn try_from_bytes(b: Vec) -> Result { - if b.len() > TOKEN_NAME_MAX_SIZE { - return Err(TokenNameTooLong { actual: b.len() }); - } - Ok(Self(b)) - } - - pub fn bytes(&self) -> &[u8] { - &self.0 - } -} - -impl Readable for TokenName { - fn read(buf: &mut ReadBuf) -> Result { - let name_length = buf.get_u8()? as usize; - if name_length > TOKEN_NAME_MAX_SIZE { - return Err(ReadError::SizeTooBig(TOKEN_NAME_MAX_SIZE, name_length)); - } - let bytes = buf.get_slice(name_length)?.into(); - Ok(Self(bytes)) - } -} - -impl TokenIdentifier { - pub fn bytes(&self) -> Vec { - let bb: ByteBuilder = ByteBuilder::new(); - let token_name = self.token_name.bytes(); - bb.bytes(self.policy_hash.bytes()) - .u8(token_name.len() as u8) - .bytes(token_name) - .finalize_as_vec() - } -} - -impl Readable for TokenIdentifier { - fn read(buf: &mut ReadBuf) -> Result { - let policy_hash = PolicyHash::read(buf)?; - let token_name = TokenName::read(buf)?; - Ok(Self { - policy_hash, - token_name, - }) - } -} - -impl fmt::Display for TokenIdentifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(&self.policy_hash.bytes()))?; - let token_name = self.token_name.bytes(); - if !token_name.is_empty() { - write!(f, ".{}", hex::encode(token_name))?; - } - Ok(()) - } -} - -impl FromStr for TokenIdentifier { - type Err = TokenIdentifierParseError; - - fn from_str(s: &str) -> Result { - let mut parts = s.split("."); - - let policy_hash = { - let hex = parts.next().ok_or(TokenIdentifierParseError::EmptyStr)?; - let bytes = hex::decode(hex)?; - PolicyHash::read(&mut ReadBuf::from(&bytes))? - }; - - let token_name = { - let bytes = if let Some(hex) = parts.next() { - hex::decode(hex)? - } else { - Vec::new() - }; - TokenName::try_from_bytes(bytes)? - }; - - if parts.next().is_some() { - return Err(TokenIdentifierParseError::UnexpectedData); - } - - Ok(TokenIdentifier { - policy_hash, - token_name, - }) - } -} - -#[cfg(any(test, feature = "property-test-api"))] -mod tests { - use super::*; - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for PolicyHash { - fn arbitrary(g: &mut G) -> Self { - let mut bytes = [0u8; POLICY_HASH_SIZE]; - for i in &mut bytes { - *i = Arbitrary::arbitrary(g); - } - Self(bytes) - } - } - - impl Arbitrary for TokenName { - fn arbitrary(g: &mut G) -> Self { - let len = usize::arbitrary(g) % 33; - let mut bytes = Vec::with_capacity(len); - for _ in 0..len { - bytes.push(Arbitrary::arbitrary(g)); - } - Self(bytes) - } - } - - impl Arbitrary for TokenIdentifier { - fn arbitrary(g: &mut G) -> Self { - let policy_hash = Arbitrary::arbitrary(g); - let token_name = Arbitrary::arbitrary(g); - Self { - policy_hash, - token_name, - } - } - } - - impl Arbitrary for MintingPolicy { - fn arbitrary(_g: &mut G) -> Self { - Self::new() - } - } - - #[quickcheck_macros::quickcheck] - fn token_identifier_display_sanity(id: TokenIdentifier) { - let s = id.to_string(); - let id_: TokenIdentifier = s.parse().unwrap(); - assert_eq!(id, id_); - } -} diff --git a/chain-impl-mockchain/src/tokens/identifier.rs b/chain-impl-mockchain/src/tokens/identifier.rs new file mode 100644 index 000000000..62c08fb0b --- /dev/null +++ b/chain-impl-mockchain/src/tokens/identifier.rs @@ -0,0 +1,131 @@ +use crate::tokens::{ + name::{TokenName, TokenNameTooLong}, + policy_hash::PolicyHash, +}; + +use std::{convert::TryFrom, fmt, str::FromStr}; + +use chain_core::mempack::{ReadBuf, ReadError, Readable}; +use thiserror::Error; +use typed_bytes::ByteBuilder; + +/// The unique identifier of a token. +/// +/// It is represented either as two hex strings separated by a dot or just a hex string when the +/// name is empty. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct TokenIdentifier { + pub policy_hash: PolicyHash, + pub token_name: TokenName, +} + +/// Error during parsing the identifier string. +#[derive(Debug, Error)] +pub enum ParseError { + #[error("got an empty str")] + EmptyStr, + + #[error(transparent)] + Hex(#[from] hex::FromHexError), + + #[error(transparent)] + PolicyHash(#[from] ReadError), + + #[error("expected a token name after the `.`")] + ExpectedTokenName, + + #[error(transparent)] + TokenName(#[from] TokenNameTooLong), + + #[error("unexpected data after the token name")] + UnexpectedData, +} + +impl TokenIdentifier { + pub fn bytes(&self) -> Vec { + let bb: ByteBuilder = ByteBuilder::new(); + let token_name = self.token_name.as_ref(); + bb.bytes(self.policy_hash.as_ref()) + .u8(token_name.len() as u8) + .bytes(token_name) + .finalize_as_vec() + } +} + +impl Readable for TokenIdentifier { + fn read(buf: &mut ReadBuf) -> Result { + let policy_hash = PolicyHash::read(buf)?; + let token_name = TokenName::read(buf)?; + Ok(Self { + policy_hash, + token_name, + }) + } +} + +impl fmt::Display for TokenIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(&self.policy_hash.as_ref()))?; + let token_name = self.token_name.as_ref(); + if !token_name.is_empty() { + write!(f, ".{}", hex::encode(token_name))?; + } + Ok(()) + } +} + +impl FromStr for TokenIdentifier { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split("."); + + let policy_hash = { + let hex = parts.next().ok_or(ParseError::EmptyStr)?; + let bytes = hex::decode(hex)?; + PolicyHash::try_from(bytes.as_ref())? + }; + + let token_name = { + let bytes = if let Some(hex) = parts.next() { + hex::decode(hex)? + } else { + Vec::new() + }; + TokenName::try_from(bytes)? + }; + + if parts.next().is_some() { + return Err(ParseError::UnexpectedData); + } + + Ok(TokenIdentifier { + policy_hash, + token_name, + }) + } +} + +#[cfg(any(test, feature = "property-test-api"))] +mod tests { + use super::*; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for TokenIdentifier { + fn arbitrary(g: &mut G) -> Self { + let policy_hash = Arbitrary::arbitrary(g); + let token_name = Arbitrary::arbitrary(g); + Self { + policy_hash, + token_name, + } + } + } + + #[quickcheck_macros::quickcheck] + fn token_identifier_display_sanity(id: TokenIdentifier) { + let s = id.to_string(); + let id_: TokenIdentifier = s.parse().unwrap(); + assert_eq!(id, id_); + } +} diff --git a/chain-impl-mockchain/src/tokens/minting_policy.rs b/chain-impl-mockchain/src/tokens/minting_policy.rs new file mode 100644 index 000000000..52e9caecd --- /dev/null +++ b/chain-impl-mockchain/src/tokens/minting_policy.rs @@ -0,0 +1,94 @@ +use crate::tokens::policy_hash::{PolicyHash, POLICY_HASH_SIZE}; + +use chain_core::mempack::{ReadBuf, ReadError, Readable}; +use cryptoxide::{blake2b::Blake2b, digest::Digest}; +use thiserror::Error; +use typed_bytes::ByteBuilder; + +/// A minting policy consists of multiple entries defining different +/// constraints on the minting process. An empty policy means that new tokens +/// cannot be minted during the chain run. +/// +/// Minting policies are meant to be ignored in block0 fragments. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MintingPolicy(Vec); + +/// An entry of a minting policy. Currently there are no entries available. +/// This is reserved for the future use. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MintingPolicyEntry {} + +/// Error while checking a minting transaction against the current system state. +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum MintingPolicyViolation { + #[error("the policy of this token does not allow minting")] + AdditionalMintingNotAllowed, +} + +impl MintingPolicy { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn check_minting_tx(&self) -> Result<(), MintingPolicyViolation> { + if self.0.is_empty() { + return Err(MintingPolicyViolation::AdditionalMintingNotAllowed); + } + + for _entry in &self.0 { + unreachable!("implement this when we have actual minting policies"); + } + + Ok(()) + } + + pub fn entries(&self) -> &[MintingPolicyEntry] { + &self.0 + } + + pub fn bytes(&self) -> Vec { + let bb: ByteBuilder = ByteBuilder::new(); + bb.u8(0).finalize_as_vec() + } + + pub fn hash(&self) -> PolicyHash { + let mut result = [0u8; POLICY_HASH_SIZE]; + if !self.0.is_empty() { + let mut hasher = Blake2b::new(POLICY_HASH_SIZE); + hasher.input(&self.bytes()); + hasher.result(&mut result); + } + result.into() + } +} + +impl Default for MintingPolicy { + fn default() -> Self { + Self::new() + } +} + +impl Readable for MintingPolicy { + fn read(buf: &mut ReadBuf) -> Result { + let no_entries = buf.get_u8()?; + if no_entries != 0 { + return Err(ReadError::InvalidData( + "non-zero number of minting policy entries, but they are currently unimplemented" + .to_string(), + )); + } + Ok(Self::new()) + } +} + +#[cfg(any(test, feature = "property-test-api"))] +mod tests { + use super::*; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for MintingPolicy { + fn arbitrary(_g: &mut G) -> Self { + Self::new() + } + } +} diff --git a/chain-impl-mockchain/src/tokens/mod.rs b/chain-impl-mockchain/src/tokens/mod.rs new file mode 100644 index 000000000..00466e014 --- /dev/null +++ b/chain-impl-mockchain/src/tokens/mod.rs @@ -0,0 +1,4 @@ +pub mod identifier; +pub mod minting_policy; +pub mod name; +pub mod policy_hash; diff --git a/chain-impl-mockchain/src/tokens/name.rs b/chain-impl-mockchain/src/tokens/name.rs new file mode 100644 index 000000000..799ac4d6a --- /dev/null +++ b/chain-impl-mockchain/src/tokens/name.rs @@ -0,0 +1,66 @@ +use std::convert::TryFrom; + +use chain_core::mempack::{ReadBuf, ReadError, Readable}; +use thiserror::Error; + +pub const TOKEN_NAME_MAX_SIZE: usize = 32; + +/// A sequence of bytes serving as a token name. Tokens that share the same name but have different +/// voting policies hashes are different tokens. A name can be empty. The maximum length of a token +/// name is 32 bytes. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TokenName(Vec); + +#[derive(Debug, Error)] +#[error("Token name can be no more that {} bytes long; got {} bytes", TOKEN_NAME_MAX_SIZE, .actual)] +pub struct TokenNameTooLong { + actual: usize, +} + +impl AsRef<[u8]> for TokenName { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl TryFrom> for TokenName { + type Error = TokenNameTooLong; + + fn try_from(value: Vec) -> Result { + if value.len() > TOKEN_NAME_MAX_SIZE { + return Err(TokenNameTooLong { + actual: value.len(), + }); + } + Ok(Self(value)) + } +} + +impl Readable for TokenName { + fn read(buf: &mut ReadBuf) -> Result { + let name_length = buf.get_u8()? as usize; + if name_length > TOKEN_NAME_MAX_SIZE { + return Err(ReadError::SizeTooBig(TOKEN_NAME_MAX_SIZE, name_length)); + } + let bytes = buf.get_slice(name_length)?.into(); + Ok(Self(bytes)) + } +} + +#[cfg(any(test, feature = "property-test-api"))] +mod tests { + use super::*; + + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for TokenName { + fn arbitrary(g: &mut G) -> Self { + let len = usize::arbitrary(g) % (TOKEN_NAME_MAX_SIZE + 1); + let mut bytes = Vec::with_capacity(len); + for _ in 0..len { + bytes.push(Arbitrary::arbitrary(g)); + } + Self(bytes) + } + } +} diff --git a/chain-impl-mockchain/src/tokens/policy_hash.rs b/chain-impl-mockchain/src/tokens/policy_hash.rs new file mode 100644 index 000000000..77583af24 --- /dev/null +++ b/chain-impl-mockchain/src/tokens/policy_hash.rs @@ -0,0 +1,55 @@ +use std::convert::{TryFrom, TryInto}; + +use chain_core::mempack::{ReadBuf, ReadError, Readable}; + +pub const POLICY_HASH_SIZE: usize = 28; + +/// blake2b_224 hash of a serialized minting policy +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PolicyHash([u8; POLICY_HASH_SIZE]); + +impl AsRef<[u8]> for PolicyHash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From<[u8; POLICY_HASH_SIZE]> for PolicyHash { + fn from(bytes: [u8; POLICY_HASH_SIZE]) -> Self { + Self(bytes) + } +} + +impl TryFrom<&[u8]> for PolicyHash { + type Error = ReadError; + + fn try_from(value: &[u8]) -> Result { + Self::read(&mut ReadBuf::from(value)) + } +} + +impl Readable for PolicyHash { + fn read(buf: &mut ReadBuf) -> Result { + let bytes = buf + .get_slice(POLICY_HASH_SIZE)? + .try_into() + .expect(&format!("already read {} bytes", POLICY_HASH_SIZE)); + Ok(Self(bytes)) + } +} + +#[cfg(any(test, feature = "property-test-api"))] +mod tests { + use super::*; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for PolicyHash { + fn arbitrary(g: &mut G) -> Self { + let mut bytes = [0u8; POLICY_HASH_SIZE]; + for i in &mut bytes { + *i = Arbitrary::arbitrary(g); + } + Self(bytes) + } + } +}