Merge branch 'main' into lang
This commit is contained in:
commit
c08f6a8454
|
@ -24,10 +24,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aiken"
|
||||
version = "0.0.11"
|
||||
version = "0.0.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"hex",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
"pallas-primitives",
|
||||
"pallas-traverse",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uplc",
|
||||
]
|
||||
|
||||
|
@ -43,9 +51,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
|
||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
@ -126,9 +134,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.20"
|
||||
version = "3.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
|
||||
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -311,15 +319,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
version = "0.2.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
|
@ -357,34 +365,14 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e575910763b21a0db7df5e142907fe944bff84d1dfc78e2ba92e7f3bdfd36b"
|
||||
dependencies = [
|
||||
"half",
|
||||
"minicbor-derive 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a20020e8e2d1881d8736f64011bb5ff99f1db9947ce3089706945c8915695cb"
|
||||
dependencies = [
|
||||
"minicbor-derive 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor-derive"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a86c5f04def8fb7735ae918bb589af82f985526f4c62e0249544b668b2f456"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"half",
|
||||
"minicbor-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -409,9 +397,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
|
@ -420,32 +408,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
|
||||
[[package]]
|
||||
name = "pallas-codec"
|
||||
version = "0.12.0"
|
||||
name = "pallas-addresses"
|
||||
version = "0.14.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dce0ea17341c1a0e43e2bb4a637740198dcb09826879ce3ac5ae1c6f4398a5d"
|
||||
checksum = "1188a2434037b74129f8d209a37a1d09721b900e6e378255db1a91abc37199bc"
|
||||
dependencies = [
|
||||
"minicbor 0.17.1",
|
||||
"base58",
|
||||
"bech32",
|
||||
"hex",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallas-codec"
|
||||
version = "0.14.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c035a772aa84e858e53b7c98e6036eaa216d8a699bb9c826787722bde13d05"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"minicbor",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallas-crypto"
|
||||
version = "0.12.0"
|
||||
version = "0.14.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "051226367cd851895c73e3115d378b58495ade1ee60c3a154c0d0c30554565fa"
|
||||
checksum = "2841f9225dcd6a78c6f386d4d5e76bcdbecd7b4489455b2d2485b105bf4c0499"
|
||||
dependencies = [
|
||||
"cryptoxide",
|
||||
"hex",
|
||||
"pallas-codec",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallas-primitives"
|
||||
version = "0.12.0"
|
||||
version = "0.14.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a0fcc7d5a7120bc2b2e203ec5e7f8088107c500c0eb665569d0e77a910d3c0"
|
||||
checksum = "3e51824547f7a1e1a6574ecec4bf8557f3819f435132873c0bae97acba81cbb1"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"bech32",
|
||||
|
@ -457,6 +462,20 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallas-traverse"
|
||||
version = "0.14.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3291d1ae31cd803b9142fb32e1fcb0a58fc1d65b3bbe1d527d14b322db6eb019"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
"pallas-primitives",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
|
@ -557,9 +576,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -628,9 +647,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
@ -694,9 +713,23 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.144"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
|
@ -723,9 +756,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -757,24 +790,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.34"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
|
||||
checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.34"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
|
||||
checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -798,35 +831,40 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "uplc"
|
||||
version = "0.0.11"
|
||||
version = "0.0.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cryptoxide",
|
||||
"flat-rs",
|
||||
"hex",
|
||||
"minicbor 0.18.0",
|
||||
"pallas-addresses",
|
||||
"pallas-codec",
|
||||
"pallas-crypto",
|
||||
"pallas-primitives",
|
||||
"pallas-traverse",
|
||||
"peg",
|
||||
"pretty",
|
||||
"proptest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
(program
|
||||
1.0.0
|
||||
[(force (force (builtin fstPair))) (con (pair integer bytestring) (22, #1122aabb))]
|
||||
)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "aiken"
|
||||
description = "Cardano smart contract language and toolchain"
|
||||
version = "0.0.11"
|
||||
version = "0.0.13"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/txpipe/aiken"
|
||||
homepage = "https://github.com/txpipe/aiken"
|
||||
|
@ -13,4 +13,12 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
|||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
clap = { version = "3.1.14", features = ["derive"] }
|
||||
uplc = { path = '../uplc', version = "0.0.11" }
|
||||
hex = "0.4.3"
|
||||
pallas-addresses = "0.14.0-alpha.3"
|
||||
pallas-codec = "0.14.0-alpha.3"
|
||||
pallas-crypto = "0.14.0-alpha.3"
|
||||
pallas-primitives = "0.14.0-alpha.3"
|
||||
pallas-traverse = "0.14.0-alpha.3"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.85"
|
||||
uplc = { path = '../uplc', version = "0.0.13" }
|
||||
|
|
|
@ -16,22 +16,58 @@ pub enum Args {
|
|||
/// Project name
|
||||
name: PathBuf,
|
||||
},
|
||||
/// A subcommand for working with transactions
|
||||
#[clap(subcommand)]
|
||||
Tx(TxCommand),
|
||||
/// A subcommand for working with Untyped Plutus Core
|
||||
#[clap(subcommand)]
|
||||
Uplc(UplcCommand),
|
||||
}
|
||||
|
||||
/// Commands for working with transactions
|
||||
#[derive(Subcommand)]
|
||||
pub enum TxCommand {
|
||||
/// Simulate a transaction by evaluating it's script
|
||||
Simulate {
|
||||
/// A file containing cbor hex for a transaction
|
||||
input: PathBuf,
|
||||
|
||||
/// Toggle whether input is raw cbor or a hex string
|
||||
#[clap(short, long)]
|
||||
cbor: bool,
|
||||
|
||||
/// A file containing cbor hex for the raw inputs
|
||||
raw_inputs: PathBuf,
|
||||
|
||||
/// A file containing cbor hex for the raw outputs
|
||||
raw_outputs: PathBuf,
|
||||
|
||||
/// Time between each slot
|
||||
#[clap(short, long, default_value_t = 1000)]
|
||||
slot_length: u64,
|
||||
|
||||
/// Time of shelley hardfork
|
||||
#[clap(long, default_value_t = 1596059091000)]
|
||||
zero_time: u64,
|
||||
|
||||
/// Slot number at the start of the shelley hardfork
|
||||
#[clap(long, default_value_t = 4492800)]
|
||||
zero_slot: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Commands for working with Untyped Plutus Core
|
||||
#[derive(Subcommand)]
|
||||
pub enum UplcCommand {
|
||||
/// Evaluate an Untyped Plutus Core program
|
||||
Eval {
|
||||
/// Handle input as flat bytes
|
||||
script: PathBuf,
|
||||
|
||||
#[clap(short, long)]
|
||||
flat: bool,
|
||||
|
||||
/// File to load and evaluate
|
||||
input: PathBuf,
|
||||
/// Arguments to pass to the uplc program
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Encode textual Untyped Plutus Core to flat bytes
|
||||
Flat {
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
use std::{fmt::Write as _, fs};
|
||||
|
||||
use pallas_primitives::{
|
||||
babbage::{TransactionInput, TransactionOutput},
|
||||
Fragment,
|
||||
};
|
||||
use pallas_traverse::{Era, MultiEraTx};
|
||||
use uplc::{
|
||||
ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term},
|
||||
machine::cost_model::ExBudget,
|
||||
parser,
|
||||
tx::{
|
||||
self,
|
||||
script_context::{ResolvedInput, SlotConfig},
|
||||
},
|
||||
};
|
||||
|
||||
mod args;
|
||||
|
||||
use args::{Args, UplcCommand};
|
||||
use args::{Args, TxCommand, UplcCommand};
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::default();
|
||||
|
@ -39,38 +48,142 @@ fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
Args::Uplc(uplc) => match uplc {
|
||||
UplcCommand::Flat { input, print, out } => {
|
||||
Args::Tx(tx_cmd) => match tx_cmd {
|
||||
TxCommand::Simulate {
|
||||
input,
|
||||
cbor,
|
||||
raw_inputs,
|
||||
raw_outputs,
|
||||
slot_length,
|
||||
zero_time,
|
||||
zero_slot,
|
||||
} => {
|
||||
let (tx_bytes, inputs_bytes, outputs_bytes) = if cbor {
|
||||
(
|
||||
fs::read(input)?,
|
||||
fs::read(raw_inputs)?,
|
||||
fs::read(raw_outputs)?,
|
||||
)
|
||||
} else {
|
||||
let cbor_hex = fs::read_to_string(input)?;
|
||||
let inputs_hex = fs::read_to_string(raw_inputs)?;
|
||||
let outputs_hex = fs::read_to_string(raw_outputs)?;
|
||||
|
||||
(
|
||||
hex::decode(cbor_hex.trim())?,
|
||||
hex::decode(inputs_hex.trim())?,
|
||||
hex::decode(outputs_hex.trim())?,
|
||||
)
|
||||
};
|
||||
|
||||
let tx = MultiEraTx::decode(Era::Babbage, &tx_bytes)
|
||||
.or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes))?;
|
||||
|
||||
let inputs = Vec::<TransactionInput>::decode_fragment(&inputs_bytes).unwrap();
|
||||
let outputs = Vec::<TransactionOutput>::decode_fragment(&outputs_bytes).unwrap();
|
||||
|
||||
let resolved_inputs: Vec<ResolvedInput> = inputs
|
||||
.iter()
|
||||
.zip(outputs.iter())
|
||||
.map(|(input, output)| ResolvedInput {
|
||||
input: input.clone(),
|
||||
output: output.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!("Simulating: {}", tx.hash());
|
||||
|
||||
if let Some(tx_babbage) = tx.as_babbage() {
|
||||
let slot_config = SlotConfig {
|
||||
zero_time,
|
||||
zero_slot,
|
||||
slot_length,
|
||||
};
|
||||
|
||||
let result = tx::eval_phase_two(
|
||||
tx_babbage,
|
||||
&resolved_inputs,
|
||||
None,
|
||||
None,
|
||||
&slot_config,
|
||||
true,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(redeemers) => {
|
||||
println!("\nTotal Budget Used\n-----------------\n");
|
||||
|
||||
let total_budget_used = redeemers.iter().fold(
|
||||
ExBudget { mem: 0, cpu: 0 },
|
||||
|accum, curr| ExBudget {
|
||||
mem: accum.mem + curr.ex_units.mem as i64,
|
||||
cpu: accum.cpu + curr.ex_units.steps as i64,
|
||||
},
|
||||
);
|
||||
|
||||
println!("mem: {}", total_budget_used.mem);
|
||||
println!("cpu: {}", total_budget_used.cpu);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("\nError\n-----\n\n{}\n", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Args::Uplc(uplc_cmd) => match uplc_cmd {
|
||||
UplcCommand::Flat {
|
||||
input,
|
||||
print,
|
||||
out,
|
||||
cbor_hex,
|
||||
} => {
|
||||
let code = std::fs::read_to_string(&input)?;
|
||||
|
||||
let program = parser::program(&code)?;
|
||||
|
||||
let program = Program::<DeBruijn>::try_from(program)?;
|
||||
|
||||
let bytes = program.to_flat()?;
|
||||
if cbor_hex {
|
||||
let bytes = program.to_flat()?;
|
||||
|
||||
if print {
|
||||
let mut output = String::new();
|
||||
if print {
|
||||
let mut output = String::new();
|
||||
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
let _ = write!(output, "{:08b}", byte);
|
||||
for (i, byte) in bytes.iter().enumerate() {
|
||||
let _ = write!(output, "{:08b}", byte);
|
||||
|
||||
if (i + 1) % 4 == 0 {
|
||||
output.push('\n');
|
||||
} else {
|
||||
output.push(' ');
|
||||
if (i + 1) % 4 == 0 {
|
||||
output.push('\n');
|
||||
} else {
|
||||
output.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", output);
|
||||
} else {
|
||||
let out_name = if let Some(out) = out {
|
||||
out
|
||||
println!("{}", output);
|
||||
} else {
|
||||
format!("{}.flat", input.file_stem().unwrap().to_str().unwrap())
|
||||
};
|
||||
let out_name = if let Some(out) = out {
|
||||
out
|
||||
} else {
|
||||
format!("{}.flat", input.file_stem().unwrap().to_str().unwrap())
|
||||
};
|
||||
|
||||
fs::write(&out_name, &bytes)?;
|
||||
fs::write(&out_name, &bytes)?;
|
||||
}
|
||||
} else {
|
||||
let cbor = program.to_hex()?;
|
||||
|
||||
if print {
|
||||
println!("{}", &cbor);
|
||||
} else {
|
||||
let out_name = if let Some(out) = out {
|
||||
out
|
||||
} else {
|
||||
format!("{}.cbor", input.file_stem().unwrap().to_str().unwrap())
|
||||
};
|
||||
|
||||
fs::write(&out_name, &cbor)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,11 +200,24 @@ fn main() -> anyhow::Result<()> {
|
|||
fs::write(&input, pretty)?;
|
||||
}
|
||||
}
|
||||
UplcCommand::Unflat {
|
||||
input,
|
||||
print,
|
||||
out,
|
||||
cbor_hex,
|
||||
} => {
|
||||
let program = if cbor_hex {
|
||||
let cbor = std::fs::read_to_string(&input)?;
|
||||
|
||||
UplcCommand::Unflat { input, print, out } => {
|
||||
let bytes = std::fs::read(&input)?;
|
||||
let mut cbor_buffer = Vec::new();
|
||||
let mut flat_buffer = Vec::new();
|
||||
|
||||
let program = Program::<DeBruijn>::from_flat(&bytes)?;
|
||||
Program::<DeBruijn>::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer)?
|
||||
} else {
|
||||
let bytes = std::fs::read(&input)?;
|
||||
|
||||
Program::<DeBruijn>::from_flat(&bytes)?
|
||||
};
|
||||
|
||||
let program: Program<Name> = program.try_into()?;
|
||||
|
||||
|
@ -110,21 +236,27 @@ fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
UplcCommand::Eval { input, flat } => {
|
||||
let program = if flat {
|
||||
let bytes = std::fs::read(&input)?;
|
||||
UplcCommand::Eval { script, flat, args } => {
|
||||
let mut program = if flat {
|
||||
let bytes = std::fs::read(&script)?;
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_flat(&bytes)?;
|
||||
|
||||
prog.into()
|
||||
} else {
|
||||
let code = std::fs::read_to_string(&input)?;
|
||||
let code = std::fs::read_to_string(&script)?;
|
||||
|
||||
let prog = parser::program(&code)?;
|
||||
|
||||
Program::<NamedDeBruijn>::try_from(prog)?
|
||||
};
|
||||
|
||||
for arg in args {
|
||||
let term: Term<NamedDeBruijn> = parser::term(&arg)?.try_into()?;
|
||||
|
||||
program = program.apply_term(&term);
|
||||
}
|
||||
|
||||
let (term, cost, logs) = program.eval();
|
||||
|
||||
match term {
|
||||
|
@ -149,7 +281,10 @@ fn main() -> anyhow::Result<()> {
|
|||
"\nBudget\n------\ncpu: {}\nmemory: {}\n",
|
||||
cost.cpu, cost.mem
|
||||
);
|
||||
println!("\nLogs\n----\n{}", logs.join("\n"))
|
||||
|
||||
if !logs.is_empty() {
|
||||
println!("\nLogs\n----\n{}", logs.join("\n"))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "uplc"
|
||||
description = "Utilities for working with Untyped Plutus Core"
|
||||
version = "0.0.11"
|
||||
version = "0.0.13"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/txpipe/aiken/crates/uplc"
|
||||
homepage = "https://github.com/txpipe/aiken"
|
||||
|
@ -16,12 +16,17 @@ exclude = ["test_data/*"]
|
|||
cryptoxide = "0.4.2"
|
||||
flat-rs = { path = "../flat", version = "0.0.10" }
|
||||
hex = "0.4.3"
|
||||
minicbor = { version = "0.18.0", features = ["std"] }
|
||||
pallas-codec = "0.12.0"
|
||||
pallas-primitives = "0.12.0"
|
||||
pallas-addresses = "0.14.0-alpha.3"
|
||||
pallas-codec = "0.14.0-alpha.3"
|
||||
pallas-crypto = "0.14.0-alpha.3"
|
||||
pallas-primitives = "0.14.0-alpha.3"
|
||||
pallas-traverse = "0.14.0-alpha.3"
|
||||
peg = "0.8.0"
|
||||
pretty = "0.11.3"
|
||||
thiserror = "1.0.31"
|
||||
anyhow = "1.0.57"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.85"
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.3"
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use std::{fmt::Display, rc::Rc};
|
||||
|
||||
use pallas_primitives::alonzo::PlutusData;
|
||||
use pallas_primitives::{alonzo::PlutusData, babbage::Language};
|
||||
|
||||
use crate::{
|
||||
builtins::DefaultFunction,
|
||||
debruijn::{self, Converter},
|
||||
flat::Binder,
|
||||
machine::{
|
||||
cost_model::{CostModel, ExBudget},
|
||||
cost_model::{initialize_cost_model, CostModel, ExBudget},
|
||||
Machine,
|
||||
},
|
||||
};
|
||||
|
@ -39,6 +39,33 @@ where
|
|||
term: applied_term,
|
||||
}
|
||||
}
|
||||
|
||||
/// We use this to apply the validator to Datum,
|
||||
/// then redeemer, then ScriptContext. If datum is
|
||||
/// even necessary (i.e. minting policy).
|
||||
pub fn apply_term(&self, term: &Term<T>) -> Self {
|
||||
let applied_term = Term::Apply {
|
||||
function: Rc::new(self.term.clone()),
|
||||
argument: Rc::new(term.clone()),
|
||||
};
|
||||
|
||||
Program {
|
||||
version: self.version,
|
||||
term: applied_term,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_data(&self, plutus_data: PlutusData) -> Self {
|
||||
let applied_term = Term::Apply {
|
||||
function: Rc::new(self.term.clone()),
|
||||
argument: Rc::new(Term::Constant(Constant::Data(plutus_data))),
|
||||
};
|
||||
|
||||
Program {
|
||||
version: self.version,
|
||||
term: applied_term,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Display for Program<T>
|
||||
|
@ -461,7 +488,54 @@ impl Program<NamedDeBruijn> {
|
|||
ExBudget,
|
||||
Vec<String>,
|
||||
) {
|
||||
let mut machine = Machine::new(CostModel::default(), ExBudget::default(), 200);
|
||||
let mut machine = Machine::new(
|
||||
Language::PlutusV2,
|
||||
CostModel::default(),
|
||||
ExBudget::default(),
|
||||
200,
|
||||
);
|
||||
|
||||
let term = machine.run(&self.term);
|
||||
|
||||
(term, machine.ex_budget, machine.logs)
|
||||
}
|
||||
|
||||
/// Evaluate a Program as PlutusV1
|
||||
pub fn eval_v1(
|
||||
&self,
|
||||
) -> (
|
||||
Result<Term<NamedDeBruijn>, crate::machine::Error>,
|
||||
ExBudget,
|
||||
Vec<String>,
|
||||
) {
|
||||
let mut machine = Machine::new(Language::PlutusV1, CostModel::v1(), ExBudget::v1(), 200);
|
||||
|
||||
let term = machine.run(&self.term);
|
||||
|
||||
(term, machine.ex_budget, machine.logs)
|
||||
}
|
||||
|
||||
pub fn eval_as(
|
||||
&self,
|
||||
version: &Language,
|
||||
costs: &[i64],
|
||||
initial_budget: Option<&ExBudget>,
|
||||
) -> (
|
||||
Result<Term<NamedDeBruijn>, crate::machine::Error>,
|
||||
ExBudget,
|
||||
Vec<String>,
|
||||
) {
|
||||
let budget = match initial_budget {
|
||||
Some(b) => *b,
|
||||
None => ExBudget::default(),
|
||||
};
|
||||
|
||||
let mut machine = Machine::new(
|
||||
version.clone(),
|
||||
initialize_cost_model(version, costs),
|
||||
budget,
|
||||
200, //slippage
|
||||
);
|
||||
|
||||
let term = machine.run(&self.term);
|
||||
|
||||
|
@ -482,3 +556,9 @@ impl Program<DeBruijn> {
|
|||
program.eval()
|
||||
}
|
||||
}
|
||||
|
||||
impl Term<NamedDeBruijn> {
|
||||
pub fn is_valid_script_result(&self) -> bool {
|
||||
!matches!(self, Term::Error)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,11 @@ where
|
|||
T: Binder<'b> + Debug,
|
||||
{
|
||||
pub fn from_cbor(bytes: &'b [u8], buffer: &'b mut Vec<u8>) -> Result<Self, de::Error> {
|
||||
let flat_bytes: Vec<u8> =
|
||||
minicbor::decode(bytes).map_err(|err| de::Error::Message(err.to_string()))?;
|
||||
let mut cbor_decoder = pallas_codec::minicbor::Decoder::new(bytes);
|
||||
|
||||
let flat_bytes = cbor_decoder
|
||||
.bytes()
|
||||
.map_err(|err| de::Error::Message(err.to_string()))?;
|
||||
|
||||
buffer.extend(flat_bytes);
|
||||
|
||||
|
@ -58,7 +61,15 @@ where
|
|||
pub fn to_cbor(&self) -> Result<Vec<u8>, en::Error> {
|
||||
let flat_bytes = self.flat()?;
|
||||
|
||||
minicbor::to_vec(&flat_bytes).map_err(|err| en::Error::Message(err.to_string()))
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
let mut cbor_encoder = pallas_codec::minicbor::Encoder::new(&mut bytes);
|
||||
|
||||
cbor_encoder
|
||||
.bytes(&flat_bytes)
|
||||
.map_err(|err| en::Error::Message(err.to_string()))?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
// convenient so that people don't need to depend on the flat crate
|
||||
|
@ -179,8 +190,10 @@ where
|
|||
6 => Ok(Term::Error),
|
||||
7 => Ok(Term::Builtin(DefaultFunction::decode(d)?)),
|
||||
x => Err(de::Error::Message(format!(
|
||||
"Unknown term constructor tag: {}",
|
||||
x
|
||||
"Unknown term constructor tag: {} and buffer position is {} and buffer length is {}",
|
||||
x,
|
||||
d.buffer.len() - d.pos,
|
||||
d.buffer.len()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ pub mod machine;
|
|||
pub mod parser;
|
||||
mod pretty;
|
||||
pub mod program_builder;
|
||||
pub mod tx;
|
||||
|
||||
pub use pallas_primitives::alonzo::PlutusData;
|
||||
pub type Error = Box<dyn std::error::Error>;
|
||||
use pallas_primitives::Fragment;
|
||||
|
||||
use pallas_primitives::{Error, Fragment};
|
||||
|
||||
pub fn plutus_data(bytes: &[u8]) -> Result<PlutusData, Error> {
|
||||
PlutusData::decode_fragment(bytes)
|
||||
|
|
|
@ -11,7 +11,7 @@ mod runtime;
|
|||
|
||||
use cost_model::{ExBudget, StepKind};
|
||||
pub use error::Error;
|
||||
use pallas_primitives::babbage::{BigInt, PlutusData};
|
||||
use pallas_primitives::babbage::{BigInt, Language, PlutusData};
|
||||
|
||||
use self::{cost_model::CostModel, runtime::BuiltinRuntime};
|
||||
|
||||
|
@ -39,10 +39,16 @@ pub struct Machine {
|
|||
unbudgeted_steps: [u32; 8],
|
||||
pub logs: Vec<String>,
|
||||
stack: Vec<MachineStep>,
|
||||
version: Language,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub fn new(costs: CostModel, initial_budget: ExBudget, slippage: u32) -> Machine {
|
||||
pub fn new(
|
||||
version: Language,
|
||||
costs: CostModel,
|
||||
initial_budget: ExBudget,
|
||||
slippage: u32,
|
||||
) -> Machine {
|
||||
Machine {
|
||||
costs,
|
||||
ex_budget: initial_budget,
|
||||
|
@ -50,6 +56,7 @@ impl Machine {
|
|||
unbudgeted_steps: [0; 8],
|
||||
logs: vec![],
|
||||
stack: vec![],
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,8 +354,10 @@ impl Machine {
|
|||
runtime: BuiltinRuntime,
|
||||
) -> Result<Value, Error> {
|
||||
if runtime.is_ready() {
|
||||
let cost = runtime.to_ex_budget(&self.costs.builtin_costs);
|
||||
|
||||
let cost = match self.version {
|
||||
Language::PlutusV1 => runtime.to_ex_budget_v1(&self.costs.builtin_costs),
|
||||
Language::PlutusV2 => runtime.to_ex_budget_v2(&self.costs.builtin_costs),
|
||||
};
|
||||
self.spend_budget(cost)?;
|
||||
|
||||
runtime.call(&mut self.logs)
|
||||
|
@ -448,7 +457,13 @@ impl Value {
|
|||
((i.abs() as f64).log2().floor() as i64 / 64) + 1
|
||||
}
|
||||
}
|
||||
Constant::ByteString(b) => (((b.len() as i64 - 1) / 8) + 1),
|
||||
Constant::ByteString(b) => {
|
||||
if b.is_empty() {
|
||||
1
|
||||
} else {
|
||||
((b.len() as i64 - 1) / 8) + 1
|
||||
}
|
||||
}
|
||||
Constant::String(s) => s.chars().count() as i64,
|
||||
Constant::Unit => 1,
|
||||
Constant::Bool(_) => 1,
|
||||
|
@ -488,7 +503,7 @@ impl Value {
|
|||
PlutusData::Map(m) => {
|
||||
let mut new_stack: VecDeque<&PlutusData>;
|
||||
// create new stack with of items from the list of pairs of data
|
||||
new_stack = m.deref().iter().fold(VecDeque::new(), |mut acc, d| {
|
||||
new_stack = m.iter().fold(VecDeque::new(), |mut acc, d| {
|
||||
acc.push_back(&d.0);
|
||||
acc.push_back(&d.1);
|
||||
acc
|
||||
|
@ -499,7 +514,7 @@ impl Value {
|
|||
}
|
||||
PlutusData::BigInt(i) => {
|
||||
if let BigInt::Int(g) = i {
|
||||
let numb: i64 = (*g).try_into().unwrap();
|
||||
let numb: i128 = (*g).try_into().unwrap();
|
||||
total += Value::Con(Constant::Integer(numb as isize)).to_ex_mem();
|
||||
} else {
|
||||
unreachable!()
|
||||
|
@ -517,14 +532,6 @@ impl Value {
|
|||
new_stack.append(&mut stack);
|
||||
stack = new_stack;
|
||||
}
|
||||
PlutusData::ArrayIndef(a) => {
|
||||
// create new stack with of items from the list of data
|
||||
let mut new_stack: VecDeque<&PlutusData> =
|
||||
VecDeque::from_iter(a.deref().iter());
|
||||
// Append old stack to the back of the new stack
|
||||
new_stack.append(&mut stack);
|
||||
stack = new_stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
total
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,10 @@
|
|||
use std::string::FromUtf8Error;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::{NamedDeBruijn, Term, Type};
|
||||
|
||||
use super::{ExBudget, Value};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Over budget mem: {} & cpu: {}", .0.mem, .0.cpu)]
|
||||
OutOfExError(ExBudget),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use pallas_codec::utils::{KeyValuePairs, MaybeIndefArray};
|
||||
use pallas_primitives::babbage::{BigInt, Constr, PlutusData};
|
||||
|
||||
use crate::{
|
||||
|
@ -64,8 +63,12 @@ impl BuiltinRuntime {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_ex_budget(&self, costs: &BuiltinCosts) -> ExBudget {
|
||||
costs.to_ex_budget(self.fun, &self.args)
|
||||
pub fn to_ex_budget_v2(&self, costs: &BuiltinCosts) -> ExBudget {
|
||||
costs.to_ex_budget_v2(self.fun, &self.args)
|
||||
}
|
||||
|
||||
pub fn to_ex_budget_v1(&self, costs: &BuiltinCosts) -> ExBudget {
|
||||
costs.to_ex_budget_v1(self.fun, &self.args)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -664,8 +667,7 @@ impl DefaultFunction {
|
|||
DefaultFunction::ChooseData => match &args[0] {
|
||||
Value::Con(Constant::Data(PlutusData::Constr(_))) => Ok(args[1].clone()),
|
||||
Value::Con(Constant::Data(PlutusData::Map(_))) => Ok(args[2].clone()),
|
||||
Value::Con(Constant::Data(PlutusData::Array(_)))
|
||||
| Value::Con(Constant::Data(PlutusData::ArrayIndef(_))) => Ok(args[3].clone()),
|
||||
Value::Con(Constant::Data(PlutusData::Array(_))) => Ok(args[3].clone()),
|
||||
Value::Con(Constant::Data(PlutusData::BigInt(_))) => Ok(args[4].clone()),
|
||||
Value::Con(Constant::Data(PlutusData::BoundedBytes(_))) => Ok(args[5].clone()),
|
||||
_ => unreachable!(),
|
||||
|
@ -687,7 +689,7 @@ impl DefaultFunction {
|
|||
// TODO: handle other types of constructor tags
|
||||
tag: convert_constr_to_tag(*i as u64),
|
||||
any_constructor: None,
|
||||
fields: MaybeIndefArray::Indef(data_list),
|
||||
fields: data_list,
|
||||
});
|
||||
Ok(Value::Con(Constant::Data(constr_data)))
|
||||
}
|
||||
|
@ -695,21 +697,23 @@ impl DefaultFunction {
|
|||
},
|
||||
DefaultFunction::MapData => match &args[0] {
|
||||
Value::Con(Constant::ProtoList(_, list)) => {
|
||||
let data_list: Vec<(PlutusData, PlutusData)> = list
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
let mut map = Vec::new();
|
||||
|
||||
for item in list {
|
||||
match item {
|
||||
Constant::ProtoPair(Type::Data, Type::Data, left, right) => {
|
||||
match (*left.clone(), *right.clone()) {
|
||||
(Constant::Data(key), Constant::Data(value)) => (key, value),
|
||||
(Constant::Data(key), Constant::Data(value)) => {
|
||||
map.push((key, value));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
Ok(Value::Con(Constant::Data(PlutusData::Map(
|
||||
KeyValuePairs::Def(data_list),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Con(Constant::Data(PlutusData::Map(map.into()))))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
@ -722,9 +726,8 @@ impl DefaultFunction {
|
|||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
Ok(Value::Con(Constant::Data(PlutusData::ArrayIndef(
|
||||
MaybeIndefArray::Indef(data_list),
|
||||
))))
|
||||
|
||||
Ok(Value::Con(Constant::Data(PlutusData::Array(data_list))))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
@ -779,8 +782,7 @@ impl DefaultFunction {
|
|||
_ => unreachable!(),
|
||||
},
|
||||
DefaultFunction::UnListData => match &args[0] {
|
||||
Value::Con(Constant::Data(PlutusData::Array(l)))
|
||||
| Value::Con(Constant::Data(PlutusData::ArrayIndef(l))) => {
|
||||
Value::Con(Constant::Data(PlutusData::Array(l))) => {
|
||||
Ok(Value::Con(Constant::ProtoList(
|
||||
Type::Data,
|
||||
l.deref()
|
||||
|
@ -794,7 +796,7 @@ impl DefaultFunction {
|
|||
DefaultFunction::UnIData => match &args[0] {
|
||||
Value::Con(Constant::Data(PlutusData::BigInt(b))) => {
|
||||
if let BigInt::Int(i) = b {
|
||||
let x: i64 = (*i).try_into().unwrap();
|
||||
let x: i128 = (*i).try_into().unwrap();
|
||||
|
||||
Ok(Value::Con(Constant::Integer(x as isize)))
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
};
|
||||
|
||||
use interner::Interner;
|
||||
use pallas_primitives::{alonzo::PlutusData, Fragment};
|
||||
use peg::{error::ParseError, str::LineCol};
|
||||
|
||||
mod interner;
|
||||
|
@ -24,6 +25,19 @@ pub fn program(src: &str) -> Result<Program<Name>, ParseError<LineCol>> {
|
|||
Ok(program)
|
||||
}
|
||||
|
||||
pub fn term(src: &str) -> Result<Term<Name>, ParseError<LineCol>> {
|
||||
// initialize the string interner to get unique name
|
||||
let mut interner = Interner::new();
|
||||
|
||||
// run the generated parser
|
||||
let mut term = uplc::term(src)?;
|
||||
|
||||
// assign proper unique ids in place
|
||||
interner.term(&mut term);
|
||||
|
||||
Ok(term)
|
||||
}
|
||||
|
||||
peg::parser! {
|
||||
grammar uplc() for str {
|
||||
pub rule program() -> Program<Name>
|
||||
|
@ -36,7 +50,7 @@ peg::parser! {
|
|||
(major as usize, minor as usize, patch as usize)
|
||||
}
|
||||
|
||||
rule term() -> Term<Name>
|
||||
pub rule term() -> Term<Name>
|
||||
= constant()
|
||||
/ builtin()
|
||||
/ var()
|
||||
|
@ -53,6 +67,7 @@ peg::parser! {
|
|||
/ constant_string()
|
||||
/ constant_unit()
|
||||
/ constant_bool()
|
||||
/ constant_data()
|
||||
) _* ")" {
|
||||
Term::Constant(con)
|
||||
}
|
||||
|
@ -110,6 +125,15 @@ peg::parser! {
|
|||
rule number() -> isize
|
||||
= n:$("-"* ['0'..='9']+) {? n.parse().or(Err("isize")) }
|
||||
|
||||
rule constant_data() -> Constant
|
||||
= "data" _+ "#" i:ident()* {
|
||||
Constant::Data(
|
||||
PlutusData::decode_fragment(
|
||||
hex::decode(String::from_iter(i)).unwrap().as_slice()
|
||||
).unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
rule name() -> Name
|
||||
= text:ident() { Name { text, unique: 0.into() } }
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
use pallas_primitives::{
|
||||
babbage::{CostMdls, MintedTx, Redeemer, TransactionInput, TransactionOutput},
|
||||
Fragment,
|
||||
};
|
||||
use pallas_traverse::{Era, MultiEraTx};
|
||||
|
||||
use error::Error;
|
||||
pub use eval::get_script_and_datum_lookup_table;
|
||||
pub use phase_one::eval_phase_one;
|
||||
use script_context::{ResolvedInput, SlotConfig};
|
||||
|
||||
use crate::machine::cost_model::ExBudget;
|
||||
|
||||
pub mod error;
|
||||
mod eval;
|
||||
mod phase_one;
|
||||
pub mod script_context;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod to_plutus_data;
|
||||
|
||||
/// Evaluate the scripts in a transaction using
|
||||
/// the UPLC Cek Machine. This function collects
|
||||
/// redeemers with ExUnits calculated from the evaluation.
|
||||
/// You may optionally run a subset of phase one checks on
|
||||
/// redeemers and scripts.
|
||||
pub fn eval_phase_two(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
cost_mdls: Option<&CostMdls>,
|
||||
initial_budget: Option<&ExBudget>,
|
||||
slot_config: &SlotConfig,
|
||||
run_phase_one: bool,
|
||||
) -> Result<Vec<Redeemer>, Error> {
|
||||
let redeemers = tx.transaction_witness_set.redeemer.as_ref();
|
||||
|
||||
let lookup_table = get_script_and_datum_lookup_table(tx, utxos);
|
||||
|
||||
if run_phase_one {
|
||||
// subset of phase 1 check on redeemers and scripts
|
||||
eval_phase_one(tx, utxos, &lookup_table)?;
|
||||
}
|
||||
|
||||
match redeemers {
|
||||
Some(rs) => {
|
||||
let mut collected_redeemers = vec![];
|
||||
|
||||
for redeemer in rs.iter() {
|
||||
let redeemer = eval::eval_redeemer(
|
||||
tx,
|
||||
utxos,
|
||||
slot_config,
|
||||
redeemer,
|
||||
&lookup_table,
|
||||
cost_mdls,
|
||||
initial_budget,
|
||||
)?;
|
||||
|
||||
collected_redeemers.push(redeemer)
|
||||
}
|
||||
|
||||
Ok(collected_redeemers)
|
||||
}
|
||||
None => Ok(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is the same as [`eval_phase_two`]
|
||||
/// but the inputs are raw bytes.
|
||||
/// initial_budget expects (cpu, mem).
|
||||
/// slot_config (zero_time, zero_slot, slot_length)
|
||||
pub fn eval_phase_two_raw(
|
||||
tx_bytes: &[u8],
|
||||
utxos_bytes: &[(Vec<u8>, Vec<u8>)],
|
||||
cost_mdls_bytes: &[u8],
|
||||
initial_budget: (u64, u64),
|
||||
slot_config: (u64, u64, u64),
|
||||
run_phase_one: bool,
|
||||
) -> Result<Vec<Vec<u8>>, Error> {
|
||||
let multi_era_tx = MultiEraTx::decode(Era::Babbage, tx_bytes)
|
||||
.or_else(|_| MultiEraTx::decode(Era::Alonzo, tx_bytes))?;
|
||||
|
||||
let cost_mdls = CostMdls::decode_fragment(cost_mdls_bytes)?;
|
||||
|
||||
let budget = ExBudget {
|
||||
cpu: initial_budget.0 as i64,
|
||||
mem: initial_budget.1 as i64,
|
||||
};
|
||||
|
||||
let mut utxos = Vec::new();
|
||||
|
||||
for (input, output) in utxos_bytes {
|
||||
utxos.push(ResolvedInput {
|
||||
input: TransactionInput::decode_fragment(input)?,
|
||||
output: TransactionOutput::decode_fragment(output)?,
|
||||
});
|
||||
}
|
||||
|
||||
let sc = SlotConfig {
|
||||
zero_time: slot_config.0,
|
||||
zero_slot: slot_config.1,
|
||||
slot_length: slot_config.2,
|
||||
};
|
||||
|
||||
match multi_era_tx {
|
||||
MultiEraTx::Babbage(tx) => {
|
||||
match eval_phase_two(
|
||||
&tx,
|
||||
&utxos,
|
||||
Some(&cost_mdls),
|
||||
Some(&budget),
|
||||
&sc,
|
||||
run_phase_one,
|
||||
) {
|
||||
Ok(redeemers) => Ok(redeemers
|
||||
.iter()
|
||||
.map(|r| r.encode_fragment().unwrap())
|
||||
.collect()),
|
||||
Err(_) => Err(Error::NoRedeemers),
|
||||
}
|
||||
}
|
||||
// MultiEraTx::AlonzoCompatible(tx, _) => match eval_tx(&tx, &utxos, &sc) {
|
||||
// Ok(redeemers) => Ok(redeemers
|
||||
// .iter()
|
||||
// .map(|r| r.encode_fragment().unwrap())
|
||||
// .collect()),
|
||||
// Err(_) => Err(()),
|
||||
// },
|
||||
// TODO: I probably did a mistake here with using MintedTx which is only compatible with Babbage tx.
|
||||
_ => todo!("Wrong era. Please use babbage"),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
use crate::machine::{self, cost_model::ExBudget};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
Address(#[from] pallas_addresses::Error),
|
||||
#[error("Only shelley reward addresses can be a part of withdrawals")]
|
||||
BadWithdrawalAddress,
|
||||
#[error("{0}")]
|
||||
FlatDecode(#[from] flat_rs::de::Error),
|
||||
#[error("{0}")]
|
||||
FragmentDecode(#[from] pallas_primitives::Error),
|
||||
#[error("{}\n\n{:#?}\n\n{}", .0, .1, .2.join("\n"))]
|
||||
Machine(machine::Error, ExBudget, Vec<String>),
|
||||
#[error("Native script can't be executed in phase-two")]
|
||||
NativeScriptPhaseTwo,
|
||||
#[error("Can't eval without redeemers")]
|
||||
NoRedeemers,
|
||||
#[error("Mismatch in required redeemers: {} {}", .missing.join(" "), .extra.join(" "))]
|
||||
RequiredRedeemersMismatch {
|
||||
missing: Vec<String>,
|
||||
extra: Vec<String>,
|
||||
},
|
||||
#[error("Extraneous redeemer found: Tag {:?}, Index {}", tag, index)]
|
||||
ExtraneousRedeemer { tag: String, index: u32 },
|
||||
#[error("Resolved Input not found")]
|
||||
ResolvedInputNotFound,
|
||||
#[error("A key hash cannot be the hash of a script")]
|
||||
ScriptKeyHash,
|
||||
#[error("PlutusV1 cost model not found.")]
|
||||
V1CostModelNotFound,
|
||||
#[error("PlutusV2 cost model not found.")]
|
||||
V2CostModelNotFound,
|
||||
#[error("Wrong era, Please use Babbage or Alonzo: {0}")]
|
||||
WrongEra(#[from] pallas_codec::minicbor::decode::Error),
|
||||
#[error("Byron address not allowed in Plutus.")]
|
||||
ByronAddressNotAllowed,
|
||||
#[error("Inline datum not allowed in PlutusV1.")]
|
||||
InlineDatumNotAllowed,
|
||||
#[error("Script and input reference not allowed in PlutusV1.")]
|
||||
ScriptAndInputRefNotAllowed,
|
||||
#[error("Address doesn't contain a payment credential.")]
|
||||
NoPaymentCredential,
|
||||
#[error("Missing required datum in witness set. Datum hash: {}", hash)]
|
||||
MissingRequiredDatum { hash: String },
|
||||
#[error("Missing required script. Script hash: {}", hash)]
|
||||
MissingRequiredScript { hash: String },
|
||||
#[error("Missing required inline datum or datum hash in script input.")]
|
||||
MissingRequiredInlineDatumOrHash,
|
||||
#[error("Only stake deregistration and delegation are valid certificate script purposes.")]
|
||||
OnlyStakeDeregAndDelegAllowed,
|
||||
}
|
|
@ -0,0 +1,833 @@
|
|||
use crate::{
|
||||
ast::{FakeNamedDeBruijn, NamedDeBruijn, Program},
|
||||
machine::cost_model::ExBudget,
|
||||
PlutusData,
|
||||
};
|
||||
use pallas_addresses::{Address, ScriptHash, StakePayload};
|
||||
use pallas_codec::utils::{KeyValuePairs, MaybeIndefArray};
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::babbage::{
|
||||
Certificate, CostMdls, DatumHash, DatumOption, ExUnits, Language, Mint, MintedTx, NativeScript,
|
||||
PlutusV1Script, PlutusV2Script, PolicyId, Redeemer, RedeemerTag, RewardAccount, Script,
|
||||
StakeCredential, TransactionInput, TransactionOutput, Value, Withdrawals,
|
||||
};
|
||||
use pallas_traverse::{ComputeHash, OriginalHash};
|
||||
use std::{collections::HashMap, convert::TryInto, ops::Deref, vec};
|
||||
|
||||
use super::{
|
||||
script_context::{
|
||||
ResolvedInput, ScriptContext, ScriptPurpose, SlotConfig, TimeRange, TxInInfo, TxInfo,
|
||||
TxInfoV1, TxInfoV2, TxOut,
|
||||
},
|
||||
to_plutus_data::{MintValue, ToPlutusData},
|
||||
Error,
|
||||
};
|
||||
|
||||
fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> u64 {
|
||||
let ms_after_begin = (slot - sc.zero_slot) * sc.slot_length;
|
||||
sc.zero_time + ms_after_begin
|
||||
}
|
||||
|
||||
fn slot_range_to_posix_time_range(slot_range: TimeRange, sc: &SlotConfig) -> TimeRange {
|
||||
TimeRange {
|
||||
lower_bound: slot_range
|
||||
.lower_bound
|
||||
.map(|lower_bound| slot_to_begin_posix_time(lower_bound, sc)),
|
||||
upper_bound: slot_range
|
||||
.upper_bound
|
||||
.map(|upper_bound| slot_to_begin_posix_time(upper_bound, sc)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ScriptVersion {
|
||||
Native(NativeScript),
|
||||
V1(PlutusV1Script),
|
||||
V2(PlutusV2Script),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum ExecutionPurpose {
|
||||
WithDatum(ScriptVersion, PlutusData), // Spending
|
||||
NoDatum(ScriptVersion), // Minting, Wdrl, DCert
|
||||
}
|
||||
|
||||
pub struct DataLookupTable {
|
||||
datum: HashMap<DatumHash, PlutusData>,
|
||||
scripts: HashMap<ScriptHash, ScriptVersion>,
|
||||
}
|
||||
|
||||
impl DataLookupTable {
|
||||
pub fn scripts(&self) -> HashMap<ScriptHash, ScriptVersion> {
|
||||
self.scripts.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tx_in_info_v1(
|
||||
inputs: &[TransactionInput],
|
||||
utxos: &[ResolvedInput],
|
||||
) -> Result<Vec<TxInInfo>, Error> {
|
||||
inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
|
||||
Some(resolved) => resolved,
|
||||
None => return Err(Error::ResolvedInputNotFound),
|
||||
};
|
||||
let address = Address::from_bytes(match &utxo.output {
|
||||
TransactionOutput::Legacy(output) => output.address.as_ref(),
|
||||
TransactionOutput::PostAlonzo(output) => output.address.as_ref(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
match address {
|
||||
Address::Byron(_) => {
|
||||
return Err(Error::ByronAddressNotAllowed);
|
||||
}
|
||||
Address::Stake(_) => {
|
||||
return Err(Error::NoPaymentCredential);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match &utxo.output {
|
||||
TransactionOutput::Legacy(_) => {}
|
||||
TransactionOutput::PostAlonzo(output) => {
|
||||
if let Some(DatumOption::Data(_)) = output.datum_option {
|
||||
return Err(Error::InlineDatumNotAllowed);
|
||||
}
|
||||
|
||||
if output.script_ref.is_some() {
|
||||
return Err(Error::ScriptAndInputRefNotAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TxInInfo {
|
||||
out_ref: utxo.input.clone(),
|
||||
resolved: TxOut::V1(utxo.output.clone()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_tx_in_info_v2(
|
||||
inputs: &[TransactionInput],
|
||||
utxos: &[ResolvedInput],
|
||||
) -> Result<Vec<TxInInfo>, Error> {
|
||||
inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
|
||||
Some(resolved) => resolved,
|
||||
None => return Err(Error::ResolvedInputNotFound),
|
||||
};
|
||||
let address = Address::from_bytes(match &utxo.output {
|
||||
TransactionOutput::Legacy(output) => output.address.as_ref(),
|
||||
TransactionOutput::PostAlonzo(output) => output.address.as_ref(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
match address {
|
||||
Address::Byron(_) => {
|
||||
return Err(Error::ByronAddressNotAllowed);
|
||||
}
|
||||
Address::Stake(_) => {
|
||||
return Err(Error::NoPaymentCredential);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Ok(TxInInfo {
|
||||
out_ref: utxo.input.clone(),
|
||||
resolved: TxOut::V2(utxo.output.clone()),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_script_purpose(
|
||||
redeemer: &Redeemer,
|
||||
inputs: &[TransactionInput],
|
||||
mint: &Option<Mint>,
|
||||
dcert: &Option<Vec<Certificate>>,
|
||||
wdrl: &Option<Withdrawals>,
|
||||
) -> Result<ScriptPurpose, Error> {
|
||||
// sorting according to specs section 4.1: https://hydra.iohk.io/build/18583827/download/1/alonzo-changes.pdf
|
||||
let tag = redeemer.tag.clone();
|
||||
let index = redeemer.index;
|
||||
match tag {
|
||||
RedeemerTag::Mint => {
|
||||
// sort lexical by policy id
|
||||
let mut policy_ids = mint
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|(policy_id, _)| *policy_id)
|
||||
.collect::<Vec<PolicyId>>();
|
||||
policy_ids.sort();
|
||||
match policy_ids.get(index as usize) {
|
||||
Some(policy_id) => Ok(ScriptPurpose::Minting(*policy_id)),
|
||||
None => Err(Error::ExtraneousRedeemer {
|
||||
tag: "Mint".to_string(),
|
||||
index,
|
||||
}),
|
||||
}
|
||||
}
|
||||
RedeemerTag::Spend => {
|
||||
// sort lexical by tx_hash and index
|
||||
let mut inputs = inputs.to_vec();
|
||||
inputs.sort();
|
||||
match inputs.get(index as usize) {
|
||||
Some(input) => Ok(ScriptPurpose::Spending(input.clone())),
|
||||
None => Err(Error::ExtraneousRedeemer {
|
||||
tag: "Spend".to_string(),
|
||||
index,
|
||||
}),
|
||||
}
|
||||
}
|
||||
RedeemerTag::Reward => {
|
||||
// sort lexical by reward account
|
||||
let mut reward_accounts = wdrl
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|(racnt, _)| racnt.clone())
|
||||
.collect::<Vec<RewardAccount>>();
|
||||
reward_accounts.sort();
|
||||
let reward_account = match reward_accounts.get(index as usize) {
|
||||
Some(ra) => ra.clone(),
|
||||
None => {
|
||||
return Err(Error::ExtraneousRedeemer {
|
||||
tag: "Reward".to_string(),
|
||||
index,
|
||||
})
|
||||
}
|
||||
};
|
||||
let address = Address::from_bytes(&reward_account)?;
|
||||
let credential = match address {
|
||||
Address::Stake(stake_address) => match stake_address.payload() {
|
||||
StakePayload::Script(script_hash) => StakeCredential::Scripthash(*script_hash),
|
||||
StakePayload::Stake(_) => {
|
||||
return Err(Error::ScriptKeyHash);
|
||||
}
|
||||
},
|
||||
_ => return Err(Error::BadWithdrawalAddress),
|
||||
};
|
||||
Ok(ScriptPurpose::Rewarding(credential))
|
||||
}
|
||||
RedeemerTag::Cert => {
|
||||
// sort by order given in the tx (just take it as it is basically)
|
||||
match dcert
|
||||
.as_ref()
|
||||
.unwrap_or(&MaybeIndefArray::Indef(vec![]))
|
||||
.get(index as usize)
|
||||
{
|
||||
Some(cert) => Ok(ScriptPurpose::Certifying(cert.clone())),
|
||||
None => Err(Error::ExtraneousRedeemer {
|
||||
tag: "Cert".to_string(),
|
||||
index,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tx_info_v1(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
slot_config: &SlotConfig,
|
||||
) -> Result<TxInfo, Error> {
|
||||
let body = tx.transaction_body.clone();
|
||||
|
||||
if body.reference_inputs.is_some() {
|
||||
return Err(Error::ScriptAndInputRefNotAllowed);
|
||||
}
|
||||
|
||||
let inputs = get_tx_in_info_v1(&body.inputs, utxos)?;
|
||||
|
||||
let outputs = body
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| TxOut::V1(output.clone()))
|
||||
.collect();
|
||||
|
||||
let fee = Value::Coin(body.fee);
|
||||
let mint = body.mint.clone().unwrap_or(KeyValuePairs::Indef(vec![]));
|
||||
let dcert = body.certificates.clone().unwrap_or_default();
|
||||
let wdrl = body
|
||||
.withdrawals
|
||||
.clone()
|
||||
.unwrap_or(KeyValuePairs::Indef(vec![]))
|
||||
.deref()
|
||||
.clone();
|
||||
|
||||
let valid_range = slot_range_to_posix_time_range(
|
||||
TimeRange {
|
||||
lower_bound: body.validity_interval_start,
|
||||
upper_bound: body.ttl,
|
||||
},
|
||||
slot_config,
|
||||
);
|
||||
let signatories = body.required_signers.clone().unwrap_or_default();
|
||||
|
||||
let data = tx
|
||||
.transaction_witness_set
|
||||
.plutus_data
|
||||
.as_ref()
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.map(|d| (d.original_hash(), d.clone().unwrap()))
|
||||
.collect();
|
||||
|
||||
let id = tx.transaction_body.compute_hash();
|
||||
|
||||
Ok(TxInfo::V1(TxInfoV1 {
|
||||
inputs,
|
||||
outputs,
|
||||
fee,
|
||||
mint: MintValue { mint_value: mint },
|
||||
dcert,
|
||||
wdrl,
|
||||
valid_range,
|
||||
signatories,
|
||||
data,
|
||||
id,
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_tx_info_v2(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
slot_config: &SlotConfig,
|
||||
) -> Result<TxInfo, Error> {
|
||||
let body = tx.transaction_body.clone();
|
||||
|
||||
let inputs = get_tx_in_info_v2(&body.inputs, utxos)?;
|
||||
let reference_inputs =
|
||||
get_tx_in_info_v2(&body.reference_inputs.clone().unwrap_or_default(), utxos)?;
|
||||
let outputs = body
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| TxOut::V2(output.clone()))
|
||||
.collect();
|
||||
let fee = Value::Coin(body.fee);
|
||||
let mint = body.mint.clone().unwrap_or(KeyValuePairs::Indef(vec![]));
|
||||
let dcert = body.certificates.clone().unwrap_or_default();
|
||||
let wdrl = body
|
||||
.withdrawals
|
||||
.clone()
|
||||
.unwrap_or(KeyValuePairs::Indef(vec![]));
|
||||
let valid_range = slot_range_to_posix_time_range(
|
||||
TimeRange {
|
||||
lower_bound: body.validity_interval_start,
|
||||
upper_bound: body.ttl,
|
||||
},
|
||||
slot_config,
|
||||
);
|
||||
let signatories = body.required_signers.clone().unwrap_or_default();
|
||||
let redeemers = KeyValuePairs::Indef(
|
||||
tx.transaction_witness_set
|
||||
.redeemer
|
||||
.as_ref()
|
||||
.unwrap_or(&MaybeIndefArray::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|r| {
|
||||
(
|
||||
get_script_purpose(
|
||||
r,
|
||||
&tx.transaction_body.inputs,
|
||||
&tx.transaction_body.mint,
|
||||
&tx.transaction_body.certificates,
|
||||
&tx.transaction_body.withdrawals,
|
||||
)
|
||||
.unwrap(),
|
||||
r.clone(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
let data = KeyValuePairs::Indef(
|
||||
tx.transaction_witness_set
|
||||
.plutus_data
|
||||
.as_ref()
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.map(|d| (d.original_hash(), d.clone().unwrap()))
|
||||
.collect(),
|
||||
);
|
||||
let id = tx.transaction_body.compute_hash();
|
||||
|
||||
Ok(TxInfo::V2(TxInfoV2 {
|
||||
inputs,
|
||||
reference_inputs,
|
||||
outputs,
|
||||
fee,
|
||||
mint: MintValue { mint_value: mint },
|
||||
dcert,
|
||||
wdrl,
|
||||
valid_range,
|
||||
signatories,
|
||||
redeemers,
|
||||
data,
|
||||
id,
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_execution_purpose(
|
||||
utxos: &[ResolvedInput],
|
||||
script_purpose: &ScriptPurpose,
|
||||
lookup_table: &DataLookupTable,
|
||||
) -> Result<ExecutionPurpose, Error> {
|
||||
match script_purpose {
|
||||
ScriptPurpose::Minting(policy_id) => {
|
||||
let policy_id_array: [u8; 28] = policy_id.to_vec().try_into().unwrap();
|
||||
let hash = Hash::from(policy_id_array);
|
||||
|
||||
let script = match lookup_table.scripts.get(&hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(ExecutionPurpose::NoDatum(script))
|
||||
}
|
||||
ScriptPurpose::Spending(out_ref) => {
|
||||
let utxo = match utxos.iter().find(|utxo| utxo.input == *out_ref) {
|
||||
Some(resolved) => resolved,
|
||||
None => return Err(Error::ResolvedInputNotFound),
|
||||
};
|
||||
match &utxo.output {
|
||||
TransactionOutput::Legacy(output) => {
|
||||
let address = Address::from_bytes(&output.address).unwrap();
|
||||
match address {
|
||||
Address::Shelley(shelley_address) => {
|
||||
let hash = shelley_address.payment().as_hash();
|
||||
let script = match lookup_table.scripts.get(hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let datum_hash = match &output.datum_hash {
|
||||
Some(hash) => hash,
|
||||
None => return Err(Error::MissingRequiredInlineDatumOrHash),
|
||||
};
|
||||
|
||||
let datum = match lookup_table.datum.get(datum_hash) {
|
||||
Some(d) => d.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredDatum {
|
||||
hash: datum_hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExecutionPurpose::WithDatum(script, datum))
|
||||
}
|
||||
_ => Err(Error::ScriptKeyHash),
|
||||
}
|
||||
}
|
||||
TransactionOutput::PostAlonzo(output) => {
|
||||
let address = Address::from_bytes(&output.address).unwrap();
|
||||
match address {
|
||||
Address::Shelley(shelley_address) => {
|
||||
let hash = shelley_address.payment().as_hash();
|
||||
let script = match lookup_table.scripts.get(hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let datum = match &output.datum_option {
|
||||
Some(DatumOption::Hash(hash)) => {
|
||||
match lookup_table.datum.get(hash) {
|
||||
Some(d) => d.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredDatum {
|
||||
hash: hash.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(DatumOption::Data(data)) => data.0.clone(),
|
||||
_ => return Err(Error::MissingRequiredInlineDatumOrHash),
|
||||
};
|
||||
|
||||
Ok(ExecutionPurpose::WithDatum(script, datum))
|
||||
}
|
||||
_ => Err(Error::ScriptKeyHash),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScriptPurpose::Rewarding(stake_credential) => {
|
||||
let script_hash = match stake_credential {
|
||||
StakeCredential::Scripthash(hash) => *hash,
|
||||
_ => return Err(Error::ScriptKeyHash),
|
||||
};
|
||||
|
||||
let script = match lookup_table.scripts.get(&script_hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: script_hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExecutionPurpose::NoDatum(script))
|
||||
}
|
||||
ScriptPurpose::Certifying(cert) => match cert {
|
||||
Certificate::StakeDeregistration(stake_credential) => {
|
||||
let script_hash = match stake_credential {
|
||||
StakeCredential::Scripthash(hash) => *hash,
|
||||
_ => return Err(Error::ScriptKeyHash),
|
||||
};
|
||||
|
||||
let script = match lookup_table.scripts.get(&script_hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: script_hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExecutionPurpose::NoDatum(script))
|
||||
}
|
||||
Certificate::StakeDelegation(stake_credential, _) => {
|
||||
let script_hash = match stake_credential {
|
||||
StakeCredential::Scripthash(hash) => *hash,
|
||||
_ => return Err(Error::ScriptKeyHash),
|
||||
};
|
||||
|
||||
let script = match lookup_table.scripts.get(&script_hash) {
|
||||
Some(s) => s.clone(),
|
||||
None => {
|
||||
return Err(Error::MissingRequiredScript {
|
||||
hash: script_hash.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ExecutionPurpose::NoDatum(script))
|
||||
}
|
||||
_ => Err(Error::OnlyStakeDeregAndDelegAllowed),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_script_and_datum_lookup_table(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
) -> DataLookupTable {
|
||||
let mut datum = HashMap::new();
|
||||
let mut scripts = HashMap::new();
|
||||
|
||||
// discovery in witness set
|
||||
|
||||
let plutus_data_witnesses = tx
|
||||
.transaction_witness_set
|
||||
.plutus_data
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
let scripts_native_witnesses = tx
|
||||
.transaction_witness_set
|
||||
.native_script
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
let scripts_v1_witnesses = tx
|
||||
.transaction_witness_set
|
||||
.plutus_v1_script
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
let scripts_v2_witnesses = tx
|
||||
.transaction_witness_set
|
||||
.plutus_v2_script
|
||||
.clone()
|
||||
.unwrap_or_default();
|
||||
|
||||
for plutus_data in plutus_data_witnesses.iter() {
|
||||
datum.insert(plutus_data.original_hash(), plutus_data.clone().unwrap());
|
||||
}
|
||||
|
||||
for script in scripts_native_witnesses.iter() {
|
||||
scripts.insert(script.compute_hash(), ScriptVersion::Native(script.clone()));
|
||||
}
|
||||
|
||||
for script in scripts_v1_witnesses.iter() {
|
||||
scripts.insert(script.compute_hash(), ScriptVersion::V1(script.clone()));
|
||||
}
|
||||
|
||||
for script in scripts_v2_witnesses.iter() {
|
||||
scripts.insert(script.compute_hash(), ScriptVersion::V2(script.clone()));
|
||||
}
|
||||
|
||||
// discovery in utxos (script ref)
|
||||
|
||||
for utxo in utxos.iter() {
|
||||
match &utxo.output {
|
||||
TransactionOutput::Legacy(_) => {}
|
||||
TransactionOutput::PostAlonzo(output) => {
|
||||
if let Some(script) = &output.script_ref {
|
||||
match &script.0 {
|
||||
Script::NativeScript(ns) => {
|
||||
scripts.insert(ns.compute_hash(), ScriptVersion::Native(ns.clone()));
|
||||
}
|
||||
Script::PlutusV1Script(v1) => {
|
||||
scripts.insert(v1.compute_hash(), ScriptVersion::V1(v1.clone()));
|
||||
}
|
||||
Script::PlutusV2Script(v2) => {
|
||||
scripts.insert(v2.compute_hash(), ScriptVersion::V2(v2.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataLookupTable { datum, scripts }
|
||||
}
|
||||
|
||||
pub fn eval_redeemer(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
slot_config: &SlotConfig,
|
||||
redeemer: &Redeemer,
|
||||
lookup_table: &DataLookupTable,
|
||||
cost_mdls_opt: Option<&CostMdls>,
|
||||
initial_budget: Option<&ExBudget>,
|
||||
) -> Result<Redeemer, Error> {
|
||||
let purpose = get_script_purpose(
|
||||
redeemer,
|
||||
&tx.transaction_body.inputs,
|
||||
&tx.transaction_body.mint,
|
||||
&tx.transaction_body.certificates,
|
||||
&tx.transaction_body.withdrawals,
|
||||
)?;
|
||||
|
||||
let execution_purpose: ExecutionPurpose = get_execution_purpose(utxos, &purpose, lookup_table)?;
|
||||
|
||||
match execution_purpose {
|
||||
ExecutionPurpose::WithDatum(script_version, datum) => match script_version {
|
||||
ScriptVersion::V1(script) => {
|
||||
let tx_info = get_tx_info_v1(tx, utxos, slot_config)?;
|
||||
let script_context = ScriptContext { tx_info, purpose };
|
||||
|
||||
let program: Program<NamedDeBruijn> = {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&script.0, &mut buffer)?;
|
||||
|
||||
prog.into()
|
||||
};
|
||||
|
||||
let program = program
|
||||
.apply_data(datum)
|
||||
.apply_data(redeemer.data.clone())
|
||||
.apply_data(script_context.to_plutus_data());
|
||||
|
||||
let (result, budget, logs) = if let Some(cost_mdls) = cost_mdls_opt {
|
||||
let costs = if let Some(costs) = &cost_mdls.plutus_v1 {
|
||||
costs
|
||||
} else {
|
||||
return Err(Error::V1CostModelNotFound);
|
||||
};
|
||||
|
||||
program.eval_as(&Language::PlutusV1, costs, initial_budget)
|
||||
} else {
|
||||
program.eval_v1()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(Error::Machine(err, budget, logs)),
|
||||
}
|
||||
|
||||
let initial_budget = match initial_budget {
|
||||
Some(b) => *b,
|
||||
None => ExBudget::default(),
|
||||
};
|
||||
|
||||
let new_redeemer = Redeemer {
|
||||
tag: redeemer.tag.clone(),
|
||||
index: redeemer.index,
|
||||
data: redeemer.data.clone(),
|
||||
ex_units: ExUnits {
|
||||
mem: (initial_budget.mem - budget.mem) as u32,
|
||||
steps: (initial_budget.cpu - budget.cpu) as u64,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(new_redeemer)
|
||||
}
|
||||
ScriptVersion::V2(script) => {
|
||||
let tx_info = get_tx_info_v2(tx, utxos, slot_config)?;
|
||||
let script_context = ScriptContext { tx_info, purpose };
|
||||
|
||||
let program: Program<NamedDeBruijn> = {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&script.0, &mut buffer)?;
|
||||
|
||||
prog.into()
|
||||
};
|
||||
|
||||
let program = program
|
||||
.apply_data(datum)
|
||||
.apply_data(redeemer.data.clone())
|
||||
.apply_data(script_context.to_plutus_data());
|
||||
|
||||
let (result, budget, logs) = if let Some(cost_mdls) = cost_mdls_opt {
|
||||
let costs = if let Some(costs) = &cost_mdls.plutus_v2 {
|
||||
costs
|
||||
} else {
|
||||
return Err(Error::V2CostModelNotFound);
|
||||
};
|
||||
|
||||
program.eval_as(&Language::PlutusV2, costs, initial_budget)
|
||||
} else {
|
||||
program.eval()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(Error::Machine(err, budget, logs)),
|
||||
}
|
||||
|
||||
let initial_budget = match initial_budget {
|
||||
Some(b) => *b,
|
||||
None => ExBudget::default(),
|
||||
};
|
||||
|
||||
let new_redeemer = Redeemer {
|
||||
tag: redeemer.tag.clone(),
|
||||
index: redeemer.index,
|
||||
data: redeemer.data.clone(),
|
||||
ex_units: ExUnits {
|
||||
mem: (initial_budget.mem - budget.mem) as u32,
|
||||
steps: (initial_budget.cpu - budget.cpu) as u64,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(new_redeemer)
|
||||
}
|
||||
ScriptVersion::Native(_) => Err(Error::NativeScriptPhaseTwo),
|
||||
},
|
||||
ExecutionPurpose::NoDatum(script_version) => match script_version {
|
||||
ScriptVersion::V1(script) => {
|
||||
let tx_info = get_tx_info_v1(tx, utxos, slot_config)?;
|
||||
let script_context = ScriptContext { tx_info, purpose };
|
||||
|
||||
let program: Program<NamedDeBruijn> = {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&script.0, &mut buffer)?;
|
||||
|
||||
prog.into()
|
||||
};
|
||||
|
||||
let program = program
|
||||
.apply_data(redeemer.data.clone())
|
||||
.apply_data(script_context.to_plutus_data());
|
||||
|
||||
let (result, budget, logs) = if let Some(cost_mdls) = cost_mdls_opt {
|
||||
let costs = if let Some(costs) = &cost_mdls.plutus_v1 {
|
||||
costs
|
||||
} else {
|
||||
return Err(Error::V1CostModelNotFound);
|
||||
};
|
||||
|
||||
program.eval_as(&Language::PlutusV1, costs, initial_budget)
|
||||
} else {
|
||||
program.eval_v1()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(Error::Machine(err, budget, logs)),
|
||||
}
|
||||
|
||||
let initial_budget = match initial_budget {
|
||||
Some(b) => *b,
|
||||
None => ExBudget::default(),
|
||||
};
|
||||
|
||||
let new_redeemer = Redeemer {
|
||||
tag: redeemer.tag.clone(),
|
||||
index: redeemer.index,
|
||||
data: redeemer.data.clone(),
|
||||
ex_units: ExUnits {
|
||||
mem: (initial_budget.mem - budget.mem) as u32,
|
||||
steps: (initial_budget.cpu - budget.cpu) as u64,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(new_redeemer)
|
||||
}
|
||||
ScriptVersion::V2(script) => {
|
||||
let tx_info = get_tx_info_v2(tx, utxos, slot_config)?;
|
||||
let script_context = ScriptContext { tx_info, purpose };
|
||||
|
||||
let program: Program<NamedDeBruijn> = {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&script.0, &mut buffer)?;
|
||||
|
||||
prog.into()
|
||||
};
|
||||
|
||||
let program = program
|
||||
.apply_data(redeemer.data.clone())
|
||||
.apply_data(script_context.to_plutus_data());
|
||||
|
||||
let (result, budget, logs) = if let Some(cost_mdls) = cost_mdls_opt {
|
||||
let costs = if let Some(costs) = &cost_mdls.plutus_v2 {
|
||||
costs
|
||||
} else {
|
||||
return Err(Error::V2CostModelNotFound);
|
||||
};
|
||||
|
||||
program.eval_as(&Language::PlutusV2, costs, initial_budget)
|
||||
} else {
|
||||
program.eval()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(err) => return Err(Error::Machine(err, budget, logs)),
|
||||
}
|
||||
|
||||
let initial_budget = match initial_budget {
|
||||
Some(b) => *b,
|
||||
None => ExBudget::default(),
|
||||
};
|
||||
|
||||
let new_redeemer = Redeemer {
|
||||
tag: redeemer.tag.clone(),
|
||||
index: redeemer.index,
|
||||
data: redeemer.data.clone(),
|
||||
ex_units: ExUnits {
|
||||
mem: (initial_budget.mem - budget.mem) as u32,
|
||||
steps: (initial_budget.cpu - budget.cpu) as u64,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(new_redeemer)
|
||||
}
|
||||
ScriptVersion::Native(_) => Err(Error::NativeScriptPhaseTwo),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,324 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use pallas_addresses::{Address, ScriptHash, ShelleyPaymentPart, StakePayload};
|
||||
use pallas_codec::utils::{KeyValuePairs, MaybeIndefArray};
|
||||
use pallas_primitives::babbage::{
|
||||
Certificate, MintedTx, PolicyId, RedeemerTag, RewardAccount, StakeCredential, TransactionOutput,
|
||||
};
|
||||
|
||||
use super::{
|
||||
error::Error,
|
||||
eval::{DataLookupTable, ScriptVersion},
|
||||
script_context::{ResolvedInput, ScriptPurpose},
|
||||
};
|
||||
|
||||
// TODO: include in pallas eventually?
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct RedeemerPtr {
|
||||
tag: RedeemerTag,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
type AlonzoScriptsNeeded = Vec<(ScriptPurpose, ScriptHash)>;
|
||||
|
||||
// subset of phase-1 ledger checks related to scripts
|
||||
pub fn eval_phase_one(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
lookup_table: &DataLookupTable,
|
||||
) -> Result<(), Error> {
|
||||
let scripts_needed = scripts_needed(tx, utxos)?;
|
||||
|
||||
validate_missing_scripts(&scripts_needed, lookup_table.scripts())?;
|
||||
|
||||
has_exact_set_of_redeemers(tx, &scripts_needed, lookup_table.scripts())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_missing_scripts(
|
||||
needed: &AlonzoScriptsNeeded,
|
||||
txscripts: HashMap<ScriptHash, ScriptVersion>,
|
||||
) -> Result<(), Error> {
|
||||
let received_hashes = txscripts.keys().copied().collect::<Vec<ScriptHash>>();
|
||||
|
||||
let needed_hashes = needed.iter().map(|x| x.1).collect::<Vec<ScriptHash>>();
|
||||
|
||||
let missing: Vec<_> = needed_hashes
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|x| !received_hashes.contains(x))
|
||||
.map(|x| format!("[Missing (sh: {})]", x))
|
||||
.collect();
|
||||
|
||||
let extra: Vec<_> = received_hashes
|
||||
.into_iter()
|
||||
.filter(|x| !needed_hashes.contains(x))
|
||||
.map(|x| format!("[Extraneous (sh: {:?})]", x))
|
||||
.collect();
|
||||
|
||||
if !missing.is_empty() || !extra.is_empty() {
|
||||
let missing_errors = missing.join(" ");
|
||||
let extra_errors = extra.join(" ");
|
||||
|
||||
unreachable!(
|
||||
"Mismatch in required scripts: {} {}",
|
||||
missing_errors, extra_errors
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scripts_needed(
|
||||
tx: &MintedTx,
|
||||
utxos: &[ResolvedInput],
|
||||
) -> Result<AlonzoScriptsNeeded, Error> {
|
||||
let mut needed = Vec::new();
|
||||
|
||||
let txb = tx.transaction_body.clone();
|
||||
|
||||
let mut spend = Vec::new();
|
||||
|
||||
for input in txb.inputs.iter() {
|
||||
let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
|
||||
Some(u) => u,
|
||||
None => return Err(Error::ResolvedInputNotFound),
|
||||
};
|
||||
|
||||
let address = Address::from_bytes(match &utxo.output {
|
||||
TransactionOutput::Legacy(output) => output.address.as_ref(),
|
||||
TransactionOutput::PostAlonzo(output) => output.address.as_ref(),
|
||||
})?;
|
||||
|
||||
if let Address::Shelley(a) = address {
|
||||
if let ShelleyPaymentPart::Script(h) = a.payment() {
|
||||
spend.push((ScriptPurpose::Spending(input.clone()), *h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut reward = txb
|
||||
.withdrawals
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.filter_map(|(acnt, _)| {
|
||||
let address = Address::from_bytes(acnt).unwrap();
|
||||
|
||||
if let Address::Stake(a) = address {
|
||||
if let StakePayload::Script(h) = a.payload() {
|
||||
let cred = StakeCredential::Scripthash(*h);
|
||||
return Some((ScriptPurpose::Rewarding(cred), *h));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect::<AlonzoScriptsNeeded>();
|
||||
|
||||
let mut cert = txb
|
||||
.certificates
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(|cert| {
|
||||
// only Dereg and Deleg certs can require scripts
|
||||
match cert {
|
||||
Certificate::StakeDeregistration(StakeCredential::Scripthash(h)) => {
|
||||
Some((ScriptPurpose::Certifying(cert.clone()), *h))
|
||||
}
|
||||
Certificate::StakeDelegation(StakeCredential::Scripthash(h), _) => {
|
||||
Some((ScriptPurpose::Certifying(cert.clone()), *h))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect::<AlonzoScriptsNeeded>();
|
||||
|
||||
let mut mint = txb
|
||||
.mint
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|(policy_id, _)| (ScriptPurpose::Minting(*policy_id), *policy_id))
|
||||
.collect::<AlonzoScriptsNeeded>();
|
||||
|
||||
needed.append(&mut spend);
|
||||
needed.append(&mut reward);
|
||||
needed.append(&mut cert);
|
||||
needed.append(&mut mint);
|
||||
|
||||
Ok(needed)
|
||||
}
|
||||
|
||||
/// hasExactSetOfRedeemers in Ledger Spec, but we pass `txscripts` directly
|
||||
pub fn has_exact_set_of_redeemers(
|
||||
tx: &MintedTx,
|
||||
needed: &AlonzoScriptsNeeded,
|
||||
tx_scripts: HashMap<ScriptHash, ScriptVersion>,
|
||||
) -> Result<(), Error> {
|
||||
let mut redeemers_needed = Vec::new();
|
||||
|
||||
for (script_purpose, script_hash) in needed {
|
||||
let redeemer_ptr = build_redeemer_ptr(tx, script_purpose)?;
|
||||
let script = tx_scripts.get(script_hash);
|
||||
|
||||
if let (Some(ptr), Some(script)) = (redeemer_ptr, script) {
|
||||
match script {
|
||||
ScriptVersion::V1(_) => {
|
||||
redeemers_needed.push((ptr, script_purpose.clone(), *script_hash))
|
||||
}
|
||||
ScriptVersion::V2(_) => {
|
||||
redeemers_needed.push((ptr, script_purpose.clone(), *script_hash))
|
||||
}
|
||||
ScriptVersion::Native(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let wits_redeemer_ptrs: Vec<RedeemerPtr> = tx
|
||||
.transaction_witness_set
|
||||
.redeemer
|
||||
.as_ref()
|
||||
.unwrap_or(&MaybeIndefArray::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|r| RedeemerPtr {
|
||||
tag: r.tag.clone(),
|
||||
index: r.index,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let needed_redeemer_ptrs: Vec<RedeemerPtr> =
|
||||
redeemers_needed.iter().map(|x| x.0.clone()).collect();
|
||||
|
||||
let missing: Vec<_> = redeemers_needed
|
||||
.into_iter()
|
||||
.filter(|x| !wits_redeemer_ptrs.contains(&x.0))
|
||||
.map(|x| {
|
||||
format!(
|
||||
"[Missing (redeemer_ptr: {:?}, script_purpose: {:?}, script_hash: {})]",
|
||||
x.0, x.1, x.2,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let extra: Vec<_> = wits_redeemer_ptrs
|
||||
.into_iter()
|
||||
.filter(|x| !needed_redeemer_ptrs.contains(x))
|
||||
.map(|x| format!("[Extraneous (redeemer_ptr: {:?})]", x))
|
||||
.collect();
|
||||
|
||||
if !missing.is_empty() || !extra.is_empty() {
|
||||
Err(Error::RequiredRedeemersMismatch { missing, extra })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// builds a redeemer pointer (tag, index) from a script purpose by setting the tag
|
||||
/// according to the type of the script purpose, and the index according to the
|
||||
/// placement of script purpose inside its container.
|
||||
fn build_redeemer_ptr(
|
||||
tx: &MintedTx,
|
||||
script_purpose: &ScriptPurpose,
|
||||
) -> Result<Option<RedeemerPtr>, Error> {
|
||||
let tx_body = tx.transaction_body.clone();
|
||||
|
||||
match script_purpose {
|
||||
ScriptPurpose::Minting(hash) => {
|
||||
let mut policy_ids = tx_body
|
||||
.mint
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|(policy_id, _)| *policy_id)
|
||||
.collect::<Vec<PolicyId>>();
|
||||
|
||||
policy_ids.sort();
|
||||
|
||||
let maybe_idx = policy_ids.iter().position(|x| x == hash);
|
||||
|
||||
match maybe_idx {
|
||||
Some(idx) => Ok(Some(RedeemerPtr {
|
||||
tag: RedeemerTag::Mint,
|
||||
index: idx as u32,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
ScriptPurpose::Spending(txin) => {
|
||||
let mut inputs = tx_body.inputs.to_vec();
|
||||
inputs.sort_by(
|
||||
|i_a, i_b| match i_a.transaction_id.cmp(&i_b.transaction_id) {
|
||||
std::cmp::Ordering::Less => std::cmp::Ordering::Less,
|
||||
std::cmp::Ordering::Equal => i_a.index.cmp(&i_b.index),
|
||||
std::cmp::Ordering::Greater => std::cmp::Ordering::Greater,
|
||||
},
|
||||
);
|
||||
|
||||
let maybe_idx = inputs.iter().position(|x| x == txin);
|
||||
|
||||
match maybe_idx {
|
||||
Some(idx) => Ok(Some(RedeemerPtr {
|
||||
tag: RedeemerTag::Spend,
|
||||
index: idx as u32,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
ScriptPurpose::Rewarding(racnt) => {
|
||||
let mut reward_accounts = tx_body
|
||||
.withdrawals
|
||||
.as_ref()
|
||||
.unwrap_or(&KeyValuePairs::Indef(vec![]))
|
||||
.iter()
|
||||
.map(|(acnt, _)| acnt.clone())
|
||||
.collect::<Vec<RewardAccount>>();
|
||||
|
||||
reward_accounts.sort();
|
||||
|
||||
let mut maybe_idx = None;
|
||||
|
||||
for (idx, x) in reward_accounts.iter().enumerate() {
|
||||
let cred = match Address::from_bytes(x).unwrap() {
|
||||
Address::Stake(a) => match a.payload() {
|
||||
StakePayload::Script(sh) => StakeCredential::Scripthash(*sh),
|
||||
StakePayload::Stake(_) => {
|
||||
return Err(Error::ScriptKeyHash);
|
||||
}
|
||||
},
|
||||
_ => return Err(Error::BadWithdrawalAddress),
|
||||
};
|
||||
|
||||
if cred == *racnt {
|
||||
maybe_idx = Some(idx);
|
||||
}
|
||||
}
|
||||
|
||||
match maybe_idx {
|
||||
Some(idx) => Ok(Some(RedeemerPtr {
|
||||
tag: RedeemerTag::Reward,
|
||||
index: idx as u32,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
ScriptPurpose::Certifying(d) => {
|
||||
let maybe_idx = tx_body
|
||||
.certificates
|
||||
.as_ref()
|
||||
.unwrap_or(&MaybeIndefArray::Indef(vec![]))
|
||||
.iter()
|
||||
.position(|x| x == d);
|
||||
|
||||
match maybe_idx {
|
||||
Some(idx) => Ok(Some(RedeemerPtr {
|
||||
tag: RedeemerTag::Cert,
|
||||
index: idx as u32,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
use pallas_codec::utils::KeyValuePairs;
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::babbage::{
|
||||
AddrKeyhash, Certificate, Coin, DatumHash, PlutusData, PolicyId, Redeemer, RewardAccount,
|
||||
StakeCredential, TransactionInput, TransactionOutput, Value, Withdrawals,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::to_plutus_data::MintValue;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize)]
|
||||
pub struct ResolvedInput {
|
||||
pub input: TransactionInput,
|
||||
pub output: TransactionOutput,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TxInInfo {
|
||||
pub out_ref: TransactionInput,
|
||||
pub resolved: TxOut,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TxOut {
|
||||
V1(TransactionOutput),
|
||||
V2(TransactionOutput),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ScriptPurpose {
|
||||
Minting(PolicyId),
|
||||
Spending(TransactionInput),
|
||||
Rewarding(StakeCredential),
|
||||
Certifying(Certificate),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TxInfoV1 {
|
||||
pub inputs: Vec<TxInInfo>,
|
||||
pub outputs: Vec<TxOut>,
|
||||
pub fee: Value,
|
||||
pub mint: MintValue,
|
||||
pub dcert: Vec<Certificate>,
|
||||
pub wdrl: Vec<(RewardAccount, Coin)>,
|
||||
pub valid_range: TimeRange,
|
||||
pub signatories: Vec<AddrKeyhash>,
|
||||
pub data: Vec<(DatumHash, PlutusData)>,
|
||||
pub id: Hash<32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TxInfoV2 {
|
||||
pub inputs: Vec<TxInInfo>,
|
||||
pub reference_inputs: Vec<TxInInfo>,
|
||||
pub outputs: Vec<TxOut>,
|
||||
pub fee: Value,
|
||||
pub mint: MintValue,
|
||||
pub dcert: Vec<Certificate>,
|
||||
pub wdrl: Withdrawals,
|
||||
pub valid_range: TimeRange,
|
||||
pub signatories: Vec<AddrKeyhash>,
|
||||
pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
|
||||
pub data: KeyValuePairs<DatumHash, PlutusData>,
|
||||
pub id: Hash<32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TxInfo {
|
||||
V1(TxInfoV1),
|
||||
V2(TxInfoV2),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ScriptContext {
|
||||
pub tx_info: TxInfo,
|
||||
pub purpose: ScriptPurpose,
|
||||
}
|
||||
|
||||
//---- Time conversion: slot range => posix time range
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TimeRange {
|
||||
pub lower_bound: Option<u64>,
|
||||
pub upper_bound: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct SlotConfig {
|
||||
pub slot_length: u64,
|
||||
pub zero_slot: u64,
|
||||
pub zero_time: u64,
|
||||
}
|
||||
|
||||
impl Default for SlotConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
slot_length: 1000,
|
||||
zero_slot: 4492800,
|
||||
zero_time: 1596059091000,
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,611 @@
|
|||
use pallas_addresses::{Address, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_codec::utils::{AnyUInt, Bytes, Int, KeyValuePairs};
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::babbage::{AssetName, BigInt, Constr, Mint, PlutusData, ScriptRef};
|
||||
use pallas_primitives::babbage::{
|
||||
Certificate, DatumOption, Redeemer, Script, StakeCredential, TransactionInput,
|
||||
TransactionOutput, Value,
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
|
||||
use super::script_context::{ScriptContext, ScriptPurpose, TimeRange, TxInInfo, TxInfo, TxOut};
|
||||
|
||||
fn wrap_with_constr(index: u64, data: PlutusData) -> PlutusData {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: constr_index(index),
|
||||
any_constructor: None,
|
||||
fields: vec![data],
|
||||
})
|
||||
}
|
||||
|
||||
fn wrap_multiple_with_constr(index: u64, data: Vec<PlutusData>) -> PlutusData {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: constr_index(index),
|
||||
any_constructor: None,
|
||||
fields: data,
|
||||
})
|
||||
}
|
||||
|
||||
fn empty_constr(index: u64) -> PlutusData {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: constr_index(index),
|
||||
any_constructor: None,
|
||||
fields: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
/// Translate constructor index to cbor tag.
|
||||
fn constr_index(index: u64) -> u64 {
|
||||
121 + index
|
||||
}
|
||||
|
||||
pub trait ToPlutusData {
|
||||
fn to_plutus_data(&self) -> PlutusData;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct MintValue {
|
||||
pub mint_value: Mint,
|
||||
}
|
||||
|
||||
impl ToPlutusData for Address {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
Address::Shelley(shelley_address) => {
|
||||
let payment_part = shelley_address.payment();
|
||||
let stake_part = shelley_address.delegation();
|
||||
|
||||
let payment_part_plutus_data = match payment_part {
|
||||
ShelleyPaymentPart::Key(payment_keyhash) => {
|
||||
wrap_with_constr(0, payment_keyhash.to_plutus_data())
|
||||
}
|
||||
ShelleyPaymentPart::Script(script_hash) => {
|
||||
wrap_with_constr(1, script_hash.to_plutus_data())
|
||||
}
|
||||
};
|
||||
|
||||
let stake_part_plutus_data = match stake_part {
|
||||
ShelleyDelegationPart::Key(stake_keyhash) => {
|
||||
Some(StakeCredential::AddrKeyhash(*stake_keyhash)).to_plutus_data()
|
||||
}
|
||||
ShelleyDelegationPart::Script(script_hash) => {
|
||||
Some(StakeCredential::Scripthash(*script_hash)).to_plutus_data()
|
||||
}
|
||||
ShelleyDelegationPart::Pointer(pointer) => Some(wrap_multiple_with_constr(
|
||||
1,
|
||||
vec![
|
||||
pointer.slot().to_plutus_data(),
|
||||
pointer.tx_idx().to_plutus_data(),
|
||||
pointer.cert_idx().to_plutus_data(),
|
||||
],
|
||||
))
|
||||
.to_plutus_data(),
|
||||
ShelleyDelegationPart::Null => None::<StakeCredential>.to_plutus_data(),
|
||||
};
|
||||
|
||||
wrap_multiple_with_constr(0, vec![payment_part_plutus_data, stake_part_plutus_data])
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for TransactionInput {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
wrap_with_constr(0, self.transaction_id.to_plutus_data()),
|
||||
PlutusData::BigInt(BigInt::Int((self.index as i64).into())),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const BYTES: usize> ToPlutusData for Hash<BYTES> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BoundedBytes(self.to_vec().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Bytes {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BoundedBytes(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: ToPlutusData, V: ToPlutusData> ToPlutusData for (K, V) {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
wrap_multiple_with_constr(0, vec![self.0.to_plutus_data(), self.1.to_plutus_data()])
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> ToPlutusData for Vec<A>
|
||||
where
|
||||
A: ToPlutusData,
|
||||
{
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::Array(self.iter().map(|p| p.to_plutus_data()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> ToPlutusData for KeyValuePairs<K, V>
|
||||
where
|
||||
K: ToPlutusData + Clone,
|
||||
V: ToPlutusData + Clone,
|
||||
{
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![];
|
||||
for (key, value) in self.iter() {
|
||||
data_vec.push((key.to_plutus_data(), value.to_plutus_data()))
|
||||
}
|
||||
PlutusData::Map(KeyValuePairs::Def(data_vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ToPlutusData> ToPlutusData for Option<A> {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
None => empty_constr(1),
|
||||
Some(data) => wrap_with_constr(0, data.to_plutus_data()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Does this here surely overwrite Option from above for DatumOption?
|
||||
impl ToPlutusData for Option<DatumOption> {
|
||||
// NoOutputDatum = 0 | OutputDatumHash = 1 | OutputDatum = 2
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
None => empty_constr(0),
|
||||
Some(option) => match option {
|
||||
DatumOption::Hash(hash) => wrap_with_constr(1, hash.to_plutus_data()),
|
||||
DatumOption::Data(data) => wrap_with_constr(2, data.0.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for AnyUInt {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
AnyUInt::U8(n) => PlutusData::BigInt(BigInt::Int(Int::from(*n as i64))),
|
||||
AnyUInt::U16(n) => PlutusData::BigInt(BigInt::Int(Int::from(*n as i64))),
|
||||
AnyUInt::U32(n) => PlutusData::BigInt(BigInt::Int(Int::from(*n as i64))),
|
||||
AnyUInt::U64(n) => PlutusData::BigInt(BigInt::Int(Int::from(*n as i64))),
|
||||
AnyUInt::MajorByte(n) => PlutusData::BigInt(BigInt::Int(Int::from(*n as i64))), // is this correct? I don't know exactly what is does
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Int {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BigInt(BigInt::Int(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for BigInt {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BigInt(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for i64 {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BigInt(BigInt::Int(Int::from(*self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for u64 {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
PlutusData::BigInt(BigInt::Int(Int::from(*self as i64)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Value {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
Value::Coin(coin) => PlutusData::Map(KeyValuePairs::Def(vec![(
|
||||
Bytes::from(vec![]).to_plutus_data(),
|
||||
PlutusData::Map(KeyValuePairs::Def(vec![(
|
||||
AssetName::from(vec![]).to_plutus_data(),
|
||||
coin.to_plutus_data(),
|
||||
)])),
|
||||
)])),
|
||||
Value::Multiasset(coin, multiassets) => {
|
||||
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![(
|
||||
Bytes::from(vec![]).to_plutus_data(),
|
||||
PlutusData::Map(KeyValuePairs::Def(vec![(
|
||||
AssetName::from(vec![]).to_plutus_data(),
|
||||
coin.to_plutus_data(),
|
||||
)])),
|
||||
)];
|
||||
|
||||
for (policy_id, assets) in multiassets.iter() {
|
||||
let mut assets_vec = vec![];
|
||||
for (asset, amount) in assets.iter() {
|
||||
assets_vec.push((asset.to_plutus_data(), amount.to_plutus_data()));
|
||||
}
|
||||
data_vec.push((
|
||||
policy_id.to_plutus_data(),
|
||||
PlutusData::Map(KeyValuePairs::Def(assets_vec)),
|
||||
));
|
||||
}
|
||||
|
||||
PlutusData::Map(KeyValuePairs::Def(data_vec))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for MintValue {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
let mut data_vec: Vec<(PlutusData, PlutusData)> = vec![(
|
||||
Bytes::from(vec![]).to_plutus_data(),
|
||||
PlutusData::Map(KeyValuePairs::Def(vec![(
|
||||
AssetName::from(vec![]).to_plutus_data(),
|
||||
0_i64.to_plutus_data(),
|
||||
)])),
|
||||
)];
|
||||
|
||||
for (policy_id, assets) in self.mint_value.iter() {
|
||||
let mut assets_vec = vec![];
|
||||
for (asset, amount) in assets.iter() {
|
||||
assets_vec.push((asset.to_plutus_data(), amount.to_plutus_data()));
|
||||
}
|
||||
data_vec.push((
|
||||
policy_id.to_plutus_data(),
|
||||
PlutusData::Map(KeyValuePairs::Def(assets_vec)),
|
||||
));
|
||||
}
|
||||
|
||||
PlutusData::Map(KeyValuePairs::Def(data_vec))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for ScriptRef {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match &self.0 {
|
||||
Script::NativeScript(native_script) => native_script.compute_hash().to_plutus_data(),
|
||||
Script::PlutusV1Script(plutus_v1) => plutus_v1.compute_hash().to_plutus_data(),
|
||||
Script::PlutusV2Script(plutus_v2) => plutus_v2.compute_hash().to_plutus_data(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for TxOut {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
TxOut::V1(output) => match output {
|
||||
TransactionOutput::Legacy(legacy_output) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
Address::from_bytes(&legacy_output.address)
|
||||
.unwrap()
|
||||
.to_plutus_data(),
|
||||
legacy_output.amount.to_plutus_data(),
|
||||
legacy_output.datum_hash.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
Address::from_bytes(&post_alonzo_output.address)
|
||||
.unwrap()
|
||||
.to_plutus_data(),
|
||||
post_alonzo_output.value.to_plutus_data(),
|
||||
match post_alonzo_output.datum_option {
|
||||
Some(DatumOption::Hash(hash)) => Some(hash).to_plutus_data(),
|
||||
_ => None::<DatumOption>.to_plutus_data(),
|
||||
},
|
||||
],
|
||||
),
|
||||
},
|
||||
TxOut::V2(output) => match output {
|
||||
TransactionOutput::Legacy(legacy_output) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
Address::from_bytes(&legacy_output.address)
|
||||
.unwrap()
|
||||
.to_plutus_data(),
|
||||
legacy_output.amount.to_plutus_data(),
|
||||
match legacy_output.datum_hash {
|
||||
Some(hash) => wrap_with_constr(1, hash.to_plutus_data()),
|
||||
_ => empty_constr(0),
|
||||
},
|
||||
None::<ScriptRef>.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
TransactionOutput::PostAlonzo(post_alonzo_output) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
Address::from_bytes(&post_alonzo_output.address)
|
||||
.unwrap()
|
||||
.to_plutus_data(),
|
||||
post_alonzo_output.value.to_plutus_data(),
|
||||
post_alonzo_output.datum_option.to_plutus_data(),
|
||||
post_alonzo_output.script_ref.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for StakeCredential {
|
||||
// Stake Credential needs to be wrapped inside another Constr, because we could have either a StakingHash or a StakingPtr
|
||||
// The current implementation of StakeCredential doesn't capture the credential of a Pointer address.
|
||||
// So a StakeCredential for a Pointer address needs to be converted separately
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
StakeCredential::AddrKeyhash(addr_keyhas) => {
|
||||
wrap_with_constr(0, wrap_with_constr(0, addr_keyhas.to_plutus_data()))
|
||||
}
|
||||
StakeCredential::Scripthash(script_hash) => {
|
||||
wrap_with_constr(0, wrap_with_constr(1, script_hash.to_plutus_data()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Certificate {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
Certificate::StakeRegistration(stake_credential) => {
|
||||
wrap_with_constr(0, stake_credential.to_plutus_data())
|
||||
}
|
||||
Certificate::StakeDeregistration(stake_credential) => {
|
||||
wrap_with_constr(1, stake_credential.to_plutus_data())
|
||||
}
|
||||
Certificate::StakeDelegation(stake_credential, pool_keyhash) => {
|
||||
wrap_multiple_with_constr(
|
||||
2,
|
||||
vec![
|
||||
stake_credential.to_plutus_data(),
|
||||
pool_keyhash.to_plutus_data(),
|
||||
],
|
||||
)
|
||||
}
|
||||
Certificate::PoolRegistration {
|
||||
operator,
|
||||
vrf_keyhash,
|
||||
pledge: _,
|
||||
cost: _,
|
||||
margin: _,
|
||||
reward_account: _,
|
||||
pool_owners: _,
|
||||
relays: _,
|
||||
pool_metadata: _,
|
||||
} => wrap_multiple_with_constr(
|
||||
3,
|
||||
vec![operator.to_plutus_data(), vrf_keyhash.to_plutus_data()],
|
||||
),
|
||||
Certificate::PoolRetirement(pool_keyhash, epoch) => wrap_multiple_with_constr(
|
||||
4,
|
||||
vec![pool_keyhash.to_plutus_data(), epoch.to_plutus_data()],
|
||||
),
|
||||
Certificate::GenesisKeyDelegation(_, _, _) => empty_constr(5),
|
||||
Certificate::MoveInstantaneousRewardsCert(_) => empty_constr(6),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for Redeemer {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
self.data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for PlutusData {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for TimeRange {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match &self {
|
||||
TimeRange {
|
||||
lower_bound: Some(lower_bound),
|
||||
upper_bound: None,
|
||||
} => {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// LowerBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// Finite
|
||||
wrap_with_constr(1, lower_bound.to_plutus_data()),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
), //UpperBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// PosInf
|
||||
empty_constr(2),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
TimeRange {
|
||||
lower_bound: None,
|
||||
upper_bound: Some(upper_bound),
|
||||
} => {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// LowerBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// NegInf
|
||||
empty_constr(0),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
//UpperBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// Finite
|
||||
wrap_with_constr(1, upper_bound.to_plutus_data()),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
TimeRange {
|
||||
lower_bound: Some(lower_bound),
|
||||
upper_bound: Some(upper_bound),
|
||||
} => {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// LowerBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// Finite
|
||||
wrap_with_constr(1, lower_bound.to_plutus_data()),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
//UpperBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// Finite
|
||||
wrap_with_constr(1, upper_bound.to_plutus_data()),
|
||||
// Closure
|
||||
false.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
TimeRange {
|
||||
lower_bound: None,
|
||||
upper_bound: None,
|
||||
} => {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// LowerBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// NegInf
|
||||
empty_constr(0),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
//UpperBound
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
// PosInf
|
||||
empty_constr(2),
|
||||
// Closure
|
||||
true.to_plutus_data(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for TxInInfo {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
self.out_ref.to_plutus_data(),
|
||||
self.resolved.to_plutus_data(),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for ScriptPurpose {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
ScriptPurpose::Minting(policy_id) => wrap_with_constr(0, policy_id.to_plutus_data()),
|
||||
ScriptPurpose::Spending(out_ref) => wrap_with_constr(1, out_ref.to_plutus_data()),
|
||||
ScriptPurpose::Rewarding(stake_credential) => {
|
||||
wrap_with_constr(2, stake_credential.to_plutus_data())
|
||||
}
|
||||
ScriptPurpose::Certifying(dcert) => wrap_with_constr(3, dcert.to_plutus_data()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for TxInfo {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
TxInfo::V1(tx_info) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
tx_info.inputs.to_plutus_data(),
|
||||
tx_info.outputs.to_plutus_data(),
|
||||
tx_info.fee.to_plutus_data(),
|
||||
tx_info.mint.to_plutus_data(),
|
||||
tx_info.dcert.to_plutus_data(),
|
||||
tx_info.wdrl.to_plutus_data(),
|
||||
tx_info.valid_range.to_plutus_data(),
|
||||
tx_info.signatories.to_plutus_data(),
|
||||
tx_info.data.to_plutus_data(),
|
||||
wrap_with_constr(0, tx_info.id.to_plutus_data()),
|
||||
],
|
||||
),
|
||||
TxInfo::V2(tx_info) => wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![
|
||||
tx_info.inputs.to_plutus_data(),
|
||||
tx_info.reference_inputs.to_plutus_data(),
|
||||
tx_info.outputs.to_plutus_data(),
|
||||
tx_info.fee.to_plutus_data(),
|
||||
tx_info.mint.to_plutus_data(),
|
||||
tx_info.dcert.to_plutus_data(),
|
||||
tx_info.wdrl.to_plutus_data(),
|
||||
tx_info.valid_range.to_plutus_data(),
|
||||
tx_info.signatories.to_plutus_data(),
|
||||
tx_info.redeemers.to_plutus_data(),
|
||||
tx_info.data.to_plutus_data(),
|
||||
wrap_with_constr(0, tx_info.id.to_plutus_data()),
|
||||
],
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for ScriptContext {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
wrap_multiple_with_constr(
|
||||
0,
|
||||
vec![self.tx_info.to_plutus_data(), self.purpose.to_plutus_data()],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlutusData for bool {
|
||||
fn to_plutus_data(&self) -> PlutusData {
|
||||
match self {
|
||||
false => empty_constr(0),
|
||||
true => empty_constr(1),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue