Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ serde = { version = "1.0.189", features = ["derive"], optional = true }
true = { version = "0.1.0", optional = true }
data-encoding = { version = "2.4.0", optional = true }
thiserror = "1.0.50"
linked-hash-map = "0.5.6"

[features]
serde = ["dep:serde", "num-bigint/serde"]
Expand Down
85 changes: 82 additions & 3 deletions src/generators/correct/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use crate::plutus_data::PlutusData;
use crate::v1::address::{
Address, CertificateIndex, ChainPointer, Credential, Slot, StakingCredential, TransactionIndex,
};
use crate::v1::crypto::{Ed25519PubKeyHash, LedgerBytes};
use crate::v1::assoc_map::AssocMap;
use crate::v1::crypto::{Ed25519PubKeyHash, LedgerBytes, PaymentPubKeyHash};
use crate::v1::datum::{Datum, DatumHash};
use crate::v1::interval::{Extended, LowerBound, PlutusInterval, UpperBound};
use crate::v1::redeemer::{Redeemer, RedeemerHash};
use crate::v1::script::{MintingPolicyHash, ScriptHash, ValidatorHash};
use crate::v1::transaction::{
POSIXTime, TransactionHash, TransactionInput, TransactionOutput, TxInInfo,
DCert, POSIXTime, ScriptContext, ScriptPurpose, TransactionHash, TransactionInfo,
TransactionInput, TransactionOutput, TxInInfo,
};
use crate::v2::value::{AssetClass, CurrencySymbol, TokenName, Value};
use num_bigint::BigInt;
Expand Down Expand Up @@ -243,7 +245,7 @@ pub fn arb_transaction_index() -> impl Strategy<Value = TransactionIndex> {
arb_integer().prop_map(TransactionIndex)
}

/// Strategy to generate a certificate index
/// Strategy to generate a certificate index.
pub fn arb_certificate_index() -> impl Strategy<Value = CertificateIndex> {
arb_integer().prop_map(CertificateIndex)
}
Expand Down Expand Up @@ -293,3 +295,80 @@ pub fn arb_tx_in_info() -> impl Strategy<Value = TxInInfo> {
(arb_transaction_input(), arb_transaction_output())
.prop_map(|(reference, output)| TxInInfo { reference, output })
}

/// Strategy to generate an AssocMap, given the strategies to generate keys and values
pub fn arb_assoc_map<K: std::fmt::Debug, V: std::fmt::Debug>(
arb_k: impl Strategy<Value = K>,
arb_v: impl Strategy<Value = V>,
) -> impl Strategy<Value = AssocMap<K, V>> {
vec((arb_k, arb_v), 10).prop_map(AssocMap)
}

/// Strategy to generate a PaymentPubKeyHash
pub fn arb_payment_pub_key_hash() -> impl Strategy<Value = PaymentPubKeyHash> {
arb_ed25519_pub_key_hash().prop_map(PaymentPubKeyHash)
}

/// Strategy to generate a DCert
pub fn arb_d_cert() -> impl Strategy<Value = DCert> {
prop_oneof![
arb_staking_credential().prop_map(DCert::DelegKey),
arb_staking_credential().prop_map(DCert::DelegDeregKey),
(arb_staking_credential(), arb_payment_pub_key_hash())
.prop_map(|(sc, pkh)| DCert::DelegDelegate(sc, pkh)),
(arb_payment_pub_key_hash(), arb_payment_pub_key_hash())
.prop_map(|(p1, p2)| DCert::PoolRegister(p1, p2)),
(arb_payment_pub_key_hash(), arb_integer()).prop_map(|(pkh, i)| DCert::PoolRetire(pkh, i)),
Just(DCert::Genesis),
Just(DCert::Mir)
]
}

/// Strategy to generate a ScriptPurpose
pub fn arb_script_purpose() -> impl Strategy<Value = ScriptPurpose> {
prop_oneof![
arb_currency_symbol().prop_map(ScriptPurpose::Minting),
arb_transaction_input().prop_map(ScriptPurpose::Spending),
arb_staking_credential().prop_map(ScriptPurpose::Rewarding),
arb_d_cert().prop_map(ScriptPurpose::Certifying)
]
}

