move utils to uplc and break up

This commit is contained in:
Kasey White 2022-09-16 04:00:29 -04:00
parent ddf3cdb6ec
commit 3f27bd9f13
9 changed files with 1596 additions and 1551 deletions

5
Cargo.lock generated
View File

@ -676,14 +676,19 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
name = "uplc"
version = "0.0.12"
dependencies = [
"anyhow",
"cryptoxide",
"flat-rs",
"hex",
"pallas-addresses",
"pallas-codec",
"pallas-crypto",
"pallas-primitives",
"pallas-traverse",
"peg",
"pretty",
"proptest",
"serde_json",
"thiserror",
]

View File

@ -1,7 +1,6 @@
use std::{
fmt::Write as _,
fs::{self, File},
io::BufReader,
fs::{self},
};
use pallas_traverse::{Era, MultiEraTx};
@ -12,12 +11,9 @@ use uplc::{
};
mod args;
mod utils;
use args::{Args, TxCommand, UplcCommand};
use crate::args::ResolvedInputOld;
fn main() -> anyhow::Result<()> {
let args = Args::default();
@ -26,7 +22,7 @@ fn main() -> anyhow::Result<()> {
TxCommand::Simulate {
input,
cbor,
resolved_inputs,
resolved_inputs: _,
} => {
let tx_bytes = if cbor {
fs::read(input)?
@ -40,6 +36,8 @@ fn main() -> anyhow::Result<()> {
.or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes))?;
println!("Simulating: {}", tx.hash());
}
},
Args::Uplc(uplc_cmd) => match uplc_cmd {

File diff suppressed because one or more lines are too long

View File

@ -16,11 +16,16 @@ exclude = ["test_data/*"]
cryptoxide = "0.4.2"
flat-rs = { path = "../flat", version = "0.0.10" }
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"
peg = "0.8.0"
pretty = "0.11.3"
thiserror = "1.0.31"
anyhow = "1.0.57"
serde_json = "1.0.85"
[dev-dependencies]
hex = "0.4.3"

View File

@ -6,6 +6,7 @@ pub mod machine;
pub mod parser;
mod pretty;
pub mod program_builder;
pub mod transaction_eval;
pub use pallas_primitives::alonzo::PlutusData;
pub type Error = Box<dyn std::error::Error>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,85 @@
use pallas_codec::utils::KeyValuePairs;
use pallas_crypto::hash::Hash;
use pallas_primitives::babbage::{
AddrKeyhash, Certificate, Coin, DatumHash, Mint, PlutusData, PolicyId, Redeemer, RewardAccount,
StakeCredential, TransactionInput, TransactionOutput, Value, Withdrawals,
};
#[derive(Debug, PartialEq, Clone)]
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: Mint,
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: Mint,
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, Clone)]
pub struct TimeRange {
pub lower_bound: Option<u64>,
pub upper_bound: Option<u64>,
}
pub struct SlotConfig {
pub slot_length: u64,
pub zero_time: u64,
}

View File

@ -0,0 +1,583 @@
use pallas_addresses::{Address, ShelleyDelegationPart, ShelleyPaymentPart};
use pallas_primitives::babbage::{AssetName, BigInt, Constr, PlutusData, ScriptRef};
use pallas_codec::utils::{AnyUInt, Bytes, Int, KeyValuePairs};
use pallas_crypto::hash::Hash;
use pallas_primitives::babbage::{
Certificate, DatumOption, PolicyId, Redeemer, Script, StakeCredential, TransactionInput,
TransactionOutput, Value,
};
use pallas_traverse::ComputeHash;
use std::vec;
use super::script_context::{TxOut, TimeRange, TxInInfo, ScriptPurpose, TxInfo, ScriptContext};
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;
}
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![(
PolicyId::from([0; 28]).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![(
PolicyId::from([0; 28]).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 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),
}
}
}

View File

