diff --git a/plutus-ledger-api/CHANGELOG.md b/plutus-ledger-api/CHANGELOG.md index 274ffc27..5b1f5507 100644 --- a/plutus-ledger-api/CHANGELOG.md +++ b/plutus-ledger-api/CHANGELOG.md @@ -5,6 +5,27 @@ This changelog is based on [Keep A Changelog](https://keepachangelog.com/en/1.1.0). +## 4.0.0 + +### Added + +- reexporting all types and functions from submodules (e.g.: + `v2::transaction::TransactionHash` can now be reached from + `v2::TransactionHash` as well) +- implement `From` and `From` for v1 equivalents, + and `From` for v2 TxInInfo (only Plutus encoding differs) +- `iter`, `iter_mut`, `IntoIter` and `FromIterator` implementations for `AssocMap` + +### Changed + +- `serde` serialization format improvements: + - all byte strings are formatted as hex strings + - Addresses are formatted using bech32, if supplemented with network id + - CurrencySymbols are formatted as hex if native tokens or as the `lovelace` + string +- `WithExtraInfo` types are now using Cow under the hood, allowing ownership of + the data + ## 3.1.0 ### Added diff --git a/plutus-ledger-api/Cargo.lock b/plutus-ledger-api/Cargo.lock index a8ced2a6..69ea02ab 100644 --- a/plutus-ledger-api/Cargo.lock +++ b/plutus-ledger-api/Cargo.lock @@ -35,6 +35,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bech32" version = "0.7.3" @@ -109,7 +115,7 @@ dependencies = [ "num-traits", "rand 0.8.5", "rand_os", - "schemars", + "schemars 0.8.22", "serde", "serde-wasm-bindgen", "serde_json", @@ -147,6 +153,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link 0.2.1", ] @@ -190,12 +197,57 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d" +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "deranged" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "diff" version = "0.1.13" @@ -328,6 +380,12 @@ dependencies = [ "yansi", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -357,6 +415,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "iana-time-zone" @@ -382,12 +443,29 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "impl_ops" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.10.0" @@ -396,6 +474,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", + "serde", ] [[package]] @@ -551,6 +630,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -616,13 +701,13 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "plutus-ledger-api" -version = "3.1.0" +version = "4.0.0-alpha" dependencies = [ "anyhow", "cardano-serialization-lib", "chrono", - "data-encoding", "goldie", + "hex", "impl_ops", "is-plutus-data-derive", "lbr-prelude", @@ -633,9 +718,16 @@ dependencies = [ "proptest", "serde", "serde_json", + "serde_with", "thiserror", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -812,6 +904,26 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex-syntax" version = "0.8.5" @@ -861,6 +973,30 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -947,6 +1083,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.9.9" @@ -966,6 +1133,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.104" @@ -1025,13 +1198,44 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "toml" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" dependencies = [ - "indexmap", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", diff --git a/plutus-ledger-api/Cargo.toml b/plutus-ledger-api/Cargo.toml index 95ab6021..10ed126d 100644 --- a/plutus-ledger-api/Cargo.toml +++ b/plutus-ledger-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plutus-ledger-api" -version = "3.1.0" +version = "4.0.0-alpha" edition = "2021" license = "Apache-2.0" description = "Plutus Ledger types and utilities implemented in Rust" @@ -14,7 +14,6 @@ lbr-prelude = { version = "0.1.3", optional = true } serde_json = { version = "1.0.145", optional = true } num-bigint = "~0.4" serde = { version = "^1.0.228", features = ["derive"], optional = true } -data-encoding = "^2.9.0" thiserror = "^1.0.69" linked-hash-map = "~0.5.6" num-traits = "~0.2.19" @@ -24,10 +23,18 @@ cardano-serialization-lib = "15.0.1" is-plutus-data-derive = { version = "1.0.0", path = ".extras/is-plutus-data-derive-0" } anyhow = "1.0.100" nom = "7.1.3" +serde_with = { version = "3.15.0", optional = true } +hex = "0.4.3" [features] default = [] -serde = ["dep:serde", "num-bigint/serde", "dep:serde_json"] +serde = [ + "dep:serde", + "num-bigint/serde", + "dep:serde_json", + "dep:serde_with", + "hex/serde", +] lbf = ["dep:lbr-prelude", "dep:serde_json"] chrono = ["dep:chrono"] diff --git a/plutus-ledger-api/src/error.rs b/plutus-ledger-api/src/error.rs index ec2cb737..f598be1d 100644 --- a/plutus-ledger-api/src/error.rs +++ b/plutus-ledger-api/src/error.rs @@ -1,4 +1,3 @@ -use data_encoding::HEXLOWER; use thiserror::Error; #[derive(Debug, Error)] @@ -15,7 +14,7 @@ pub enum ConversionError { #[error("String cannot be parsed as a hexadecimal value: {value_hex}")] HexDecodeError { value_hex: String, - source: data_encoding::DecodeError, + source: hex::FromHexError, }, #[error(transparent)] @@ -34,11 +33,11 @@ impl ConversionError { expected, got: bytes.len(), relation: relation.to_string(), - value_hex: HEXLOWER.encode(bytes), + value_hex: hex::encode(bytes), } } - pub fn hex_decode_error(err: data_encoding::DecodeError, value_hex: &str) -> Self { + pub fn hex_decode_error(err: hex::FromHexError, value_hex: &str) -> Self { ConversionError::HexDecodeError { source: err, value_hex: value_hex.to_string(), diff --git a/plutus-ledger-api/src/plutus_data.rs b/plutus-ledger-api/src/plutus_data.rs index 0be189b7..179a85ea 100644 --- a/plutus-ledger-api/src/plutus_data.rs +++ b/plutus-ledger-api/src/plutus_data.rs @@ -10,27 +10,27 @@ use crate::csl::pla_to_csl::{TryFromPLA, TryFromPLAError, TryToCSL}; pub use is_plutus_data_derive::IsPlutusData; -#[cfg(feature = "lbf")] -use data_encoding::HEXLOWER; #[cfg(feature = "lbf")] use lbr_prelude::error::Error; #[cfg(feature = "lbf")] use lbr_prelude::json::{ case_json_constructor, case_json_object, json_constructor, json_object, Json, }; +#[cfg(feature = "serde")] +use serde_with::serde_as; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Data representation of on-chain data such as Datums and Redeemers #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde_as)] pub enum PlutusData { Constr(BigInt, Vec), Map(Vec<(PlutusData, PlutusData)>), List(Vec), Integer(BigInt), - Bytes(Vec), + Bytes(#[cfg_attr(feature = "serde", serde_as = "hex::serde")] Vec), } #[derive(Clone, Debug)] @@ -141,7 +141,7 @@ impl Json for PlutusData { PlutusData::List(list) => json_constructor("List", vec![list.to_json()]), PlutusData::Integer(int) => json_constructor("Integer", vec![int.to_json()]), PlutusData::Bytes(bytes) => { - json_constructor("Bytes", vec![String::to_json(&HEXLOWER.encode(bytes))]) + json_constructor("Bytes", vec![String::to_json(&hex::encode(bytes))]) } } } @@ -219,7 +219,7 @@ impl Json for PlutusData { Box::new(|ctor_fields| match &ctor_fields[..] { [val] => { let bytes = String::from_json(val).and_then(|str| { - HEXLOWER.decode(&str.into_bytes()).map_err(|_| { + hex::decode(&str.into_bytes()).map_err(|_| { Error::UnexpectedJsonInvariant { wanted: "base16 string".to_owned(), got: "unexpected string".to_owned(), diff --git a/plutus-ledger-api/src/v1/address.rs b/plutus-ledger-api/src/v1/address.rs index 0dc81378..a1f4b519 100644 --- a/plutus-ledger-api/src/v1/address.rs +++ b/plutus-ledger-api/src/v1/address.rs @@ -1,14 +1,16 @@ //! Types related to Cardano addresses +use std::borrow::Cow; use std::str::FromStr; use anyhow::anyhow; use cardano_serialization_lib as csl; - #[cfg(feature = "lbf")] use lbr_prelude::json::{self, Error, Json}; use num_bigint::BigInt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate as plutus_ledger_api; use crate::csl::csl_to_pla::{FromCSL, TryFromCSL, TryFromCSLError, TryToPLA}; @@ -38,9 +40,16 @@ pub struct Address { } impl Address { - pub fn with_extra_info(&self, network_tag: u8) -> AddressWithExtraInfo { + pub fn with_extra_info<'a>(&'a self, network_tag: u8) -> AddressWithExtraInfo<'a> { AddressWithExtraInfo { - address: self, + address: Cow::Borrowed(self), + network_tag, + } + } + + pub fn into_address_with_extra_info<'a>(self, network_tag: u8) -> AddressWithExtraInfo<'a> { + AddressWithExtraInfo { + address: Cow::Owned(self), network_tag, } } @@ -85,10 +94,11 @@ impl TryFromCSL for Address { } #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] /// Address with network information. The `WithExtraInfo` variant has Display instance, serializing into /// a bech32 address format. pub struct AddressWithExtraInfo<'a> { - pub address: &'a Address, + pub address: Cow<'a, Address>, pub network_tag: u8, } @@ -111,6 +121,22 @@ impl TryFromPLA> for csl::Address { } } +impl<'a> TryFromCSL for AddressWithExtraInfo<'a> { + fn try_from_csl(value: &csl::Address) -> Result + where + Self: Sized, + { + Ok(AddressWithExtraInfo { + address: Cow::Owned(value.try_to_pla()?), + network_tag: value.network_id().map_err(|err| { + TryFromCSLError::ImpossibleConversion(format!( + "Couldn't extract network tag from address: {err}" + )) + })?, + }) + } +} + /// Serializing into a bech32 address format. impl std::fmt::Display for AddressWithExtraInfo<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -125,6 +151,18 @@ impl std::fmt::Display for AddressWithExtraInfo<'_> { } } +impl<'a> FromStr for AddressWithExtraInfo<'a> { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let csl_addr = csl::Address::from_bech32(s) + .map_err(|err| anyhow!("Couldn't parse bech32 address: {}", err))?; + csl_addr + .try_to_pla() + .map_err(|err| anyhow!("Couldn't convert address: {}", err)) + } +} + //////////////// // Credential // //////////////// @@ -332,7 +370,7 @@ impl FromCSL for StakingCredential { #[derive(Clone, Debug)] pub struct RewardAddressWithExtraInfo<'a> { - pub staking_credential: &'a StakingCredential, + pub staking_credential: Cow<'a, StakingCredential>, pub network_tag: u8, } @@ -340,7 +378,7 @@ impl TryFromPLA> for csl::RewardAddress { fn try_from_pla(val: &RewardAddressWithExtraInfo<'_>) -> Result { Ok(csl::RewardAddress::new( val.network_tag, - &val.staking_credential.try_to_csl()?, + &val.staking_credential.as_ref().try_to_csl()?, )) } } diff --git a/plutus-ledger-api/src/v1/assoc_map.rs b/plutus-ledger-api/src/v1/assoc_map.rs index f03ecd57..05b5b953 100644 --- a/plutus-ledger-api/src/v1/assoc_map.rs +++ b/plutus-ledger-api/src/v1/assoc_map.rs @@ -65,6 +65,19 @@ impl AssocMap { } } } + /// Returns an iterator over the slice. + /// + /// The iterator yields all items from start to end. + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, (K, V)> { + self.0.iter() + } + + /// Returns an iterator that allows modifying each value. + /// + /// The iterator yields all items from start to end. + pub fn iter_mut<'a>(&'a mut self) -> std::slice::IterMut<'a, (K, V)> { + self.0.iter_mut() + } } impl IsPlutusData for AssocMap { @@ -122,6 +135,22 @@ impl From> for AssocMap { } } +impl IntoIterator for AssocMap { + type Item = (K, V); + + type IntoIter = std::vec::IntoIter<(K, V)>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator<(K, V)> for AssocMap { + fn from_iter>(iter: T) -> Self { + AssocMap(Vec::from_iter(iter)) + } +} + #[cfg(feature = "lbf")] impl Json for AssocMap { fn to_json(&self) -> serde_json::Value { diff --git a/plutus-ledger-api/src/v1/crypto.rs b/plutus-ledger-api/src/v1/crypto.rs index 25ed6bd5..72823dac 100644 --- a/plutus-ledger-api/src/v1/crypto.rs +++ b/plutus-ledger-api/src/v1/crypto.rs @@ -1,13 +1,21 @@ //! Types for cryptographic primitives, and other lower level building blocks + +use std::str::FromStr; + +use anyhow::anyhow; use cardano_serialization_lib as csl; -use data_encoding::HEXLOWER; #[cfg(feature = "lbf")] use lbr_prelude::json::{Error, Json}; +use nom::combinator::all_consuming; +use nom::Finish; use nom::{combinator::map_res, error::VerboseError, IResult}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate as plutus_ledger_api; +use crate::error::ConversionError; use crate::{ csl::{ csl_to_pla::FromCSL, @@ -79,47 +87,59 @@ pub struct StakePubKeyHash(pub Ed25519PubKeyHash); /// A bytestring in the Cardano ledger context #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)] #[is_plutus_data_derive_strategy = "Newtype"] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] pub struct LedgerBytes(pub Vec); impl std::fmt::Debug for LedgerBytes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", HEXLOWER.encode(&self.0)) + write!(f, "{}", hex::encode(&self.0)) } } impl std::fmt::Display for LedgerBytes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", HEXLOWER.encode(&self.0)) + write!(f, "{}", hex::encode(&self.0)) } } +impl FromStr for LedgerBytes { + type Err = ConversionError; + + fn from_str(s: &str) -> Result { + all_consuming(ledger_bytes)(s) + .finish() + .map_err(|err| { + ConversionError::ParseError(anyhow!( + "Error while parsing CurrencySymbol '{}': {}", + s, + err + )) + }) + .map(|(_, cs)| cs) + } +} /// Nom parser for LedgerBytes /// Expects a hexadecimal string of arbitrary length (0 length is allowed) /// E.g.: 00112233445566778899aabbcc pub(crate) fn ledger_bytes(input: &str) -> IResult<&str, LedgerBytes, VerboseError<&str>> { map_res(nom::character::complete::hex_digit0, |hex_bytes: &str| { - HEXLOWER - .decode(&hex_bytes.to_owned().to_ascii_lowercase().into_bytes()) - .map(LedgerBytes) + hex::decode(&hex_bytes.to_owned().to_ascii_lowercase().into_bytes()).map(LedgerBytes) })(input) } #[cfg(feature = "lbf")] impl Json for LedgerBytes { fn to_json(&self) -> serde_json::Value { - String::to_json(&HEXLOWER.encode(&self.0)) + String::to_json(&hex::encode(&self.0)) } fn from_json(value: &serde_json::Value) -> Result { let bytes = String::from_json(value).and_then(|str| { - HEXLOWER - .decode(&str.into_bytes()) - .map_err(|_| Error::UnexpectedJsonInvariant { - wanted: "base16 string".to_owned(), - got: "unexpected string".to_owned(), - parser: "Plutus.V1.Bytes".to_owned(), - }) + hex::decode(&str.into_bytes()).map_err(|_| Error::UnexpectedJsonInvariant { + wanted: "base16 string".to_owned(), + got: "unexpected string".to_owned(), + parser: "Plutus.V1.Bytes".to_owned(), + }) })?; Ok(Self(bytes)) diff --git a/plutus-ledger-api/src/v1/mod.rs b/plutus-ledger-api/src/v1/mod.rs index 1e070d28..fd20bdc1 100644 --- a/plutus-ledger-api/src/v1/mod.rs +++ b/plutus-ledger-api/src/v1/mod.rs @@ -8,3 +8,13 @@ pub mod redeemer; pub mod script; pub mod transaction; pub mod value; + +pub use address::*; +pub use assoc_map::*; +pub use crypto::*; +pub use datum::*; +pub use interval::*; +pub use redeemer::*; +pub use script::*; +pub use transaction::*; +pub use value::*; diff --git a/plutus-ledger-api/src/v1/redeemer.rs b/plutus-ledger-api/src/v1/redeemer.rs index beeb6161..c21625be 100644 --- a/plutus-ledger-api/src/v1/redeemer.rs +++ b/plutus-ledger-api/src/v1/redeemer.rs @@ -1,5 +1,7 @@ //! Types related to Plutus Redeemers +use std::borrow::Cow; + use cardano_serialization_lib as csl; #[cfg(feature = "lbf")] @@ -24,18 +26,32 @@ use crate::v1::crypto::LedgerBytes; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Redeemer(pub PlutusData); +impl Redeemer { + pub fn with_extra_info<'a>( + &'a self, + tag: csl::RedeemerTag, + index: u64, + ) -> RedeemerWithExtraInfo<'a> { + RedeemerWithExtraInfo { + redeemer: Cow::Borrowed(self), + tag, + index, + } + } +} + #[derive(Clone, Debug)] pub struct RedeemerWithExtraInfo<'a> { - pub redeemer: &'a Redeemer, - pub tag: &'a csl::RedeemerTag, + pub redeemer: Cow<'a, Redeemer>, + pub tag: csl::RedeemerTag, pub index: u64, } impl TryFromPLA> for csl::Redeemer { fn try_from_pla<'a>(val: &RedeemerWithExtraInfo<'_>) -> Result { - let Redeemer(plutus_data) = val.redeemer; + let Redeemer(plutus_data) = val.redeemer.as_ref(); Ok(csl::Redeemer::new( - val.tag, + &val.tag, &val.index.try_to_csl()?, &plutus_data.try_to_csl()?, &csl::ExUnits::new(&csl::BigNum::from(0u64), &csl::BigNum::from(0u64)), diff --git a/plutus-ledger-api/src/v1/transaction.rs b/plutus-ledger-api/src/v1/transaction.rs index 54f59f0f..3361b985 100644 --- a/plutus-ledger-api/src/v1/transaction.rs +++ b/plutus-ledger-api/src/v1/transaction.rs @@ -15,6 +15,8 @@ use nom::{ use num_bigint::BigInt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{DeserializeFromStr, SerializeDisplay}; use super::{ address::{Address, StakingCredential}, @@ -47,7 +49,7 @@ use crate::{ /// inside the transaction #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)] #[is_plutus_data_derive_strategy = "Constr"] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] #[cfg_attr(feature = "lbf", derive(Json))] pub struct TransactionInput { pub transaction_id: TransactionHash, @@ -139,7 +141,7 @@ impl FromStr for TransactionInput { /// Note: Plutus docs might incorrectly state that it uses SHA256. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)] #[is_plutus_data_derive_strategy = "Constr"] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] #[cfg_attr(feature = "lbf", derive(Json))] pub struct TransactionHash(pub LedgerBytes); diff --git a/plutus-ledger-api/src/v1/value.rs b/plutus-ledger-api/src/v1/value.rs index f6204781..ee3ade2f 100644 --- a/plutus-ledger-api/src/v1/value.rs +++ b/plutus-ledger-api/src/v1/value.rs @@ -30,6 +30,8 @@ use num_traits::Zero; use serde::{Deserialize, Serialize}; #[cfg(feature = "lbf")] use serde_json; +#[cfg(feature = "serde")] +use serde_with::{DeserializeFromStr, SerializeDisplay}; use crate as plutus_ledger_api; use crate::aux::{big_int, singleton, union_b_tree_maps_with, union_btree_maps_with}; @@ -49,7 +51,7 @@ use super::crypto::ledger_bytes; /// Identifier of a currency, which could be either Ada (or tAda), or a native token represented by /// it's minting policy hash. A currency may be associated with multiple `AssetClass`es. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] pub enum CurrencySymbol { Ada, NativeToken(MintingPolicyHash), @@ -719,7 +721,7 @@ impl FromCSL for Value { /// Name of a token. This can be any arbitrary bytearray #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] #[cfg_attr(feature = "lbf", derive(Json))] pub struct TokenName(pub LedgerBytes); diff --git a/plutus-ledger-api/src/v2/mod.rs b/plutus-ledger-api/src/v2/mod.rs index 11c6f297..37db0679 100644 --- a/plutus-ledger-api/src/v2/mod.rs +++ b/plutus-ledger-api/src/v2/mod.rs @@ -4,11 +4,14 @@ pub mod datum; pub mod transaction; +pub use datum::*; +pub use transaction::*; + // Inherited from v1 -pub use crate::v1::address; -pub use crate::v1::assoc_map; -pub use crate::v1::crypto; -pub use crate::v1::interval; -pub use crate::v1::redeemer; -pub use crate::v1::script; -pub use crate::v1::value; +pub use crate::v1::address::{self, *}; +pub use crate::v1::assoc_map::{self, *}; +pub use crate::v1::crypto::{self, *}; +pub use crate::v1::interval::{self, *}; +pub use crate::v1::redeemer::{self, *}; +pub use crate::v1::script::{self, *}; +pub use crate::v1::value::{self, *}; diff --git a/plutus-ledger-api/src/v2/transaction.rs b/plutus-ledger-api/src/v2/transaction.rs index 45c1c5e8..13ed0771 100644 --- a/plutus-ledger-api/src/v2/transaction.rs +++ b/plutus-ledger-api/src/v2/transaction.rs @@ -1,5 +1,6 @@ //! Types related to Cardano transactions. +use std::borrow::Cow; use std::collections::BTreeMap; use cardano_serialization_lib as csl; @@ -49,6 +50,22 @@ pub struct TransactionOutput { pub reference_script: Option, } +impl TransactionOutput { + pub fn with_extra_info<'a>( + &'a self, + scripts: &'a BTreeMap, + network_id: u8, + data_cost: &'a csl::DataCost, + ) -> TransactionOutputWithExtraInfo<'a> { + TransactionOutputWithExtraInfo { + transaction_output: Cow::Borrowed(self), + scripts: Cow::Borrowed(scripts), + network_id, + data_cost: Cow::Borrowed(data_cost), + } + } +} + impl TryFromCSL for TransactionOutput { fn try_from_csl(value: &csl::TransactionOutput) -> Result { Ok(TransactionOutput { @@ -86,17 +103,17 @@ impl TryFromCSL for Vec { #[derive(Clone, Debug)] pub struct TransactionOutputWithExtraInfo<'a> { - pub transaction_output: &'a TransactionOutput, - pub scripts: &'a BTreeMap, + pub transaction_output: Cow<'a, TransactionOutput>, + pub scripts: Cow<'a, BTreeMap>, pub network_id: u8, - pub data_cost: &'a csl::DataCost, + pub data_cost: Cow<'a, csl::DataCost>, } impl TryFromPLA> for csl::TransactionOutput { fn try_from_pla(val: &TransactionOutputWithExtraInfo<'_>) -> Result { let mut output_builder = csl::TransactionOutputBuilder::new().with_address( &AddressWithExtraInfo { - address: &val.transaction_output.address, + address: Cow::Borrowed(&val.transaction_output.address), network_tag: val.network_id, } .try_to_csl()?, @@ -127,7 +144,7 @@ impl TryFromPLA> for csl::TransactionOutput { let value_without_min_utxo = val.transaction_output.value.try_to_csl()?; - let mut calc = csl::MinOutputAdaCalculator::new_empty(val.data_cost) + let mut calc = csl::MinOutputAdaCalculator::new_empty(val.data_cost.as_ref()) .map_err(TryFromPLAError::CSLJsError)?; calc.set_amount(&value_without_min_utxo); match &val.transaction_output.datum { @@ -180,6 +197,7 @@ impl From<(TransactionInput, TransactionOutput)> for TxInInfo { } } +///////////////////// // TransactionInfo // ///////////////////// @@ -205,7 +223,7 @@ pub struct TransactionInfo { #[derive(Clone, Debug)] pub struct WithdrawalsWithExtraInfo<'a> { - pub withdrawals: &'a AssocMap, + pub withdrawals: Cow<'a, AssocMap>, pub network_tag: u8, } @@ -217,7 +235,7 @@ impl TryFromPLA> for csl::Withdrawals { .try_fold(csl::Withdrawals::new(), |mut acc, (s, q)| { acc.insert( &RewardAddressWithExtraInfo { - staking_credential: s, + staking_credential: Cow::Borrowed(s), network_tag: val.network_tag, } .try_to_csl()?, diff --git a/plutus-ledger-api/src/v3/mod.rs b/plutus-ledger-api/src/v3/mod.rs index 6350950a..4c9cc501 100644 --- a/plutus-ledger-api/src/v3/mod.rs +++ b/plutus-ledger-api/src/v3/mod.rs @@ -4,12 +4,15 @@ pub mod ratio; pub mod transaction; +pub use ratio::*; +pub use transaction::*; + // Inherited from v2 -pub use crate::v2::address; -pub use crate::v2::assoc_map; -pub use crate::v2::crypto; -pub use crate::v2::datum; -pub use crate::v2::interval; -pub use crate::v2::redeemer; -pub use crate::v2::script; -pub use crate::v2::value; +pub use crate::v2::address::{self, *}; +pub use crate::v2::assoc_map::{self, *}; +pub use crate::v2::crypto::{self, *}; +pub use crate::v2::datum::{self, *}; +pub use crate::v2::interval::{self, *}; +pub use crate::v2::redeemer::{self, *}; +pub use crate::v2::script::{self, *}; +pub use crate::v2::value::{self, *}; diff --git a/plutus-ledger-api/src/v3/transaction.rs b/plutus-ledger-api/src/v3/transaction.rs index 608a95ac..9ec71486 100644 --- a/plutus-ledger-api/src/v3/transaction.rs +++ b/plutus-ledger-api/src/v3/transaction.rs @@ -16,6 +16,8 @@ use nom::{ use num_bigint::BigInt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_with::{DeserializeFromStr, SerializeDisplay}; #[cfg(feature = "chrono")] pub use crate::v1::transaction::POSIXTimeConversionError; @@ -32,7 +34,9 @@ use crate::{ }, error::ConversionError, plutus_data::{IsPlutusData, PlutusData}, + v1, v2::{ + self, address::Credential, assoc_map::AssocMap, crypto::{PaymentPubKeyHash, StakePubKeyHash}, @@ -59,7 +63,7 @@ use super::{ /// V3 TransactionHash uses a more efficient Plutus Data encoding #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)] #[is_plutus_data_derive_strategy = "Newtype"] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] #[cfg_attr(feature = "lbf", derive(Json))] pub struct TransactionHash(pub LedgerBytes); @@ -92,6 +96,18 @@ impl TryFromPLA for csl::TransactionHash { } } +impl From for TransactionHash { + fn from(value: v1::transaction::TransactionHash) -> Self { + Self(value.0) + } +} + +impl From for v1::transaction::TransactionHash { + fn from(value: TransactionHash) -> Self { + Self(value.0) + } +} + /// Nom parser for TransactionHash /// Expects a hexadecimal string representation of 32 bytes /// E.g.: 1122334455667788990011223344556677889900112233445566778899001122 @@ -131,7 +147,7 @@ impl FromStr for TransactionHash { /// inside the transaction #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IsPlutusData)] #[is_plutus_data_derive_strategy = "Constr"] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))] #[cfg_attr(feature = "lbf", derive(Json))] pub struct TransactionInput { pub transaction_id: TransactionHash, @@ -181,6 +197,24 @@ impl TryFromPLA> for csl::TransactionInputs { } } +impl From for v1::transaction::TransactionInput { + fn from(value: TransactionInput) -> Self { + Self { + transaction_id: value.transaction_id.into(), + index: value.index, + } + } +} + +impl From for TransactionInput { + fn from(value: v1::transaction::TransactionInput) -> Self { + Self { + transaction_id: value.transaction_id.into(), + index: value.index, + } + } +} + /// Nom parser for TransactionInput /// Expects a transaction hash of 32 bytes in hexadecimal followed by a # and an integer index /// E.g.: 1122334455667788990011223344556677889900112233445566778899001122#1 @@ -547,6 +581,24 @@ impl From<(TransactionInput, TransactionOutput)> for TxInInfo { } } +impl From for TxInInfo { + fn from(value: v2::transaction::TxInInfo) -> Self { + Self { + reference: value.reference.into(), + output: value.output, + } + } +} + +impl From for v2::transaction::TxInInfo { + fn from(value: TxInInfo) -> Self { + Self { + reference: value.reference.into(), + output: value.output, + } + } +} + /////////////////// // ScriptContext // /////////////////// diff --git a/plutus-ledger-api/tests/display.rs b/plutus-ledger-api/tests/display.rs index 96da0d60..42c7011b 100644 --- a/plutus-ledger-api/tests/display.rs +++ b/plutus-ledger-api/tests/display.rs @@ -95,7 +95,8 @@ mod display_serialisation_tests { use plutus_ledger_api::{ generators::correct::v1::{ - arb_address, arb_asset_class, arb_currency_symbol, arb_transaction_input, arb_value, + arb_address, arb_asset_class, arb_currency_symbol, arb_ledger_bytes, + arb_transaction_input, arb_value, }, v1::{address::Address, value::TokenName}, }; @@ -110,6 +111,11 @@ mod display_serialisation_tests { proptest! { + #[test] + fn ledger_bytes(val in arb_ledger_bytes(10)) { + assert_eq!(val, from_to_string(&val)?); + } + #[test] fn currency_symbol(val in arb_currency_symbol()) { assert_eq!(val, from_to_string(&val)?);