/// Strategy to generate a TransactionInfo. Note that its inputs, outputs, d_cert,
/// signatories and datums field will each have a length of 0 to 5
pub fn arb_transaction_info() -> impl Strategy<Value = TransactionInfo> {
(
vec(arb_tx_in_info(), 5),
vec(arb_transaction_output(), 5),
arb_value(),
arb_value(),
vec(arb_d_cert(), 5),
vec((arb_staking_credential(), arb_integer()), 5),
arb_plutus_interval_posix_time(),
vec(arb_payment_pub_key_hash(), 5),
vec((arb_datum_hash(), arb_datum()), 5),
arb_transaction_hash(),
)
.prop_map(
|(inputs, outputs, fee, mint, d_cert, wdrl, valid_range, signatories, datums, id)| {
TransactionInfo {
inputs,
outputs,
fee,
mint,
d_cert,
wdrl,
valid_range,
signatories,
datums,
id,
}
},
)
}

/// Strategy to generate a ScriptContext
pub fn arb_script_context() -> impl Strategy<Value = ScriptContext> {
(arb_script_purpose(), arb_transaction_info())
.prop_map(|(purpose, tx_info)| ScriptContext { purpose, tx_info })
}
62 changes: 61 additions & 1 deletion src/generators/correct/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ use crate::generators::correct::v1::{
arb_address, arb_datum, arb_datum_hash, arb_script_hash, arb_transaction_input, arb_value,
};
use crate::v2::datum::OutputDatum;
use crate::v2::transaction::{TransactionOutput, TxInInfo};
use crate::v2::transaction::{ScriptContext, TransactionInfo, TransactionOutput, TxInInfo};
use proptest::collection::vec;
use proptest::option;
use proptest::prelude::{prop_oneof, Just};
use proptest::strategy::Strategy;

use super::primitive::arb_integer;
use super::v1::{
arb_assoc_map, arb_d_cert, arb_payment_pub_key_hash, arb_plutus_interval_posix_time,
arb_redeemer, arb_script_purpose, arb_staking_credential, arb_transaction_hash,
};

/// Strategy to generate transaction output
pub fn arb_transaction_output() -> impl Strategy<Value = TransactionOutput> {
(
Expand Down Expand Up @@ -42,3 +49,56 @@ pub fn arb_tx_in_info() -> impl Strategy<Value = TxInInfo> {
(arb_transaction_input(), arb_transaction_output())
.prop_map(|(reference, output)| TxInInfo { reference, output })
}

pub fn arb_transaction_info() -> impl Strategy<Value = TransactionInfo> {
(
vec(arb_tx_in_info(), 5),
vec(arb_tx_in_info(), 5),
vec(arb_transaction_output(), 5),
arb_value(),
arb_value(),
vec(arb_d_cert(), 5),
arb_assoc_map(arb_staking_credential(), arb_integer()),
arb_plutus_interval_posix_time(),
vec(arb_payment_pub_key_hash(), 5),
arb_assoc_map(arb_script_purpose(), arb_redeemer()),
arb_assoc_map(arb_datum_hash(), arb_datum()),
arb_transaction_hash(),
)
.prop_map(
|(
inputs,
reference_inputs,
outputs,
fee,
mint,
d_cert,
wdrl,
valid_range,
signatories,
redeemers,
datums,
id,
)| {
TransactionInfo {
inputs,
reference_inputs,
outputs,
fee,
mint,
d_cert,
wdrl,
valid_range,
signatories,
redeemers,
datums,
id,
}
},
)
}

pub fn arb_script_context() -> impl Strategy<Value = ScriptContext> {
(arb_script_purpose(), arb_transaction_info())
.prop_map(|(purpose, tx_info)| ScriptContext { purpose, tx_info })
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod v1;
pub mod v2;
#[cfg(feature = "lbf")]
pub use lbr_prelude::json;
pub(crate) mod utils;
48 changes: 48 additions & 0 deletions src/plutus_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,51 @@ pub fn verify_constr_fields(
Ok(())
}
}

/// Given a vector of PlutusData, parse it as an array whose length is known at
/// compile time.
pub fn parse_fixed_len_constr_fields<'a, const LEN: usize>(
v: &'a [PlutusData],
) -> Result<&'a [PlutusData; LEN], PlutusDataError> {
v.try_into()
.map_err(|_| PlutusDataError::UnexpectedListLength {
got: v.len(),
wanted: LEN,
})
}

/// Given a PlutusData, parse it as PlutusData::Constr and its tag as u32. Return
/// the u32 tag and fields.
pub fn parse_constr<'a>(
data: &'a PlutusData,
) -> Result<(u32, &'a Vec<PlutusData>), PlutusDataError> {
match data {
PlutusData::Constr(tag, fields) => u32::try_from(tag)
.map_err(|err| PlutusDataError::UnexpectedPlutusInvariant {
got: err.to_string(),
wanted: "Constr bigint tag within u32 range".into(),
})
.map(|tag| (tag, fields)),
_ => Err(PlutusDataError::UnexpectedPlutusType {
wanted: PlutusType::Constr,
got: PlutusType::from(data),
}),
}
}