@ -0,0 +1,715 @@
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, DatumHash, DatumOption, ExUnits, Mint, MintedTx,
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::ToPlutusData,
};
fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> u64 {
let ms_after_begin = 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)]
enum ScriptVersion {
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>,
}
pub fn get_tx_in_info_v1(
inputs: &[TransactionInput],
utxos: &[ResolvedInput],
) -> anyhow::Result<Vec<TxInInfo>> {
let result = inputs
.iter()
.map(|input| {
let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
Some(u) => u,
None => unreachable!("Resolved input not found."),
};
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(_) => unreachable!("Byron addresses not supported in Plutus."),
Address::Stake(_) => {
unreachable!("This is impossible. A stake address cannot lock a UTxO.")
}
_ => {}
}
match &utxo.output {
TransactionOutput::Legacy(_) => {}
TransactionOutput::PostAlonzo(output) => {
if let Some(DatumOption::Data(_)) = output.datum_option {
unreachable!("Inline datum not allowed in PlutusV1.")
}
if output.script_ref.is_some() {
unreachable!("Reference scripts not allowed in PlutusV1.")
}
}
}
TxInInfo {
out_ref: utxo.input.clone(),
resolved: TxOut::V1(utxo.output.clone()),
}
})
.collect::<Vec<TxInInfo>>();
Ok(result)
}
fn get_tx_in_info_v2(
inputs: &[TransactionInput],
utxos: &[ResolvedInput],
) -> anyhow::Result<Vec<TxInInfo>> {
let result = inputs
.iter()
.map(|input| {
let utxo = match utxos.iter().find(|utxo| utxo.input == *input) {
Some(u) => u,
None => unreachable!("Resolved input not found."),
};
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(_) => unreachable!("Byron addresses not supported in Plutus."),
Address::Stake(_) => {
unreachable!("This is impossible. A stake address cannot lock a UTxO.")
}
_ => {}
}
TxInInfo {
out_ref: utxo.input.clone(),
resolved: TxOut::V2(utxo.output.clone()),
}
})
.collect::<Vec<TxInInfo>>();
Ok(result)
}
fn get_script_purpose(
redeemer: &Redeemer,
inputs: &[TransactionInput],
mint: &Option<Mint>,
dcert: &Option<Vec<Certificate>>,
wdrl: &Option<Withdrawals>,
) -> anyhow::Result<ScriptPurpose> {
// 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 => unreachable!("Script purpose not found for redeemer."),
}
}
RedeemerTag::Spend => {
// sort lexical by tx_hash and index
let mut inputs = inputs.to_vec();
// is this correct? Does this sort lexical from low to high? maybe get Ordering into pallas for TransactionInput?
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,
},
);
match inputs.get(index as usize) {
Some(input) => Ok(ScriptPurpose::Spending(input.clone())),
None => unreachable!("Script purpose not found for redeemer."),
}
}
RedeemerTag::Reward => {
// sort lexical by reward account
let mut reward_accounts = wdrl
.as_ref()
.unwrap_or(&KeyValuePairs::Indef(vec![]))
.iter()
.map(|(policy_id, _)| policy_id.clone())
.collect::<Vec<RewardAccount>>();
reward_accounts.sort();
let reward_account = match reward_accounts.get(index as usize) {
Some(ra) => ra.clone(),
None => unreachable!("Script purpose not found for redeemer."),
};
let addresss = Address::from_bytes(&reward_account)?;
let credential = match addresss {
Address::Stake(stake_address) => match stake_address.payload() {
StakePayload::Script(script_hash) => {
StakeCredential::Scripthash(*script_hash)
}
StakePayload::Stake(_) => {
unreachable!(
"This is impossible. A key hash cannot be the hash of a script."
);
}
},
_ => unreachable!(
"This is impossible. Only shelley reward addresses can be a part of withdrawals."
),
};
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 => unreachable!("Script purpose not found for redeemer."),
}
}
}
}
fn get_tx_info_v1(
tx: &MintedTx,
utxos: &[ResolvedInput],
slot_config: &SlotConfig,
) -> anyhow::Result<TxInfo> {
let body = tx.transaction_body.clone();
if body.reference_inputs.is_some() {
unreachable!("Reference inputs not allowed in PlutusV1.")
}
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,
dcert,
wdrl,
valid_range,
signatories,
data,
id,
}))
}
fn get_tx_info_v2(
tx: &MintedTx,
utxos: &[ResolvedInput],
slot_config: &SlotConfig,
) -> anyhow::Result<TxInfo> {
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,
dcert,
wdrl,
valid_range,
signatories,
redeemers,
data,
id,
}))
}
fn get_execution_purpose(
utxos: &[ResolvedInput],
script_purpose: &ScriptPurpose,
lookup_table: &DataLookupTable,
) -> ExecutionPurpose {
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 => unreachable!("Missing required scripts.")
};
ExecutionPurpose::NoDatum(script)
}
ScriptPurpose::Spending(out_ref) => {
let utxo = utxos.iter().find(|utxo| utxo.input == *out_ref).unwrap();
match &utxo.output {
TransactionOutput::Legacy(output) => {
let address = Address::from_bytes(&output.address).unwrap();
match address {
Address::Shelley(shelley_address) => {
let script = match lookup_table
.scripts
.get(shelley_address.payment().as_hash()) {
Some(s) => s.clone(),
None => unreachable!("Missing required scripts.")
};
let datum = match lookup_table.datum.get(&output.datum_hash.unwrap_or_else(|| unreachable!("Missing datum hash in input."))) {
Some(d) => d.clone(),
None => unreachable!("Missing datum in witness set.")
};
ExecutionPurpose::WithDatum(script, datum)
}
_ => unreachable!(
"This is impossible. Only shelley addresses can contain a script hash."
),
}
}
TransactionOutput::PostAlonzo(output) => {
let address = Address::from_bytes(&output.address).unwrap();
match address {
Address::Shelley(shelley_address) => {
let script = match lookup_table
.scripts
.get(shelley_address.payment().as_hash()) {
Some(s) => s.clone(),
None => unreachable!("Missing required scripts.")
};
let datum = match &output.datum_option {
Some(DatumOption::Hash(hash)) => {
lookup_table.datum.get(hash).unwrap().clone()
}
Some(DatumOption::Data(data)) => data.0.clone(),
_ => unreachable!( "Missing datum hash or inline datum in input."),
};
ExecutionPurpose::WithDatum(script, datum)
}
_ => unreachable!(
"This is impossible. Only shelley addresses can contain a script hash."
),
}
}
}
}
ScriptPurpose::Rewarding(stake_credential) => {
let script_hash = match stake_credential {
StakeCredential::Scripthash(hash) => *hash,
_ => unreachable!("This is impossible. A key hash cannot be the hash of a script."),
};
let script = match lookup_table.scripts.get(&script_hash) {
Some(s) => s.clone(),
None => unreachable!("Missing required scripts.")
};
ExecutionPurpose::NoDatum(script)
}
ScriptPurpose::Certifying(cert) => match cert {
// StakeRegistration doesn't require a witness from a stake key/script. So I assume it doesn't need to be handled in Plutus either?
Certificate::StakeDeregistration(stake_credential) => {
let script_hash = match stake_credential {
StakeCredential::Scripthash(hash) => *hash,
_ => unreachable!(
"This is impossible. A key hash cannot be the hash of a script."
),
};
let script = match lookup_table.scripts.get(&script_hash) {
Some(s) => s.clone(),
None => unreachable!("Missing required scripts.")
};
ExecutionPurpose::NoDatum(script)
}
Certificate::StakeDelegation(stake_credential, _) => {
let script_hash = match stake_credential {
StakeCredential::Scripthash(hash) => *hash,
_ => unreachable!(
"This is impossible. A key hash cannot be the hash of a script."
),
};
let script = match lookup_table.scripts.get(&script_hash) {
Some(s) => s.clone(),
None => unreachable!("Missing required scripts.")
};
ExecutionPurpose::NoDatum(script)
}
_ => unreachable!("This is impossible. Only stake deregistration and stake delegation are valid script purposes."),
},
}
}
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_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_v1_witnesses.iter() {
scripts.insert(script.compute_hash(), ScriptVersion::V1(script.clone()));
// TODO: fix hashing bug in pallas
}
for script in scripts_v2_witnesses.iter() {
scripts.insert(script.compute_hash(), ScriptVersion::V2(script.clone()));
// TODO: fix hashing bug in pallas
}
// 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::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,
) -> anyhow::Result<Redeemer> {
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 result = program
.apply_data(datum)
.apply_data(redeemer.data.clone())
.apply_data(script_context.to_plutus_data())
.eval();
match result.0 {
Ok(_) => {}
Err(_err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message
}
let new_redeemer = Redeemer {
tag: redeemer.tag.clone(),
index: redeemer.index,
data: redeemer.data.clone(),
ex_units: ExUnits {
mem: (ExBudget::default().mem - result.1.mem) as u32,
steps: (ExBudget::default().cpu - result.1.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 result = program
.apply_data(datum)
.apply_data(redeemer.data.clone())
.apply_data(script_context.to_plutus_data())
.eval();
match result.0 {
Ok(_) => {}
Err(_err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message
}
let new_redeemer = Redeemer {
tag: redeemer.tag.clone(),
index: redeemer.index,
data: redeemer.data.clone(),
ex_units: ExUnits {
mem: (ExBudget::default().mem - result.1.mem) as u32,
steps: (ExBudget::default().cpu - result.1.cpu) as u64,
},
};
Ok(new_redeemer)
}
},
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 result = program
.apply_data(redeemer.data.clone())
.apply_data(script_context.to_plutus_data())
.eval();
match result.0 {
Ok(_) => {}
Err(_err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message
}
let new_redeemer = Redeemer {
tag: redeemer.tag.clone(),
index: redeemer.index,
data: redeemer.data.clone(),
ex_units: ExUnits {
mem: (ExBudget::default().mem - result.1.mem) as u32,
steps: (ExBudget::default().cpu - result.1.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 result = program
.apply_data(redeemer.data.clone())
.apply_data(script_context.to_plutus_data())
.eval();
match result.0 {
Ok(_) => {}
Err(_err) => unreachable!("Error in Plutus core."), // TODO: Add the actual error message
}
let new_redeemer = Redeemer {
tag: redeemer.tag.clone(),
index: redeemer.index,
data: redeemer.data.clone(),
ex_units: ExUnits {
mem: (ExBudget::default().mem - result.1.mem) as u32,
steps: (ExBudget::default().cpu - result.1.cpu) as u64,
},
};
Ok(new_redeemer)
}
},
}
}