diff --git a/pallas-applying/src/alonzo.rs b/pallas-applying/src/alonzo.rs index 3cf45cf..b417784 100644 --- a/pallas-applying/src/alonzo.rs +++ b/pallas-applying/src/alonzo.rs @@ -528,7 +528,7 @@ fn check_redeemers( Some(redeemers) => redeemers .iter() .map(|x| RedeemerPointer { - tag: x.tag.clone(), + tag: x.tag, index: x.index, }) .collect(), diff --git a/pallas-applying/src/babbage.rs b/pallas-applying/src/babbage.rs index ba8a1c5..759fb85 100644 --- a/pallas-applying/src/babbage.rs +++ b/pallas-applying/src/babbage.rs @@ -934,7 +934,7 @@ fn check_redeemers( Some(redeemers) => redeemers .iter() .map(|x| RedeemerPointer { - tag: x.tag.clone(), + tag: x.tag, index: x.index, }) .collect(), diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index 16d0f6f..c1c4e67 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -158,6 +158,139 @@ where } } +/// Custom collection to ensure ordered pairs of values (non-empty) +/// +/// Since the ordering of the entries requires a particular order to maintain +/// canonicalization for isomorphic decoding / encoding operators, we use a Vec +/// as the underlaying struct for storage of the items (as opposed to a BTreeMap +/// or HashMap). +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[serde(try_from = "Vec::<(K, V)>", into = "Vec::<(K, V)>")] +pub enum NonEmptyKeyValuePairs +where + K: Clone, + V: Clone, +{ + Def(Vec<(K, V)>), + Indef(Vec<(K, V)>), +} + +impl NonEmptyKeyValuePairs +where + K: Clone, + V: Clone, +{ + pub fn to_vec(self) -> Vec<(K, V)> { + self.into() + } +} + +impl From> for Vec<(K, V)> +where + K: Clone, + V: Clone, +{ + fn from(other: NonEmptyKeyValuePairs) -> Self { + match other { + NonEmptyKeyValuePairs::Def(x) => x, + NonEmptyKeyValuePairs::Indef(x) => x, + } + } +} + +impl TryFrom> for NonEmptyKeyValuePairs +where + K: Clone, + V: Clone, +{ + type Error = String; + + fn try_from(value: Vec<(K, V)>) -> Result { + if value.is_empty() { + Err("NonEmptyKeyValuePairs must contain at least one element".into()) + } else { + Ok(NonEmptyKeyValuePairs::Def(value)) + } + } +} + +impl Deref for NonEmptyKeyValuePairs +where + K: Clone, + V: Clone, +{ + type Target = Vec<(K, V)>; + + fn deref(&self) -> &Self::Target { + match self { + NonEmptyKeyValuePairs::Def(x) => x, + NonEmptyKeyValuePairs::Indef(x) => x, + } + } +} + +impl<'b, C, K, V> minicbor::decode::Decode<'b, C> for NonEmptyKeyValuePairs +where + K: Decode<'b, C> + Clone, + V: Decode<'b, C> + Clone, +{ + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let datatype = d.datatype()?; + + let items: Result, _> = d.map_iter_with::(ctx)?.collect(); + let items = items?; + + if items.is_empty() { + return Err(Error::message( + "decoding empty map as NonEmptyKeyValuePairs", + )); + } + + match datatype { + minicbor::data::Type::Map => Ok(NonEmptyKeyValuePairs::Def(items)), + minicbor::data::Type::MapIndef => Ok(NonEmptyKeyValuePairs::Indef(items)), + _ => Err(minicbor::decode::Error::message( + "invalid data type for nonemptykeyvaluepairs", + )), + } + } +} + +impl minicbor::encode::Encode for NonEmptyKeyValuePairs +where + K: Encode + Clone, + V: Encode + Clone, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + match self { + NonEmptyKeyValuePairs::Def(x) => { + e.map(x.len() as u64)?; + + for (k, v) in x.iter() { + k.encode(e, ctx)?; + v.encode(e, ctx)?; + } + } + NonEmptyKeyValuePairs::Indef(x) => { + e.begin_map()?; + + for (k, v) in x.iter() { + k.encode(e, ctx)?; + v.encode(e, ctx)?; + } + + e.end()?; + } + } + + Ok(()) + } +} + /// A struct that maintains a reference to whether a cbor array was indef or not #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub enum MaybeIndefArray { @@ -557,6 +690,95 @@ where } } +/// Non-empty Set +/// +/// Optional 258 tag (until era after Conway, at which point is it required) +/// with a vec of items which should contain no duplicates +#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Serialize, Deserialize)] +pub struct NonEmptySet(Vec); + +impl NonEmptySet { + pub fn to_vec(self) -> Vec { + self.0 + } +} + +impl Deref for NonEmptySet { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom> for NonEmptySet { + type Error = Vec; + + fn try_from(value: Vec) -> Result { + if value.is_empty() { + Err(value) + } else { + Ok(NonEmptySet(value)) + } + } +} + +impl From>> for NonEmptySet { + fn from(value: NonEmptySet>) -> Self { + let inner = value.0.into_iter().map(|x| x.unwrap()).collect(); + Self(inner) + } +} + +impl<'a, T> IntoIterator for &'a NonEmptySet { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'b, C, T> minicbor::decode::Decode<'b, C> for NonEmptySet +where + T: Decode<'b, C>, +{ + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + // decode optional set tag (this will be required in era following Conway) + if d.datatype()? == Type::Tag { + let found_tag = d.tag()?; + + if found_tag != Tag::Unassigned(TAG_SET) { + return Err(Error::message(format!("Unrecognised tag: {found_tag:?}"))); + } + } + + let inner: Vec = d.decode_with(ctx)?; + + if inner.is_empty() { + return Err(Error::message("decoding empty set as NonEmptySet")); + } + + Ok(Self(inner)) + } +} + +impl minicbor::encode::Encode for NonEmptySet +where + T: Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.tag(Tag::Unassigned(TAG_SET))?; + e.encode_with(&self.0, ctx)?; + + Ok(()) + } +} + /// A uint structure that preserves original int length #[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash)] pub enum AnyUInt { @@ -665,6 +887,110 @@ impl From<&AnyUInt> for u64 { } } +/// Introduced in Conway +/// positive_coin = 1 .. 18446744073709551615 +#[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct PositiveCoin(u64); + +impl TryFrom for PositiveCoin { + type Error = u64; + + fn try_from(value: u64) -> Result { + if value == 0 { + return Err(value); + } + + Ok(Self(value)) + } +} + +impl From for u64 { + fn from(value: PositiveCoin) -> Self { + value.0 + } +} + +impl<'b, C> minicbor::decode::Decode<'b, C> for PositiveCoin { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let n = d.decode_with(ctx)?; + + if n == 0 { + return Err(Error::message("decoding 0 as PositiveCoin")); + } + + Ok(Self(n)) + } +} + +impl minicbor::encode::Encode for PositiveCoin { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.encode(self.0)?; + + Ok(()) + } +} + +/// Introduced in Conway +/// negInt64 = -9223372036854775808 .. -1 +/// posInt64 = 1 .. 9223372036854775807 +/// nonZeroInt64 = negInt64 / posInt64 ; this is the same as the current int64 definition but without zero +#[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct NonZeroInt(i64); + +impl TryFrom for NonZeroInt { + type Error = i64; + + fn try_from(value: i64) -> Result { + if value == 0 { + return Err(value); + } + + Ok(Self(value)) + } +} + +impl From for i64 { + fn from(value: NonZeroInt) -> Self { + value.0 + } +} + +impl From<&NonZeroInt> for i64 { + fn from(x: &NonZeroInt) -> Self { + i64::from(*x) + } +} + +impl<'b, C> minicbor::decode::Decode<'b, C> for NonZeroInt { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + let n = d.decode_with(ctx)?; + + if n == 0 { + return Err(Error::message("decoding 0 as NonZeroInt")); + } + + Ok(Self(n)) + } +} + +impl minicbor::encode::Encode for NonZeroInt { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.encode(self.0)?; + + Ok(()) + } +} + /// Decodes a struct while preserving original CBOR /// /// # Examples @@ -812,7 +1138,7 @@ impl minicbor::Encode for AnyCbor { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(from = "Option::", into = "Option::")] pub enum Nullable where diff --git a/pallas-primitives/src/alonzo/model.rs b/pallas-primitives/src/alonzo/model.rs index eeb2527..e84daaf 100644 --- a/pallas-primitives/src/alonzo/model.rs +++ b/pallas-primitives/src/alonzo/model.rs @@ -110,7 +110,7 @@ pub struct Nonce { pub hash: Option>, } -pub type ScriptHash = Bytes; +pub type ScriptHash = Hash<28>; pub type PolicyId = Hash<28>; @@ -298,8 +298,8 @@ pub type DnsName = String; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum Relay { - SingleHostAddr(Option, Option, Option), - SingleHostName(Option, DnsName), + SingleHostAddr(Nullable, Nullable, Nullable), + SingleHostName(Nullable, DnsName), MultiHostName(DnsName), } @@ -473,7 +473,7 @@ pub enum Certificate { reward_account: RewardAccount, pool_owners: Vec, relays: Vec, - pool_metadata: Option, + pool_metadata: Nullable, }, PoolRetirement(PoolKeyhash, Epoch), GenesisKeyDelegation(Genesishash, GenesisDelegateHash, VrfKeyhash), @@ -1197,7 +1197,7 @@ where } } -#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)] pub struct ExUnits { #[n(0)] pub mem: u32, @@ -1214,7 +1214,7 @@ pub struct ExUnitPrices { step_price: PositiveInterval, } -#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)] #[cbor(index_only)] pub enum RedeemerTag { #[n(0)] @@ -1242,7 +1242,7 @@ pub struct Redeemer { pub ex_units: ExUnits, } -#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)] pub struct RedeemerPointer { #[n(0)] pub tag: RedeemerTag, diff --git a/pallas-primitives/src/conway/defs.cddl b/pallas-primitives/src/conway/defs.cddl index 5fd38a6..2e624a1 100644 --- a/pallas-primitives/src/conway/defs.cddl +++ b/pallas-primitives/src/conway/defs.cddl @@ -1,4 +1,4 @@ -; fetched 11 sep 2023 +; fetched 19 mar 2024 block = [ header @@ -40,24 +40,24 @@ header_body = ] operational_cert = - ( hot_vkey : $kes_vkey + [ hot_vkey : $kes_vkey , sequence_number : uint , kes_period : uint , sigma : $signature - ) + ] next_major_protocol_version = 10 major_protocol_version = 1..next_major_protocol_version -protocol_version = (major_protocol_version, uint) +protocol_version = [(major_protocol_version, uint)] transaction_body = { 0 : set ; inputs , 1 : [* transaction_output] , 2 : coin ; fee , ? 3 : uint ; time to live - , ? 4 : [+ certificate] + , ? 4 : certificates , ? 5 : withdrawals , ? 7 : auxiliary_data_hash , ? 8 : uint ; validity interval start @@ -70,7 +70,7 @@ transaction_body = , ? 17 : coin ; total collateral , ? 18 : nonempty_set ; reference inputs , ? 19 : voting_procedures ; New; Voting procedures - , ? 20 : [+ proposal_procedure] ; New; Proposal procedures + , ? 20 : proposal_procedures ; New; Proposal procedures , ? 21 : coin ; New; current treasury value , ? 22 : positive_coin ; New; donation } @@ -89,30 +89,34 @@ proposal_procedure = , anchor ] +proposal_procedures = nonempty_oset + +certificates = nonempty_oset + gov_action = [ parameter_change_action // hard_fork_initiation_action // treasury_withdrawals_action // no_confidence - // new_committee + // update_committee // new_constitution // info_action ] -parameter_change_action = (0, gov_action_id / null, protocol_param_update) +policy_hash = scripthash -hard_fork_initiation_action = (1, gov_action_id / null, [protocol_version]) +parameter_change_action = (0, gov_action_id / null, protocol_param_update, policy_hash / null) -treasury_withdrawals_action = (2, { $reward_account => coin }) +hard_fork_initiation_action = (1, gov_action_id / null, protocol_version) + +treasury_withdrawals_action = (2, { reward_account => coin }, policy_hash / null) no_confidence = (3, gov_action_id / null) -new_committee = (4, gov_action_id / null, set<$committee_cold_credential>, committee) +update_committee = (4, gov_action_id / null, set, { committee_cold_credential => epoch }, unit_interval) new_constitution = (5, gov_action_id / null, constitution) -committee = [{ $committee_cold_credential => epoch }, unit_interval] - constitution = [ anchor , scripthash / null @@ -148,15 +152,17 @@ gov_action_id = , gov_action_index : uint ] -required_signers = nonempty_set<$addr_keyhash> +required_signers = nonempty_set transaction_input = [ transaction_id : $hash32 , index : uint ] -transaction_output = legacy_transaction_output / post_alonzo_transaction_output +; Both of the Alonzo and Babbage style TxOut formats are equally valid +; and can be used interchangeably +transaction_output = pre_babbage_transaction_output / post_alonzo_transaction_output -legacy_transaction_output = +pre_babbage_transaction_output = [ address , amount : value , ? datum_hash : $hash32 @@ -310,7 +316,7 @@ stake_vote_reg_deleg_cert = (13, stake_credential, pool_keyhash, drep, coin) ; GOVCERT auth_committee_hot_cert = (14, committee_cold_credential, committee_hot_credential) -resign_committee_cold_cert = (15, committee_cold_credential) +resign_committee_cold_cert = (15, committee_cold_credential, anchor / null) reg_drep_cert = (16, drep_credential, coin, anchor / null) unreg_drep_cert = (17, drep_credential, coin) update_drep_cert = (18, drep_credential, anchor / null) @@ -349,7 +355,7 @@ pool_params = ( operator: pool_keyhash port = uint .le 65535 ipv4 = bytes .size 4 ipv6 = bytes .size 16 -dns_name = tstr .size (0..64) +dns_name = tstr .size (0..128) single_host_addr = ( 0 , port / null @@ -370,21 +376,21 @@ relay = ] pool_metadata = [url, pool_metadata_hash] -url = tstr .size (0..64) +url = tstr .size (0..128) withdrawals = { + reward_account => coin } protocol_param_update = - { ? 0: uint ; minfee A - , ? 1: uint ; minfee B + { ? 0: coin ; minfee A + , ? 1: coin ; minfee B , ? 2: uint ; max block body size , ? 3: uint ; max transaction size , ? 4: uint ; max block header size , ? 5: coin ; key deposit , ? 6: coin ; pool deposit - , ? 7: epoch ; maximum epoch - , ? 8: uint ; n_opt: desired number of stake pools - , ? 9: rational ; pool pledge influence + , ? 7: epoch ; maximum epoch + , ? 8: uint ; n_opt: desired number of stake pools + , ? 9: nonnegative_interval ; pool pledge influence , ? 10: unit_interval ; expansion rate , ? 11: unit_interval ; treasury growth rate , ? 16: coin ; min pool cost @@ -399,11 +405,12 @@ protocol_param_update = , ? 25: pool_voting_thresholds ; pool voting thresholds , ? 26: drep_voting_thresholds ; DRep voting thresholds , ? 27: uint ; min committee size - , ? 28: uint ; committee term limit + , ? 28: epoch ; committee term limit , ? 29: epoch ; governance action validity period , ? 30: coin ; governance action deposit , ? 31: coin ; DRep deposit , ? 32: epoch ; DRep inactivity period + , ? 33: nonnegative_interval ; MinFee RefScriptCostPerByte } pool_voting_thresholds = @@ -411,6 +418,7 @@ pool_voting_thresholds = , unit_interval ; committee normal , unit_interval ; committee no confidence , unit_interval ; hard fork initiation + , unit_interval ; security relevant parameter voting threshold ] drep_voting_thresholds = @@ -427,19 +435,23 @@ drep_voting_thresholds = ] transaction_witness_set = - { ? 0: [* vkeywitness ] - , ? 1: [* native_script ] - , ? 2: [* bootstrap_witness ] - , ? 3: [* plutus_v1_script ] - , ? 4: [* plutus_data ] - , ? 5: [* redeemer ] - , ? 6: [* plutus_v2_script ] - , ? 7: [* plutus_v3_script ] + { ? 0: nonempty_set + , ? 1: nonempty_set + , ? 2: nonempty_set + , ? 3: nonempty_set + , ? 4: nonempty_set + , ? 5: redeemers + , ? 6: nonempty_set + , ? 7: nonempty_set } -plutus_v1_script = bytes -plutus_v2_script = bytes -plutus_v3_script = bytes +; The real type of plutus_v1_script, plutus_v2_script and plutus_v3_script is bytes. +; However, because we enforce uniqueness when many scripts are supplied, +; we need to hack around for tests in order to avoid generating duplicates, +; since the cddl tool we use for roundtrip testing doesn't generate distinct collections. +plutus_v1_script = distinct +plutus_v2_script = distinct +plutus_v3_script = distinct plutus_data = constr @@ -463,17 +475,24 @@ constr = ; similarly for tag range: 6.1280 .. 6.1400 inclusive / #6.102([uint, [* a]]) -redeemer = [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] +; Flat Array support is included for backwards compatibility and will be removed in the next era. +; It is recommended for tools to adopt using a Map instead of Array going forward. +redeemers = + [ + [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] ] + / { + [ tag: redeemer_tag, index: uint ] => [ data: plutus_data, ex_units: ex_units ] } + redeemer_tag = - 0 ; inputTag "Spend" - / 1 ; mintTag "Mint" - / 2 ; certTag "Cert" - / 3 ; wdrlTag "Reward" - ; TODO / 4 ; drepTag "DRep" + 0 ; Spending + / 1 ; Minting + / 2 ; Certifying + / 3 ; Rewarding + / 4 ; Voting + / 5 ; Proposing + ex_units = [mem: uint, steps: uint] ex_unit_prices = - [ mem_price: sub_coin, step_price: sub_coin ] + [ mem_price: nonnegative_interval, step_price: nonnegative_interval ] language = 0 ; Plutus v1 / 1 ; Plutus v2 @@ -484,13 +503,12 @@ potential_languages = 0 .. 255 ; The format for costmdls is flexible enough to allow adding Plutus built-ins and language ; versions in the future. ; -; To construct valid cost models, however, you must restrict to: -; -; { ? 0 : [ 166* int ] ; Plutus v1, only 166 integers are used, but more are accepted (and ignored) -; , ? 1 : [ 175* int ] ; Plutus v2, only 175 integers are used, but more are accepted (and ignored) -; , ? 2 : [ 179* int ] ; Plutus v3, only 179 integers are used, but more are accepted (and ignored) -; } -costmdls = { * potential_languages => [int] } +costmdls = + { ? 0 : [ 166* int ] ; Plutus v1, only 166 integers are used, but more are accepted (and ignored) + , ? 1 : [ 175* int ] ; Plutus v2, only 175 integers are used, but more are accepted (and ignored) + , ? 2 : [ 233* int ] ; Plutus v3, only 233 integers are used, but more are accepted (and ignored) + , ? 3 : [ int ] ; Any 8-bit unsigned number can be used as a key. + } transaction_metadatum = { * transaction_metadatum => transaction_metadatum } @@ -545,8 +563,6 @@ invalid_hereafter = (5, uint) coin = uint -sub_coin = positive_interval - multiasset = { + policy_id => { + asset_name => a } } policy_id = scripthash asset_name = bytes .size (0..32) @@ -557,7 +573,7 @@ nonZeroInt64 = negInt64 / posInt64 ; this is the same as the current int64 defin positive_coin = 1 .. 18446744073709551615 -value = positive_coin / [positive_coin,multiasset] +value = coin / [coin, multiasset] mint = multiasset diff --git a/pallas-primitives/src/conway/model.rs b/pallas-primitives/src/conway/model.rs index e04ba49..da38e3c 100644 --- a/pallas-primitives/src/conway/model.rs +++ b/pallas-primitives/src/conway/model.rs @@ -2,18 +2,25 @@ //! //! Handcrafted, idiomatic rust artifacts based on based on the [Conway CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/conway/test-suite/cddl-files/conway.cddl) file in IOHK repo. +use std::ops::Deref; + +use pallas_codec::minicbor::decode::Error; use serde::{Deserialize, Serialize}; use pallas_codec::minicbor::{Decode, Encode}; use pallas_crypto::hash::Hash; -use pallas_codec::utils::{Bytes, KeepRaw, KeyValuePairs, MaybeIndefArray, Nullable, Set}; +use pallas_codec::utils::{ + Bytes, CborWrap, KeepRaw, KeyValuePairs, MaybeIndefArray, NonEmptyKeyValuePairs, NonEmptySet, + NonZeroInt, Nullable, PositiveCoin, Set, +}; // required for derive attrs to work use pallas_codec::minicbor; pub use crate::alonzo::VrfCert; +use crate::babbage; pub use crate::babbage::HeaderBody; pub use crate::babbage::OperationalCert; @@ -36,13 +43,59 @@ pub use crate::alonzo::PolicyId; pub use crate::alonzo::AssetName; -pub use crate::alonzo::Multiasset; +pub type Multiasset = NonEmptyKeyValuePairs>; pub use crate::alonzo::Mint; pub use crate::alonzo::Coin; -pub use crate::alonzo::Value; +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum Value { + Coin(Coin), + Multiasset(Coin, Multiasset), +} + +impl<'b, C> minicbor::decode::Decode<'b, C> for Value { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + match d.datatype()? { + minicbor::data::Type::U8 => Ok(Value::Coin(d.decode_with(ctx)?)), + minicbor::data::Type::U16 => Ok(Value::Coin(d.decode_with(ctx)?)), + minicbor::data::Type::U32 => Ok(Value::Coin(d.decode_with(ctx)?)), + minicbor::data::Type::U64 => Ok(Value::Coin(d.decode_with(ctx)?)), + minicbor::data::Type::Array => { + d.array()?; + let coin = d.decode_with(ctx)?; + let multiasset = d.decode_with(ctx)?; + Ok(Value::Multiasset(coin, multiasset)) + } + _ => Err(minicbor::decode::Error::message( + "unknown cbor data type for Alonzo Value enum", + )), + } + } +} + +impl minicbor::encode::Encode for Value { + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + // TODO: check how to deal with uint variants (u32 vs u64) + match self { + Value::Coin(coin) => { + e.encode_with(coin, ctx)?; + } + Value::Multiasset(coin, other) => { + e.array(2)?; + e.encode_with(coin, ctx)?; + e.encode_with(other, ctx)?; + } + }; + + Ok(()) + } +} pub use crate::alonzo::TransactionOutput as LegacyTransactionOutput; @@ -64,9 +117,9 @@ pub use crate::alonzo::MoveInstantaneousReward; pub use crate::alonzo::RewardAccount; -pub type Withdrawals = KeyValuePairs; +pub type Withdrawals = NonEmptyKeyValuePairs; -pub type RequiredSigners = Set; // TODO: NON EMPTY SET +pub type RequiredSigners = NonEmptySet; pub use crate::alonzo::Port; @@ -108,7 +161,7 @@ pub enum Certificate { reward_account: RewardAccount, pool_owners: Set, relays: Vec, - pool_metadata: Option, + pool_metadata: Nullable, }, PoolRetirement(PoolKeyhash, Epoch), @@ -121,10 +174,10 @@ pub enum Certificate { StakeVoteRegDeleg(StakeCredential, PoolKeyhash, DRep, Coin), AuthCommitteeHot(CommitteeColdCredential, CommitteeHotCredential), - ResignCommitteeCold(CommitteeColdCredential), - RegDRepCert(DRepCredential, Coin, Option), + ResignCommitteeCold(CommitteeColdCredential, Nullable), + RegDRepCert(DRepCredential, Coin, Nullable), UnRegDRepCert(DRepCredential, Coin), - UpdateDRepCert(StakeCredential, Option), + UpdateDRepCert(StakeCredential, Nullable), } impl<'b, C> minicbor::decode::Decode<'b, C> for Certificate { @@ -222,7 +275,8 @@ impl<'b, C> minicbor::decode::Decode<'b, C> for Certificate { } 15 => { let a = d.decode_with(ctx)?; - Ok(Certificate::ResignCommitteeCold(a)) + let b = d.decode_with(ctx)?; + Ok(Certificate::ResignCommitteeCold(a, b)) } 16 => { let a = d.decode_with(ctx)?; @@ -354,10 +408,11 @@ impl minicbor::encode::Encode for Certificate { e.encode_with(a, ctx)?; e.encode_with(b, ctx)?; } - Certificate::ResignCommitteeCold(a) => { - e.array(2)?; + Certificate::ResignCommitteeCold(a, b) => { + e.array(3)?; e.u16(15)?; e.encode_with(a, ctx)?; + e.encode_with(b, ctx)?; } Certificate::RegDRepCert(a, b, c) => { e.array(4)?; @@ -536,7 +591,7 @@ pub struct ProtocolParamUpdate { #[n(27)] pub min_committee_size: Option, #[n(28)] - pub committee_term_limit: Option, + pub committee_term_limit: Option, #[n(29)] pub governance_action_validity_period: Option, #[n(30)] @@ -545,6 +600,8 @@ pub struct ProtocolParamUpdate { pub drep_deposit: Option, #[n(32)] pub drep_inactivity_period: Option, + #[n(33)] + pub minfee_refscript_cost_per_byte: Option, } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -553,6 +610,7 @@ pub struct PoolVotingThresholds { pub committee_normal: UnitInterval, pub committee_no_confidence: UnitInterval, pub hard_fork_initiation: UnitInterval, + pub security_voting_threshold: UnitInterval, } impl<'b, C> minicbor::Decode<'b, C> for PoolVotingThresholds { @@ -564,6 +622,7 @@ impl<'b, C> minicbor::Decode<'b, C> for PoolVotingThresholds { committee_normal: d.decode_with(ctx)?, committee_no_confidence: d.decode_with(ctx)?, hard_fork_initiation: d.decode_with(ctx)?, + security_voting_threshold: d.decode_with(ctx)?, }) } } @@ -574,12 +633,13 @@ impl minicbor::Encode for PoolVotingThresholds { e: &mut minicbor::Encoder, ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { - e.array(4)?; + e.array(5)?; e.encode_with(&self.motion_no_confidence, ctx)?; e.encode_with(&self.committee_normal, ctx)?; e.encode_with(&self.committee_no_confidence, ctx)?; e.encode_with(&self.hard_fork_initiation, ctx)?; + e.encode_with(&self.security_voting_threshold, ctx)?; Ok(()) } @@ -657,13 +717,11 @@ pub struct PseudoTransactionBody { pub ttl: Option, #[n(4)] - pub certificates: Option>, // TODO: NON EMPTY ORDERED SET + pub certificates: Option>, #[n(5)] pub withdrawals: Option>, // TODO: NON EMPTY - // #[n(6)] - // pub update: Option, #[n(7)] pub auxiliary_data_hash: Option, @@ -671,16 +729,16 @@ pub struct PseudoTransactionBody { pub validity_interval_start: Option, #[n(9)] - pub mint: Option>, // TODO: MULTI ASSET NON EMPTY + pub mint: Option>, #[n(11)] pub script_data_hash: Option>, #[n(13)] - pub collateral: Option>, // TODO: NON EMPTY SET + pub collateral: Option>, #[n(14)] - pub required_signers: Option>, // TODO: NON EMPTY SET + pub required_signers: Option, #[n(15)] pub network_id: Option, @@ -692,20 +750,20 @@ pub struct PseudoTransactionBody { pub total_collateral: Option, #[n(18)] - pub reference_inputs: Option>, // TODO: NON EMPTY SET + pub reference_inputs: Option>, // -- NEW IN CONWAY #[n(19)] pub voting_procedures: Option, #[n(20)] - pub proposal_procedures: Option>, // TODO: NON EMPTY ORDERED SET + pub proposal_procedures: Option>, #[n(21)] pub treasury_value: Option, #[n(22)] - pub donation: Option, // TODO: NON ZERO (POSITIVE COIN) + pub donation: Option, } pub type TransactionBody = PseudoTransactionBody; @@ -778,12 +836,13 @@ impl minicbor::Encode for Vote { } } -pub type VotingProcedures = KeyValuePairs>; +pub type VotingProcedures = + NonEmptyKeyValuePairs>; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct VotingProcedure { pub vote: Vote, - pub anchor: Option, + pub anchor: Nullable, } impl<'b, C> minicbor::Decode<'b, C> for VotingProcedure { @@ -852,17 +911,21 @@ impl minicbor::Encode for ProposalProcedure { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum GovAction { - ParameterChange(Option, Box), - HardForkInitiation(Option, Vec), - TreasuryWithdrawals(KeyValuePairs), - NoConfidence(Option), + ParameterChange( + Nullable, + Box, + Nullable, + ), + HardForkInitiation(Nullable, ProtocolVersion), + TreasuryWithdrawals(KeyValuePairs, Nullable), + NoConfidence(Nullable), UpdateCommittee( - Option, + Nullable, Set, KeyValuePairs, UnitInterval, ), - NewConstitution(Option, Constitution), + NewConstitution(Nullable, Constitution), Information, } @@ -875,7 +938,8 @@ impl<'b, C> minicbor::decode::Decode<'b, C> for GovAction { 0 => { let a = d.decode_with(ctx)?; let b = d.decode_with(ctx)?; - Ok(GovAction::ParameterChange(a, b)) + let c = d.decode_with(ctx)?; + Ok(GovAction::ParameterChange(a, b, c)) } 1 => { let a = d.decode_with(ctx)?; @@ -884,7 +948,8 @@ impl<'b, C> minicbor::decode::Decode<'b, C> for GovAction { } 2 => { let a = d.decode_with(ctx)?; - Ok(GovAction::TreasuryWithdrawals(a)) + let b = d.decode_with(ctx)?; + Ok(GovAction::TreasuryWithdrawals(a, b)) } 3 => { let a = d.decode_with(ctx)?; @@ -917,11 +982,12 @@ impl minicbor::encode::Encode for GovAction { ctx: &mut C, ) -> Result<(), minicbor::encode::Error> { match self { - GovAction::ParameterChange(a, b) => { - e.array(3)?; + GovAction::ParameterChange(a, b, c) => { + e.array(4)?; e.u16(0)?; e.encode_with(a, ctx)?; e.encode_with(b, ctx)?; + e.encode_with(c, ctx)?; } GovAction::HardForkInitiation(a, b) => { e.array(3)?; @@ -929,10 +995,11 @@ impl minicbor::encode::Encode for GovAction { e.encode_with(a, ctx)?; e.encode_with(b, ctx)?; } - GovAction::TreasuryWithdrawals(a) => { - e.array(2)?; + GovAction::TreasuryWithdrawals(a, b) => { + e.array(3)?; e.u16(2)?; e.encode_with(a, ctx)?; + e.encode_with(b, ctx)?; } GovAction::NoConfidence(a) => { e.array(2)?; @@ -953,7 +1020,7 @@ impl minicbor::encode::Encode for GovAction { e.encode_with(a, ctx)?; e.encode_with(b, ctx)?; } - // TODO: CDDL SAYS JUST "6", no group (array) + // TODO: CDDL says just "6", not group/array "(6)"? GovAction::Information => { e.array(1)?; e.u16(6)?; @@ -964,8 +1031,8 @@ impl minicbor::encode::Encode for GovAction { } } -#[derive(Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord, Clone)] -pub struct Constitution(Anchor, Option); +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Constitution(Anchor, Nullable); impl<'b, C> minicbor::Decode<'b, C> for Constitution { fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { @@ -1153,15 +1220,37 @@ where } } -pub use crate::babbage::TransactionOutput; - -pub use crate::babbage::MintedTransactionOutput; - pub use crate::babbage::PseudoPostAlonzoTransactionOutput; -pub use crate::babbage::PostAlonzoTransactionOutput; +pub type TransactionOutput = PseudoTransactionOutput; -pub use crate::babbage::MintedPostAlonzoTransactionOutput; +pub type MintedTransactionOutput<'b> = + PseudoTransactionOutput>; + +impl<'b> From> for TransactionOutput { + fn from(value: MintedTransactionOutput<'b>) -> Self { + match value { + PseudoTransactionOutput::Legacy(x) => Self::Legacy(x), + PseudoTransactionOutput::PostAlonzo(x) => Self::PostAlonzo(x.into()), + } + } +} + +pub type PostAlonzoTransactionOutput = PseudoPostAlonzoTransactionOutput; + +pub type MintedPostAlonzoTransactionOutput<'b> = + PseudoPostAlonzoTransactionOutput, MintedScriptRef<'b>>; + +impl<'b> From> for PostAlonzoTransactionOutput { + fn from(value: MintedPostAlonzoTransactionOutput<'b>) -> Self { + Self { + address: value.address, + value: value.value, + datum_option: value.datum_option.map(|x| x.into()), + script_ref: value.script_ref.map(|x| CborWrap(x.unwrap().into())), + } + } +} pub use crate::alonzo::VKeyWitness; @@ -1189,9 +1278,16 @@ pub use crate::alonzo::Constr; pub use crate::alonzo::ExUnits; -pub use crate::alonzo::ExUnitPrices; - #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct ExUnitPrices { + #[n(0)] + mem_price: RationalNumber, + + #[n(1)] + step_price: RationalNumber, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)] #[cbor(index_only)] pub enum RedeemerTag { #[n(0)] @@ -1203,9 +1299,9 @@ pub enum RedeemerTag { #[n(3)] Reward, #[n(4)] - DRep, + Vote, #[n(5)] - VotingProposal, + Propose, } #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] @@ -1223,62 +1319,145 @@ pub struct Redeemer { pub ex_units: ExUnits, } +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct RedeemersKey { + #[n(0)] + pub tag: RedeemerTag, + #[n(1)] + pub index: u32, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct RedeemersValue { + #[n(0)] + pub data: PlutusData, + #[n(1)] + pub ex_units: ExUnits, +} + +// TODO: Redeemers needs to be KeepRaw because of script data hash +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Redeemers(NonEmptyKeyValuePairs); + +impl Deref for Redeemers { + type Target = NonEmptyKeyValuePairs; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Redeemers { + fn from(value: NonEmptyKeyValuePairs) -> Self { + Redeemers(value) + } +} + +impl<'b, C> minicbor::Decode<'b, C> for Redeemers { + fn decode(d: &mut minicbor::Decoder<'b>, ctx: &mut C) -> Result { + match d.datatype()? { + minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => { + let redeemers: Vec = d.decode_with(ctx)?; + + let kvs = redeemers + .into_iter() + .map(|x| { + ( + RedeemersKey { + tag: x.tag, + index: x.index, + }, + RedeemersValue { + data: x.data, + ex_units: x.ex_units, + }, + ) + }) + .collect::>() + .try_into() + .map_err(|_| Error::message("decoding empty redeemers"))?; + + Ok(Self(kvs)) + } + minicbor::data::Type::Map | minicbor::data::Type::MapIndef => { + Ok(Self(d.decode_with(ctx)?)) + } + _ => Err(minicbor::decode::Error::message( + "invalid type for redeemers struct", + )), + } + } +} + +impl minicbor::Encode for Redeemers { + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.encode_with(&self.0, ctx)?; + + Ok(()) + } +} + pub use crate::alonzo::BootstrapWitness; #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct WitnessSet { #[n(0)] - pub vkeywitness: Option>, // TODO: NON EMPTY SET + pub vkeywitness: Option>, #[n(1)] - pub native_script: Option>, // TODO: NON EMPTY SET + pub native_script: Option>, #[n(2)] - pub bootstrap_witness: Option>, // TODO: NON EMPTY SET + pub bootstrap_witness: Option>, #[n(3)] - pub plutus_v1_script: Option>, // TODO: NON EMPTY SET + pub plutus_v1_script: Option>, #[n(4)] - pub plutus_data: Option>, // TODO: NON EMPTY SET + pub plutus_data: Option>, #[n(5)] - pub redeemer: Option>, + pub redeemer: Option, #[n(6)] - pub plutus_v2_script: Option>, // TODO: NON EMPTY SET + pub plutus_v2_script: Option>, #[n(7)] - pub plutus_v3_script: Option>, // TODO: NON EMPTY SET + pub plutus_v3_script: Option>, } #[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct MintedWitnessSet<'b> { #[n(0)] - pub vkeywitness: Option>, // TODO: NON EMPTY SET + pub vkeywitness: Option>, #[n(1)] - pub native_script: Option>>, // TODO: NON EMPTY SET + pub native_script: Option>>, #[n(2)] - pub bootstrap_witness: Option>, // TODO: NON EMPTY SET + pub bootstrap_witness: Option>, #[n(3)] - pub plutus_v1_script: Option>, // TODO: NON EMPTY SET + pub plutus_v1_script: Option>, #[b(4)] - pub plutus_data: Option>>, // TODO: NON EMPTY SET + pub plutus_data: Option>>, #[n(5)] - pub redeemer: Option>, + pub redeemer: Option>, #[n(6)] - pub plutus_v2_script: Option>, // TODO: NON EMPTY SET + pub plutus_v2_script: Option>, #[n(7)] - pub plutus_v3_script: Option>, // TODO: NON EMPTY SET + pub plutus_v3_script: Option>, } impl<'b> From> for WitnessSet { @@ -1289,7 +1468,7 @@ impl<'b> From> for WitnessSet { bootstrap_witness: x.bootstrap_witness, plutus_v1_script: x.plutus_v1_script, plutus_data: x.plutus_data.map(Into::into), - redeemer: x.redeemer, + redeemer: x.redeemer.map(|x| x.unwrap()), plutus_v2_script: x.plutus_v2_script, plutus_v3_script: x.plutus_v3_script, } @@ -1348,6 +1527,17 @@ impl<'b> From> for ScriptRef { } } +// TODO: Remove in favour of multierascriptref +impl<'b> From> for MintedScriptRef<'b> { + fn from(value: babbage::MintedScriptRef<'b>) -> Self { + match value { + babbage::MintedScriptRef::NativeScript(x) => Self::NativeScript(x), + babbage::MintedScriptRef::PlutusV1Script(x) => Self::PlutusV1Script(x), + babbage::MintedScriptRef::PlutusV2Script(x) => Self::PlutusV2Script(x), + } + } +} + impl<'b, C, T> minicbor::Decode<'b, C> for PseudoScript where T: minicbor::Decode<'b, ()>, @@ -1363,9 +1553,10 @@ where 1 => Ok(Self::PlutusV1Script(d.decode()?)), 2 => Ok(Self::PlutusV2Script(d.decode()?)), 3 => Ok(Self::PlutusV3Script(d.decode()?)), - _ => Err(minicbor::decode::Error::message( - "invalid variant for script enum", - )), + x => Err(minicbor::decode::Error::message(format!( + "invalid variant for script enum: {}", + x + ))), } } } @@ -1518,7 +1709,7 @@ mod tests { fn block_isomorphic_decoding_encoding() { let test_blocks = [ include_str!("../../../test_data/conway1.block"), - include_str!("../../../test_data/conway1.artificial.block"), + include_str!("../../../test_data/conway2.block"), ]; for (idx, block_str) in test_blocks.iter().enumerate() { diff --git a/pallas-traverse/src/assets.rs b/pallas-traverse/src/assets.rs index ecfc097..b69adff 100644 --- a/pallas-traverse/src/assets.rs +++ b/pallas-traverse/src/assets.rs @@ -7,13 +7,15 @@ impl<'b> MultiEraPolicyAssets<'b> { match self { MultiEraPolicyAssets::AlonzoCompatibleMint(x, _) => x, MultiEraPolicyAssets::AlonzoCompatibleOutput(x, _) => x, + MultiEraPolicyAssets::ConwayMint(x, _) => x, } } pub fn is_output(&self) -> bool { match self { - MultiEraPolicyAssets::AlonzoCompatibleOutput(_, _) => true, MultiEraPolicyAssets::AlonzoCompatibleMint(_, _) => false, + MultiEraPolicyAssets::AlonzoCompatibleOutput(_, _) => true, + MultiEraPolicyAssets::ConwayMint(_, _) => false, } } @@ -21,6 +23,7 @@ impl<'b> MultiEraPolicyAssets<'b> { match self { MultiEraPolicyAssets::AlonzoCompatibleMint(_, _) => true, MultiEraPolicyAssets::AlonzoCompatibleOutput(_, _) => false, + MultiEraPolicyAssets::ConwayMint(_, _) => true, } } @@ -34,6 +37,10 @@ impl<'b> MultiEraPolicyAssets<'b> { .iter() .map(|(k, v)| MultiEraAsset::AlonzoCompatibleOutput(p, k, *v)) .collect(), + MultiEraPolicyAssets::ConwayMint(p, x) => x + .iter() + .map(|(k, v)| MultiEraAsset::ConwayMint(p, k, *v)) + .collect(), } } @@ -48,6 +55,10 @@ impl<'b> MultiEraPolicyAssets<'b> { MultiEraPolicyAssets::AlonzoCompatibleOutput(_, x) => { x.iter().map(|(k, v)| (k.as_slice(), *v as i128)).collect() } + MultiEraPolicyAssets::ConwayMint(_, x) => x + .iter() + .map(|(k, v)| (k.as_slice(), i64::from(v) as i128)) + .collect(), } } } @@ -57,20 +68,23 @@ impl<'b> MultiEraAsset<'b> { match self { MultiEraAsset::AlonzoCompatibleMint(x, ..) => x, MultiEraAsset::AlonzoCompatibleOutput(x, ..) => x, + MultiEraAsset::ConwayMint(x, ..) => x, } } pub fn name(&self) -> &[u8] { match self { - MultiEraAsset::AlonzoCompatibleOutput(_, x, _) => x, MultiEraAsset::AlonzoCompatibleMint(_, x, _) => x, + MultiEraAsset::AlonzoCompatibleOutput(_, x, _) => x, + MultiEraAsset::ConwayMint(_, x, _) => x, } } pub fn is_output(&self) -> bool { match self { - MultiEraAsset::AlonzoCompatibleOutput(..) => true, MultiEraAsset::AlonzoCompatibleMint(..) => false, + MultiEraAsset::AlonzoCompatibleOutput(..) => true, + MultiEraAsset::ConwayMint(..) => false, } } @@ -78,6 +92,7 @@ impl<'b> MultiEraAsset<'b> { match self { MultiEraAsset::AlonzoCompatibleMint(..) => true, MultiEraAsset::AlonzoCompatibleOutput(..) => false, + MultiEraAsset::ConwayMint(..) => true, } } @@ -85,20 +100,23 @@ impl<'b> MultiEraAsset<'b> { match self { MultiEraAsset::AlonzoCompatibleMint(_, _, x) => Some(*x), MultiEraAsset::AlonzoCompatibleOutput(_, _, _) => None, + MultiEraAsset::ConwayMint(_, _, x) => Some(x.into()), } } pub fn output_coin(&self) -> Option { match self { - MultiEraAsset::AlonzoCompatibleOutput(_, _, x) => Some(*x), MultiEraAsset::AlonzoCompatibleMint(_, _, _) => None, + MultiEraAsset::AlonzoCompatibleOutput(_, _, x) => Some(*x), + MultiEraAsset::ConwayMint(_, _, _) => None, } } pub fn any_coin(&self) -> i128 { match self { - MultiEraAsset::AlonzoCompatibleOutput(_, _, x) => *x as i128, MultiEraAsset::AlonzoCompatibleMint(_, _, x) => *x as i128, + MultiEraAsset::AlonzoCompatibleOutput(_, _, x) => *x as i128, + MultiEraAsset::ConwayMint(_, _, x) => i64::from(x) as i128, } } diff --git a/pallas-traverse/src/lib.rs b/pallas-traverse/src/lib.rs index 0406942..f278359 100644 --- a/pallas-traverse/src/lib.rs +++ b/pallas-traverse/src/lib.rs @@ -4,7 +4,7 @@ use std::{borrow::Cow, fmt::Display, hash::Hash as StdHash}; use thiserror::Error; -use pallas_codec::utils::{KeepRaw, KeyValuePairs}; +use pallas_codec::utils::{KeepRaw, KeyValuePairs, NonEmptyKeyValuePairs, NonZeroInt}; use pallas_crypto::hash::Hash; use pallas_primitives::{alonzo, babbage, byron, conway}; @@ -22,6 +22,7 @@ pub mod input; pub mod meta; pub mod output; pub mod probe; +pub mod redeemers; pub mod signers; pub mod size; pub mod time; @@ -90,6 +91,7 @@ pub enum MultiEraTx<'b> { pub enum MultiEraOutput<'b> { AlonzoCompatible(Box>), Babbage(Box>>), + Conway(Box>>), Byron(Box>), } @@ -108,6 +110,16 @@ pub enum MultiEraCert<'b> { Conway(Box>), } +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum MultiEraRedeemer<'b> { + AlonzoCompatible(Box>), + Conway( + Box>, + Box>, + ), +} + #[derive(Debug, Clone, Default)] #[non_exhaustive] pub enum MultiEraMeta<'b> { @@ -128,6 +140,10 @@ pub enum MultiEraPolicyAssets<'b> { &'b alonzo::PolicyId, &'b KeyValuePairs, ), + ConwayMint( + &'b alonzo::PolicyId, + &'b NonEmptyKeyValuePairs, + ), } #[derive(Debug, Clone)] @@ -135,6 +151,7 @@ pub enum MultiEraPolicyAssets<'b> { pub enum MultiEraAsset<'b> { AlonzoCompatibleOutput(&'b alonzo::PolicyId, &'b alonzo::AssetName, u64), AlonzoCompatibleMint(&'b alonzo::PolicyId, &'b alonzo::AssetName, i64), + ConwayMint(&'b alonzo::PolicyId, &'b alonzo::AssetName, NonZeroInt), } #[derive(Debug, Clone)] diff --git a/pallas-traverse/src/output.rs b/pallas-traverse/src/output.rs index b9379bd..9749e46 100644 --- a/pallas-traverse/src/output.rs +++ b/pallas-traverse/src/output.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref}; use pallas_addresses::{Address, ByronAddress, Error as AddressError}; use pallas_codec::minicbor; -use pallas_primitives::{alonzo, babbage, byron}; +use pallas_primitives::{alonzo, babbage, byron, conway}; use crate::{Era, MultiEraOutput, MultiEraPolicyAssets}; @@ -19,6 +19,10 @@ impl<'b> MultiEraOutput<'b> { Self::Babbage(Box::new(Cow::Borrowed(output))) } + pub fn from_conway(output: &'b conway::MintedTransactionOutput<'b>) -> Self { + Self::Conway(Box::new(Cow::Borrowed(output))) + } + pub fn datum(&self) -> Option { match self { MultiEraOutput::AlonzoCompatible(x) => { @@ -30,19 +34,32 @@ impl<'b> MultiEraOutput<'b> { } babbage::MintedTransactionOutput::PostAlonzo(x) => x.datum_option.clone(), }, - _ => None, + MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(x) => match x.deref().deref() { + conway::MintedTransactionOutput::Legacy(x) => { + x.datum_hash.map(babbage::MintedDatumOption::Hash) + } + conway::MintedTransactionOutput::PostAlonzo(x) => x.datum_option.clone(), + }, } } - pub fn script_ref(&self) -> Option { + pub fn script_ref(&self) -> Option { match &self { + MultiEraOutput::AlonzoCompatible(_) => None, MultiEraOutput::Babbage(x) => match x.deref().deref() { babbage::MintedTransactionOutput::Legacy(_) => None, babbage::MintedTransactionOutput::PostAlonzo(x) => { + x.script_ref.clone().map(|x| x.unwrap().into()) + } + }, + MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(x) => match x.deref().deref() { + conway::MintedTransactionOutput::Legacy(_) => None, + conway::MintedTransactionOutput::PostAlonzo(x) => { x.script_ref.clone().map(|x| x.unwrap()) } }, - _ => None, } } @@ -56,14 +73,10 @@ impl<'b> MultiEraOutput<'b> { MultiEraOutput::Byron(x) => { Ok(ByronAddress::new(&x.address.payload.0, x.address.crc).into()) } - } - } - - pub fn as_babbage(&self) -> Option<&babbage::MintedTransactionOutput> { - match self { - MultiEraOutput::AlonzoCompatible(_) => None, - MultiEraOutput::Babbage(x) => Some(x), - MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(x) => match x.deref().deref() { + conway::MintedTransactionOutput::Legacy(x) => Address::from_bytes(&x.address), + conway::MintedTransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address), + }, } } @@ -72,6 +85,16 @@ impl<'b> MultiEraOutput<'b> { MultiEraOutput::AlonzoCompatible(x) => Some(x), MultiEraOutput::Babbage(_) => None, MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(_) => None, + } + } + + pub fn as_babbage(&self) -> Option<&babbage::MintedTransactionOutput> { + match self { + MultiEraOutput::AlonzoCompatible(_) => None, + MultiEraOutput::Babbage(x) => Some(x), + MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(_) => None, } } @@ -80,6 +103,16 @@ impl<'b> MultiEraOutput<'b> { MultiEraOutput::AlonzoCompatible(_) => None, MultiEraOutput::Babbage(_) => None, MultiEraOutput::Byron(x) => Some(x), + MultiEraOutput::Conway(_) => None, + } + } + + pub fn as_conway(&self) -> Option<&conway::MintedTransactionOutput> { + match self { + MultiEraOutput::AlonzoCompatible(_) => None, + MultiEraOutput::Babbage(_) => None, + MultiEraOutput::Byron(_) => None, + MultiEraOutput::Conway(x) => Some(x), } } @@ -89,6 +122,7 @@ impl<'b> MultiEraOutput<'b> { Self::AlonzoCompatible(x) => minicbor::to_vec(x).unwrap(), Self::Babbage(x) => minicbor::to_vec(x).unwrap(), Self::Byron(x) => minicbor::to_vec(x).unwrap(), + Self::Conway(x) => minicbor::to_vec(x).unwrap(), } } @@ -104,11 +138,16 @@ impl<'b> MultiEraOutput<'b> { let tx = Box::new(Cow::Owned(tx)); Ok(Self::AlonzoCompatible(tx)) } - Era::Babbage | Era::Conway => { + Era::Babbage => { let tx = minicbor::decode(cbor)?; let tx = Box::new(Cow::Owned(tx)); Ok(Self::Babbage(tx)) } + Era::Conway => { + let tx = minicbor::decode(cbor)?; + let tx = Box::new(Cow::Owned(tx)); + Ok(Self::Conway(tx)) + } } } @@ -119,7 +158,10 @@ impl<'b> MultiEraOutput<'b> { /// lovelace). pub fn lovelace_amount(&self) -> u64 { match self { - MultiEraOutput::Byron(x) => x.amount, + MultiEraOutput::AlonzoCompatible(x) => match x.amount { + alonzo::Value::Coin(c) => c, + alonzo::Value::Multiasset(c, _) => c, + }, MultiEraOutput::Babbage(x) => match x.deref().deref() { babbage::MintedTransactionOutput::Legacy(x) => match x.amount { babbage::Value::Coin(c) => c, @@ -130,9 +172,16 @@ impl<'b> MultiEraOutput<'b> { babbage::Value::Multiasset(c, _) => c, }, }, - MultiEraOutput::AlonzoCompatible(x) => match x.amount { - alonzo::Value::Coin(c) => c, - alonzo::Value::Multiasset(c, _) => c, + MultiEraOutput::Byron(x) => x.amount, + MultiEraOutput::Conway(x) => match x.deref().deref() { + conway::MintedTransactionOutput::Legacy(x) => match x.amount { + babbage::Value::Coin(c) => c, + babbage::Value::Multiasset(c, _) => c, + }, + conway::MintedTransactionOutput::PostAlonzo(x) => match x.value { + babbage::Value::Coin(c) => c, + babbage::Value::Multiasset(c, _) => c, + }, }, } } @@ -144,7 +193,13 @@ impl<'b> MultiEraOutput<'b> { /// list. pub fn non_ada_assets(&self) -> Vec { match self { - MultiEraOutput::Byron(_) => vec![], + MultiEraOutput::AlonzoCompatible(x) => match &x.amount { + alonzo::Value::Coin(_) => vec![], + alonzo::Value::Multiasset(_, x) => x + .iter() + .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleOutput(k, v)) + .collect(), + }, MultiEraOutput::Babbage(x) => match x.deref().deref() { babbage::MintedTransactionOutput::Legacy(x) => match &x.amount { babbage::Value::Coin(_) => vec![], @@ -161,12 +216,22 @@ impl<'b> MultiEraOutput<'b> { .collect(), }, }, - MultiEraOutput::AlonzoCompatible(x) => match &x.amount { - alonzo::Value::Coin(_) => vec![], - alonzo::Value::Multiasset(_, x) => x - .iter() - .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleOutput(k, v)) - .collect(), + MultiEraOutput::Byron(_) => vec![], + MultiEraOutput::Conway(x) => match x.deref().deref() { + conway::MintedTransactionOutput::Legacy(x) => match &x.amount { + babbage::Value::Coin(_) => vec![], + babbage::Value::Multiasset(_, x) => x + .iter() + .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleOutput(k, v)) + .collect(), + }, + conway::MintedTransactionOutput::PostAlonzo(x) => match &x.value { + babbage::Value::Coin(_) => vec![], + babbage::Value::Multiasset(_, x) => x + .iter() + .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleOutput(k, v)) + .collect(), + }, }, } } diff --git a/pallas-traverse/src/probe.rs b/pallas-traverse/src/probe.rs index 996a15d..72c09f2 100644 --- a/pallas-traverse/src/probe.rs +++ b/pallas-traverse/src/probe.rs @@ -113,7 +113,7 @@ mod tests { #[test] fn conway_block_detected() { - let block_str = include_str!("../../test_data/conway1.artificial.block"); + let block_str = include_str!("../../test_data/conway1.block"); let bytes = hex::decode(block_str).unwrap(); let inference = block_era(bytes.as_slice()); diff --git a/pallas-traverse/src/redeemers.rs b/pallas-traverse/src/redeemers.rs new file mode 100644 index 0000000..83aa2e4 --- /dev/null +++ b/pallas-traverse/src/redeemers.rs @@ -0,0 +1,68 @@ +use std::borrow::Cow; + +use pallas_primitives::{alonzo, conway}; + +use crate::MultiEraRedeemer; + +impl<'b> MultiEraRedeemer<'b> { + pub fn tag(&self) -> conway::RedeemerTag { + match &self { + Self::AlonzoCompatible(x) => match x.tag { + alonzo::RedeemerTag::Cert => conway::RedeemerTag::Cert, + alonzo::RedeemerTag::Spend => conway::RedeemerTag::Spend, + alonzo::RedeemerTag::Mint => conway::RedeemerTag::Mint, + alonzo::RedeemerTag::Reward => conway::RedeemerTag::Reward, + }, + Self::Conway(x, _) => x.tag, + } + } + + pub fn data(&self) -> &alonzo::PlutusData { + match &self { + Self::AlonzoCompatible(x) => &x.data, + Self::Conway(_, x) => &x.data, + } + } + + pub fn ex_units(&self) -> alonzo::ExUnits { + match &self { + Self::AlonzoCompatible(x) => x.ex_units, + Self::Conway(_, x) => x.ex_units, + } + } + + pub fn index(&self) -> u32 { + match self { + Self::AlonzoCompatible(x) => x.index, + Self::Conway(x, _) => x.index, + } + } + + pub fn as_alonzo(&self) -> Option<&alonzo::Redeemer> { + match self { + Self::AlonzoCompatible(x) => Some(x), + Self::Conway(..) => None, + } + } + + pub fn as_conway(&self) -> Option<(&conway::RedeemersKey, &conway::RedeemersValue)> { + match self { + Self::AlonzoCompatible(_) => None, + Self::Conway(x, y) => Some((x, y)), + } + } + + pub fn from_alonzo_compatible(redeemer: &'b alonzo::Redeemer) -> Self { + Self::AlonzoCompatible(Box::new(Cow::Borrowed(redeemer))) + } + + pub fn from_conway( + redeemers_key: &'b conway::RedeemersKey, + redeemers_val: &'b conway::RedeemersValue, + ) -> Self { + Self::Conway( + Box::new(Cow::Borrowed(redeemers_key)), + Box::new(Cow::Borrowed(redeemers_val)), + ) + } +} diff --git a/pallas-traverse/src/tx.rs b/pallas-traverse/src/tx.rs index 0231688..0f33e21 100644 --- a/pallas-traverse/src/tx.rs +++ b/pallas-traverse/src/tx.rs @@ -134,7 +134,7 @@ impl<'b> MultiEraTx<'b> { .transaction_body .outputs .iter() - .map(MultiEraOutput::from_babbage) + .map(MultiEraOutput::from_conway) .collect(), } } @@ -160,7 +160,7 @@ impl<'b> MultiEraTx<'b> { .transaction_body .outputs .get(index) - .map(MultiEraOutput::from_babbage), + .map(MultiEraOutput::from_conway), } } @@ -281,13 +281,12 @@ impl<'b> MultiEraTx<'b> { .flat_map(|x| x.iter()) .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleMint(k, v)) .collect(), - // TODO: Is this still AlonzoCompatible? Zero vals not allowed or something MultiEraTx::Conway(x) => x .transaction_body .mint .iter() .flat_map(|x| x.iter()) - .map(|(k, v)| MultiEraPolicyAssets::AlonzoCompatibleMint(k, v)) + .map(|(k, v)| MultiEraPolicyAssets::ConwayMint(k, v)) .collect(), } } @@ -334,7 +333,7 @@ impl<'b> MultiEraTx<'b> { .transaction_body .collateral_return .as_ref() - .map(MultiEraOutput::from_babbage), + .map(MultiEraOutput::from_conway), _ => None, } } @@ -523,12 +522,11 @@ impl<'b> MultiEraTx<'b> { .map(MultiEraSigners::AlonzoCompatible) .unwrap_or_default(), MultiEraTx::Byron(_) => MultiEraSigners::NotApplicable, - // TODO: still compat? MultiEraTx::Conway(x) => x .transaction_body .required_signers .as_ref() - .map(MultiEraSigners::AlonzoCompatible) + .map(|x| MultiEraSigners::AlonzoCompatible(x.deref())) .unwrap_or_default(), } } diff --git a/pallas-traverse/src/witnesses.rs b/pallas-traverse/src/witnesses.rs index fca01eb..17e3062 100644 --- a/pallas-traverse/src/witnesses.rs +++ b/pallas-traverse/src/witnesses.rs @@ -1,11 +1,11 @@ use pallas_codec::utils::KeepRaw; use pallas_primitives::{ alonzo::{self, BootstrapWitness, NativeScript, PlutusData, VKeyWitness}, - babbage::{PlutusV2Script, Redeemer}, - conway::{self, PlutusV3Script}, + babbage::PlutusV2Script, + conway::PlutusV3Script, }; -use crate::MultiEraTx; +use crate::{MultiEraRedeemer, MultiEraTx}; impl<'b> MultiEraTx<'b> { pub fn vkey_witnesses(&self) -> &[VKeyWitness] { @@ -128,37 +128,30 @@ impl<'b> MultiEraTx<'b> { } } - // TODO: MultiEraRedeemer? - pub fn redeemers(&self) -> &[Redeemer] { + pub fn redeemers(&self) -> Vec { match self { - Self::Byron(_) => &[], + Self::Byron(_) => vec![], Self::AlonzoCompatible(x, _) => x .transaction_witness_set .redeemer - .as_ref() - .map(|x| x.as_ref()) - .unwrap_or(&[]), + .iter() + .flat_map(|x| x.iter()) + .map(MultiEraRedeemer::from_alonzo_compatible) + .collect(), Self::Babbage(x) => x .transaction_witness_set .redeemer - .as_ref() - .map(|x| x.as_ref()) - .unwrap_or(&[]), - Self::Conway(_) => &[], - } - } - - pub fn conway_redeemers(&self) -> &[conway::Redeemer] { - match self { - Self::Byron(_) => &[], - Self::AlonzoCompatible(_, _) => &[], - Self::Babbage(_) => &[], + .iter() + .flat_map(|x| x.iter()) + .map(MultiEraRedeemer::from_alonzo_compatible) + .collect(), Self::Conway(x) => x .transaction_witness_set .redeemer - .as_ref() - .map(|x| x.as_ref()) - .unwrap_or(&[]), + .iter() + .flat_map(|x| x.iter()) + .map(|(k, v)| MultiEraRedeemer::from_conway(k, v)) + .collect(), } } diff --git a/pallas-utxorpc/src/lib.rs b/pallas-utxorpc/src/lib.rs index f91c866..7d5e967 100644 --- a/pallas-utxorpc/src/lib.rs +++ b/pallas-utxorpc/src/lib.rs @@ -1,34 +1,35 @@ use std::ops::Deref; use pallas_codec::utils::KeyValuePairs; -use pallas_primitives::{alonzo, babbage}; +use pallas_primitives::{alonzo, babbage, conway}; use pallas_traverse as trv; use trv::OriginalHash; use utxorpc_spec::utxorpc::v1alpha::cardano as u5c; -pub fn map_purpose(x: &alonzo::RedeemerTag) -> u5c::RedeemerPurpose { +pub fn map_purpose(x: &conway::RedeemerTag) -> u5c::RedeemerPurpose { match x { - babbage::RedeemerTag::Spend => u5c::RedeemerPurpose::Spend, - babbage::RedeemerTag::Mint => u5c::RedeemerPurpose::Mint, - babbage::RedeemerTag::Cert => u5c::RedeemerPurpose::Cert, - babbage::RedeemerTag::Reward => u5c::RedeemerPurpose::Reward, + conway::RedeemerTag::Spend => u5c::RedeemerPurpose::Spend, + conway::RedeemerTag::Mint => u5c::RedeemerPurpose::Mint, + conway::RedeemerTag::Cert => u5c::RedeemerPurpose::Cert, + conway::RedeemerTag::Reward => u5c::RedeemerPurpose::Reward, + conway::RedeemerTag::Vote => todo!(), + conway::RedeemerTag::Propose => todo!(), } } -pub fn map_redeemer(x: &alonzo::Redeemer) -> u5c::Redeemer { +pub fn map_redeemer(x: &trv::MultiEraRedeemer) -> u5c::Redeemer { u5c::Redeemer { - purpose: map_purpose(&x.tag).into(), - datum: map_plutus_datum(&x.data).into(), + purpose: map_purpose(&x.tag()).into(), + datum: map_plutus_datum(x.data()).into(), } } pub fn map_tx_input(i: &trv::MultiEraInput, tx: &trv::MultiEraTx) -> u5c::TxInput { - let redeemer = tx - .redeemers() - .iter() - .find(|r| (r.index as u64) == i.index()); + let redeemers = tx.redeemers(); + + let redeemer = redeemers.iter().find(|r| (r.index() as u64) == i.index()); u5c::TxInput { tx_hash: i.hash().to_vec().into(), @@ -57,19 +58,20 @@ pub fn map_tx_output(x: &trv::MultiEraOutput) -> u5c::TxOutput { _ => vec![].into(), }, script: match x.script_ref() { - Some(babbage::PseudoScript::NativeScript(x)) => u5c::Script { + Some(conway::PseudoScript::NativeScript(x)) => u5c::Script { script: u5c::script::Script::Native(map_native_script(&x)).into(), } .into(), - Some(babbage::PseudoScript::PlutusV1Script(x)) => u5c::Script { + Some(conway::PseudoScript::PlutusV1Script(x)) => u5c::Script { script: u5c::script::Script::PlutusV1(x.0.to_vec().into()).into(), } .into(), - Some(babbage::PseudoScript::PlutusV2Script(x)) => u5c::Script { + Some(conway::PseudoScript::PlutusV2Script(x)) => u5c::Script { script: u5c::script::Script::PlutusV2(x.0.to_vec().into()).into(), } .into(), - _ => None, + Some(conway::PseudoScript::PlutusV3Script(_)) => todo!(), + None => None, }, } } @@ -92,16 +94,17 @@ pub fn map_stake_credential(x: &babbage::StakeCredential) -> u5c::StakeCredentia pub fn map_relay(x: &alonzo::Relay) -> u5c::Relay { match x { babbage::Relay::SingleHostAddr(port, v4, v6) => u5c::Relay { - ip_v4: v4.as_ref().map(|x| x.to_vec().into()).unwrap_or_default(), - ip_v6: v6.as_ref().map(|x| x.to_vec().into()).unwrap_or_default(), + // ip_v4: v4.map(|x| x.to_vec().into()).into().unwrap_or_default(), + ip_v4: Option::from(v4.clone().map(|x| x.to_vec().into())).unwrap_or_default(), + ip_v6: Option::from(v6.clone().map(|x| x.to_vec().into())).unwrap_or_default(), dns_name: String::default(), - port: port.unwrap_or_default(), + port: Option::from(port.clone()).unwrap_or_default(), }, babbage::Relay::SingleHostName(port, name) => u5c::Relay { ip_v4: Default::default(), ip_v6: Default::default(), dns_name: name.clone(), - port: port.unwrap_or_default(), + port: Option::from(port.clone()).unwrap_or_default(), }, babbage::Relay::MultiHostName(name) => u5c::Relay { ip_v4: Default::default(), @@ -149,10 +152,13 @@ pub fn map_cert(x: &trv::MultiEraCert) -> u5c::Certificate { reward_account: reward_account.to_vec().into(), pool_owners: pool_owners.iter().map(|x| x.to_vec().into()).collect(), relays: relays.iter().map(map_relay).collect(), - pool_metadata: pool_metadata.as_ref().map(|x| u5c::PoolMetadata { - url: x.url.clone(), - hash: x.hash.to_vec().into(), - }), + pool_metadata: pool_metadata + .clone() + .map(|x| u5c::PoolMetadata { + url: x.url.clone(), + hash: x.hash.to_vec().into(), + }) + .into(), }), babbage::Certificate::PoolRetirement(a, b) => { u5c::certificate::Certificate::PoolRetirement(u5c::PoolRetirementCert { diff --git a/test_data/conway1.artificial.block b/test_data/conway1.artificial.block deleted file mode 100644 index cf74156..0000000 --- a/test_data/conway1.artificial.block +++ /dev/null @@ -1 +0,0 @@ -820785828a1a007f5c9e1a04fdb753582082391b0942883229e6bfbe7fcab87305943194ea9834d7e991001b4cf54690525820c00ee16bffe23f4487310d8ee0a0c20f62399f1ef760873c59d07fe75c8a3b1e5820c2bf930daa87329b0b8fd123c7dec6627bc2f58d66245da75bf433a9d2111fa382584056ff802388282b637e60aa290da907b0c72449969d0bf05d2dd8b00d2e807d357191b55db3e63e0e06ce4f8957d556534be407937f7c40e8c427cba47c8a66ea5850ba02c8fa6db09056cd0d4b8ef83b8efc21f087e7e7d11eab4f434aaccd71b71be177ba941c1801d7cd28b701bfd58e65c9322c82ddae536923d998e01916212f4a126c112efda66aeba616b47effcb0d1a00013cd5582062edbca04a6395f812d5f8428d115c27327966204cd2ba8b2a8debdf613ef9a38458203d0db834b561cd08d5a8beb5d1264fd487b97c8b32377b8a16f13af5e34071030a19024f5840204bfdea9a68287c00b202169746554e567ae56bf753c7f4c9404447d2ad272e66761332d1d3d7e2fbab73d7227b729d8d5c3f5dfb1a643c8fa227ab031e82058209005901c0256b0f58e4019758a1e03372977a75268060c730f907d474b25066e066c5098abb913649b31c46463c48f1e03e0fe797a93b0d73aedc01f79ec7d6bb15c1280c65d90cbae45c9d3413b989cc4b9655aa8d6b97f18ce09e6e4abd2d1a0f9ca688a852e68b6ea5ba2beb435562212440be7d6138306d93b094b8f0952c551fa3ac8bdcb9662fa5a3b2157e62f5babc60a31eeed53be528950a4218b08841a163e65995e268338e28ac000dacaa3e5f74ae4e99e383b972d74fa195d712776805a49acb416ef139bdfff173710583e817693fccaf1fdd9a9456cac9aaa1d12313f8ad3abeb43fc3aec8a3ffa7aafd45875636883fcc4c40a9eacb15686bceeca06dac19d00e04421b40fc272a55722c756ad30a489225d6a3c46c5c9c0d49fa645bff979ee5d0717c320821fb5520c070bc5867ce41b205ea4ea1d8ce019952bc0a3b5701806419c6e57b66a736880b7240e37a05d50303cacc81ff1cc56bb277e8d4a795218eb65d401a406adda9022f096afecdefc7afc8af0b78b9aedca0cc3917729f606bb59eb2086b8b8ff9ecdf814b88c2f9025753ce140fc7ce46a95a5fc9a98470667411048ef045999b2c369d02c52812a589da53efd1337ac538566981a80082825820010101010101010101010101010101010101010101010101010101010101010102825820020202020202020202020202020202020202020202020202020202020202020201018182583900c05e80bdcf267e7fe7bf4a867afe54a65a3605b32aae830ed07f8e1ccc339a35f9e0fe039cf510c761d4dd29040c48e9657fdac7e9c01d941864021a034fb5e30491820f8200581c01efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164830e8200581c02efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648200581c03efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16484108200581c04efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164190190f683118200581c05efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16419019083128200581c06efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164f68a03581c0defb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16458200fefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304186418c8d81e82186e18dc581de00aefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16482581c0befb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164581c0cefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16480826f68747470733a2f2f696f686b2e696f582005efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164010203048304581c10efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1641864840a8200581c12efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164581c13efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648200581c11efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16483028200581c14efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164581c15efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16482018200581c16efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16483088200581c16efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16418c982008200581c17efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16483078200581c17efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16418c9840b8200581c17efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164581c18efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16418c9850d8200581c1aefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164581c1befb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648200581c19efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16418c983098200581c1defb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648200581c1cefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164840c8200581c1fefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648200581c1eefb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16418c913a38200581c01efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164a28258200101010101010101010101010101010101010101010101010101010101010101018200f68258200202020202020202020202020202020202020202020202020202020202020202028201f68202581c02efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164a18258200202020202020202020202020202020202020202020202020202020202020202028202f68204581c03efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164a18258200303030303030303030303030303030303030303030303030303030303030303038200f61487841903e8581de001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164830182582001010101010101010101010101010101010101010101010101010101010101010081820102826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de002efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648504f6818200581c01efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee164a18200581c01efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401d81e820102826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de003efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648305f682826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304f6826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de004efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648203f6826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de004efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648300f6b8200019ad9c0119ad9c0219ad9c0319ad9c0419ad9c0519ad9c0619ad9c0719ad9c0819ad9c09d81e8219ad9c19ad9c0ad81e8219ad9c19ad9c0bd81e8219ad9c19ad9c0cd81e8219ad9c19ad9c0d81000e8201021019ad9c1119ad9c12a10098a61a000302590001011a00060bc719026d00011a000249f01903e800011a000249f018201a0025cea81971f70419744d186419744d186419744d186419744d186419744d186419744d18641864186419744d18641a000249f018201a000249f018201a000249f018201a000249f01903e800011a000249f018201a000249f01903e800081a000242201a00067e2318760001011a000249f01903e800081a000249f01a0001b79818f7011a000249f0192710011a0002155e19052e011903e81a000249f01903e8011a000249f018201a000249f018201a000249f0182001011a000249f0011a000249f0041a000194af18f8011a000194af18f8011a0002377c190556011a0002bdea1901f1011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000242201a00067e23187600010119f04c192bd200011a000249f018201a000242201a00067e2318760001011a000242201a00067e2318760001011a0025cea81971f704001a000141bb041a000249f019138800011a000249f018201a000302590001011a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a000249f018201a00330da701011382d81e82190241192710d81e821902d11a0098968014821a000cdcf41a0eab311115821a000cdcf41a0eab31111619ad9c1719ad9c181819ad9c181984d81e8219ad7119ad72d81e8219ad7319ad74d81e8219ad7519ad76d81e8219ad7619ad77181a8ad81e8219ad7119ad72d81e8219ad7319ad74d81e8219ad7519ad76d81e8219ad7619ad77d81e8219ad7819ad79d81e8219ad7a19ad7bd81e8219ad7c19ad7cd81e8219ad7e19ad7fd81e8219ad8019ad81d81e8219ad8219ad83181b19ad9c181c19ad9c181d19ad9c181e19ad9c181f19ad9c182019ad9c826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de005efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648202a1581de101efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304841903e8581de005efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee1648106826f68747470733a2f2f696f686b2e696f582001efb5788e8713c844dfd32b2e91de1e309fefffd555f827cc9ee16401020304151903e816186480A080 \ No newline at end of file diff --git a/test_data/conway2.block b/test_data/conway2.block new file mode 100644 index 0000000..974f8d1 --- /dev/null +++ b/test_data/conway2.block @@ -0,0 +1 @@ +820785828a1a00120f0b1a016dc6a35820758bc1310101e0f7936f86fd98c107fd8afed20a6834995ed4c24ed142c1318258204aa6b5dbdcd388f380141bd2cb3fa5b241340fac92c27b5469fe73eff565f4675820b578942b49a57735cb4d96de30faafa5fab3097389743cde5447258e902be5bb82584099f90365a06bad67307a8dc282d260a948f2166891c00fa4b897afd62d94d243a5b4d3f6f03d36434fb9f4b2cb2927ed30b6bd0bd27f835b22cb705aa2e5c03958504110edda16d5886109322503f8b564400c1cfd86abe6b1becfdcd750a6dd2f3176f1dffadb6b6e28d1b1d5850ec5780e1236306fd1db7f0a15167908765de967257dccd729d2fb95add164cb8e2103041901505820e65a86a416e6134b634b980c47481192d1e76a8dfb9271b8238b97b4ccb5503e84582055bbc058b524a50351600ea242e7c2410136bf020c57e82a9ae7f4aa8fc4bd530018a158400907bf340838d6a7e8005eb14f6562e7156eac0d15f1b28318ec275a6bae24cc8b5b228d0828dc0fc0b9388eb270a8bb52802ce75592b84aa488f9c28da0c70c8209005901c0b42732ad68ad1d026fa64a6b495b7d627ead721a8a006d5e66474b8dbd9fa51964e3f2edeb653dff4e66b415e54ee5f30b0fa8a6780ce79374070ff429ed280731881ffb28e0f8f753f332e0a5bded5b2ee500635da2abdda5b9572448ffcaf737137891efa1403e79103559d066ccf7f7744ca3b3f5f08e8e90d6ed2f67b0ef893d7e78ad04d414f5adae25f9d684384cc3999991343e19fa1e601a1040398510436fd137de03b7b47bb1fc68f51c20e7aa2b3c5ae33562090a7e2f90a55468c9f5d7bc9e0465af512dbaef10bdc539d90f25da66d99ddb558584e8dc2b3771a41121be407da1dcfdfc5ea7275b4b1ec28501ee6b375cb392e2264e09336471cfaff50fee8073c9fd43a83c8e16567b0c96094752dfe597a13e4f457f60d057236a9d4ba127e9f1208781a7934f8a749af51335a7f2bdc6db0142ff4ab1c5cc3a5e0457aad99c69d7e12c952739b8157788792e5421081c473be36574e580335ee445a64ca663dc5f8e2ae37972d6fd49150349d7f38884934a4083025d383dfe9e5e4ee3fded389eea87cf13238233ea1888ad1915ff81e9acf21f506f8f325dccd8cfea20fb7e44412c9d49f3649f49f62096566f9703acdf5dde7d7fbd4681a400d9010281825820d1c5bf73e34357190fe9c55ea9da64c26306663c4e42f5c4aa82095f963e96b800018182583900db1bc3c3f99ce68977ceaf27ab4dd917123ef9e73f85c304236eab2397a993b0c8166aa8c48345be19257a4025ab764c86e799beab15b3031b00000001661f931e021a0002985d14d9010281841a3b9aca00581de0db1bc3c3f99ce68977ceaf27ab4dd917123ef9e73f85c304236eab238301f6820901827668747470733a2f2f6269742e6c792f337a434832484c5820111111111111111111111111111111111111111111111111111111111111111181a100d90102818258205f89ea8c6ab580e2e7a32c3586869eb95fae54f42ac982639b6665359601f63e58409d45b4846cd2ab8260d6f684c6c083dad6938bc07428606ce1c90f6805814a517ff2cb6e8cdabf28a3aadada7957f338685180df4f357a53db276c7516d2e508a080 \ No newline at end of file