/// Given a PlutusData, parse it as PlutusData::Constr and verify its tag.
pub fn parse_constr_with_tag<'a>(
data: &'a PlutusData,
expected_tag: u32,
) -> Result<&'a Vec<PlutusData>, PlutusDataError> {
let (tag, fields) = parse_constr(data)?;

if tag != expected_tag {
Err(PlutusDataError::UnexpectedPlutusInvariant {
got: tag.to_string(),
wanted: format!("Constr tag to be: {}", expected_tag),
})
} else {
Ok(fields)
}
}
17 changes: 17 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::iter::{empty, once};

/// Create a container C from one element.
pub fn singleton<T, C>(value: T) -> C
where
C: FromIterator<T>,
{
once(value).collect()
}

/// Create an empty container.
pub fn none<T, C>() -> C
where
C: FromIterator<T>,
{
empty::<T>().collect()
}
98 changes: 98 additions & 0 deletions src/v1/assoc_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::hash::Hash;

#[cfg(feature = "lbf")]
use lbr_prelude::json::{json_array, Json};
use linked_hash_map::LinkedHashMap;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::plutus_data::{IsPlutusData, PlutusData, PlutusDataError, PlutusType};

#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AssocMap<K, V>(pub Vec<(K, V)>);

impl<K: IsPlutusData, V: IsPlutusData> IsPlutusData for AssocMap<K, V> {
fn to_plutus_data(&self) -> PlutusData {
PlutusData::Map(
(&self.0)
.into_iter()
.map(|(k, v)| (k.to_plutus_data(), v.to_plutus_data()))
.collect(),
)
}

fn from_plutus_data(plutus_data: &PlutusData) -> Result<Self, PlutusDataError> {
match plutus_data {
PlutusData::Map(pairs) => pairs
.into_iter()
.map(|(k, v)| Ok((K::from_plutus_data(k)?, V::from_plutus_data(v)?)))
.collect::<Result<Vec<(K, V)>, PlutusDataError>>()
.map(Self),
_ => Err(PlutusDataError::UnexpectedPlutusType {
got: From::from(plutus_data),
wanted: PlutusType::Map,
}),
}
}
}

impl<K, V> From<Vec<(K, V)>> for AssocMap<K, V> {
fn from(vec: Vec<(K, V)>) -> Self {
AssocMap(vec)
}
}

impl<K, V> From<AssocMap<K, V>> for Vec<(K, V)> {
fn from(m: AssocMap<K, V>) -> Self {
m.0
}
}

impl<K: Hash + Eq, V> From<AssocMap<K, V>> for LinkedHashMap<K, V> {
fn from(m: AssocMap<K, V>) -> Self {
m.0.into_iter().collect()
}
}

impl<K: Hash + Eq, V> From<LinkedHashMap<K, V>> for AssocMap<K, V> {
fn from(value: LinkedHashMap<K, V>) -> Self {
AssocMap(value.into_iter().collect())
}
}

#[cfg(feature = "lbf")]
impl<K: Json, V: Json> Json for AssocMap<K, V> {
fn to_json(&self) -> serde_json::Value {
json_array(
(&self.0)
.into_iter()
.map(|(k, v)| json_array(vec![k.to_json(), v.to_json()]))
.collect(),
)
}

fn from_json(value: &serde_json::Value) -> Result<Self, lbr_prelude::json::Error> {
let vec_of_vectors: Vec<Vec<serde_json::Value>> = Json::from_json(value)?;
let vec_of_pairs = vec_of_vectors
.into_iter()
.map(|vec| {
let [k, v]: [serde_json::Value; 2] =
TryFrom::try_from(vec).map_err(|vec: Vec<_>| {
lbr_prelude::json::Error::UnexpectedArrayLength {
got: vec.len(),
wanted: 2,
parser: "v1::assoc_map::AssocMap".into(),
}
})?;

let k = K::from_json(&k)?;
let v = V::from_json(&v)?;

Ok((k, v))
})
.collect::<Result<Vec<(K, V)>, _>>()?;

Ok(Self(vec_of_pairs))
}
}
1 change: 1 addition & 0 deletions src/v1/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Plutus types and utilities for Plutus V1
pub mod address;
pub mod assoc_map;
pub mod crypto;
pub mod datum;
pub mod interval;
Expand Down
Loading