Further remove todos for v3, and reduce duplication in transaction evaluation
This commit is contained in:
parent
ff4a480242
commit
445ffc483d
|
@ -4,14 +4,13 @@ use crate::{
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use eval::get_script_and_datum_lookup_table;
|
|
||||||
use pallas_primitives::{
|
use pallas_primitives::{
|
||||||
conway::{CostMdls, MintedTx, Redeemer, TransactionInput, TransactionOutput},
|
conway::{CostMdls, MintedTx, Redeemer, TransactionInput, TransactionOutput},
|
||||||
Fragment,
|
Fragment,
|
||||||
};
|
};
|
||||||
use pallas_traverse::{Era, MultiEraTx};
|
use pallas_traverse::{Era, MultiEraTx};
|
||||||
pub use phase_one::eval_phase_one;
|
pub use phase_one::eval_phase_one;
|
||||||
pub use script_context::{ResolvedInput, SlotConfig};
|
pub use script_context::{DataLookupTable, ResolvedInput, SlotConfig};
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
@ -37,7 +36,7 @@ pub fn eval_phase_two(
|
||||||
) -> Result<Vec<Redeemer>, Error> {
|
) -> Result<Vec<Redeemer>, Error> {
|
||||||
let redeemers = tx.transaction_witness_set.redeemer.as_ref();
|
let redeemers = tx.transaction_witness_set.redeemer.as_ref();
|
||||||
|
|
||||||
let lookup_table = get_script_and_datum_lookup_table(tx, utxos);
|
let lookup_table = DataLookupTable::from_transaction(tx, utxos);
|
||||||
|
|
||||||
if run_phase_one {
|
if run_phase_one {
|
||||||
// subset of phase 1 check on redeemers and scripts
|
// subset of phase 1 check on redeemers and scripts
|
||||||
|
@ -123,9 +122,6 @@ pub fn eval_phase_two_raw(
|
||||||
};
|
};
|
||||||
|
|
||||||
match multi_era_tx {
|
match multi_era_tx {
|
||||||
MultiEraTx::Babbage(_) => {
|
|
||||||
todo!("convert Babbage's tx into Conway's")
|
|
||||||
}
|
|
||||||
MultiEraTx::Conway(tx) => {
|
MultiEraTx::Conway(tx) => {
|
||||||
match eval_phase_two(
|
match eval_phase_two(
|
||||||
&tx,
|
&tx,
|
||||||
|
@ -143,7 +139,12 @@ pub fn eval_phase_two_raw(
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => todo!("Wrong era. Please use a more recent transaction format"),
|
_ => unimplemented!(
|
||||||
|
r#"The transaction is serialized in an old era format. Because we're slightly lazy to
|
||||||
|
maintain backward compatibility with every possible transaction format AND, because
|
||||||
|
those formats are mostly forward-compatible, you are kindly expected to provide a
|
||||||
|
transaction in a format suitable for the Conway era."#
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,10 @@ pub enum Error {
|
||||||
ExtraneousRedeemer,
|
ExtraneousRedeemer,
|
||||||
#[error("Resolved Input not found.")]
|
#[error("Resolved Input not found.")]
|
||||||
ResolvedInputNotFound(TransactionInput),
|
ResolvedInputNotFound(TransactionInput),
|
||||||
#[error("A key hash cannot be the hash of a script.")]
|
#[error("Redeemer points to a non-script withdrawal.")]
|
||||||
ScriptKeyHash,
|
NonScriptWithdrawal,
|
||||||
|
#[error("Stake credential points to a non-script withdrawal.")]
|
||||||
|
NonScriptStakeCredential,
|
||||||
#[error("Cost model not found for language: {:?}.", .0)]
|
#[error("Cost model not found for language: {:?}.", .0)]
|
||||||
CostModelNotFound(Language),
|
CostModelNotFound(Language),
|
||||||
#[error("Wrong era, Please use Babbage or Alonzo: {0}")]
|
#[error("Wrong era, Please use Babbage or Alonzo: {0}")]
|
||||||
|
@ -49,14 +51,16 @@ pub enum Error {
|
||||||
MissingRequiredScript { hash: String },
|
MissingRequiredScript { hash: String },
|
||||||
#[error("Missing required inline datum or datum hash in script input.")]
|
#[error("Missing required inline datum or datum hash in script input.")]
|
||||||
MissingRequiredInlineDatumOrHash,
|
MissingRequiredInlineDatumOrHash,
|
||||||
#[error("Only stake deregistration and delegation are valid certificate script purposes.")]
|
#[error("Redeemer points to an unsupported certificate type.")]
|
||||||
OnlyStakeDeregAndDelegAllowed,
|
UnsupportedCertificateType,
|
||||||
#[error("Redeemer ({}, {}): {}", tag, index, err)]
|
#[error("Redeemer ({}, {}): {}", tag, index, err)]
|
||||||
RedeemerError {
|
RedeemerError {
|
||||||
tag: String,
|
tag: String,
|
||||||
index: u32,
|
index: u32,
|
||||||
err: Box<Error>,
|
err: Box<Error>,
|
||||||
},
|
},
|
||||||
|
#[error("Missing script for redeemer")]
|
||||||
|
MissingScriptForRedeemer,
|
||||||
#[error("Failed to apply parameters to Plutus script.")]
|
#[error("Failed to apply parameters to Plutus script.")]
|
||||||
ApplyParamsError,
|
ApplyParamsError,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,391 +1,48 @@
|
||||||
use super::{
|
use super::{
|
||||||
script_context::{ResolvedInput, ScriptContext, ScriptPurpose, SlotConfig, TxInfo},
|
script_context::{find_script, ResolvedInput, ScriptContext, SlotConfig, TxInfo},
|
||||||
to_plutus_data::ToPlutusData,
|
to_plutus_data::ToPlutusData,
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{FakeNamedDeBruijn, NamedDeBruijn, Program},
|
ast::{FakeNamedDeBruijn, NamedDeBruijn, Program},
|
||||||
machine::cost_model::ExBudget,
|
machine::cost_model::ExBudget,
|
||||||
tx::script_context::{TxInfoV1, TxInfoV2, TxInfoV3},
|
tx::script_context::{DataLookupTable, ScriptVersion, TxInfoV1, TxInfoV2},
|
||||||
PlutusData,
|
PlutusData,
|
||||||
};
|
};
|
||||||
use pallas_addresses::{Address, ScriptHash, StakePayload};
|
use pallas_codec::utils::Bytes;
|
||||||
use pallas_codec::utils::{Bytes, NonEmptyKeyValuePairs, NonEmptySet};
|
|
||||||
use pallas_crypto::hash::Hash;
|
|
||||||
use pallas_primitives::conway::{
|
use pallas_primitives::conway::{
|
||||||
Certificate, CostMdls, CostModel, DatumHash, DatumOption, ExUnits, Language, Mint, MintedTx,
|
CostMdls, CostModel, ExUnits, Language, MintedTx, Redeemer, RedeemerTag,
|
||||||
NativeScript, PlutusV1Script, PlutusV2Script, PlutusV3Script, PolicyId, PseudoScript, Redeemer,
|
|
||||||
RedeemerTag, RewardAccount, StakeCredential, TransactionInput, TransactionOutput, Withdrawals,
|
|
||||||
};
|
};
|
||||||
use pallas_traverse::{ComputeHash, OriginalHash};
|
|
||||||
use std::{collections::HashMap, convert::TryInto, vec};
|
|
||||||
|
|
||||||
fn redeemer_tag_to_string(redeemer_tag: &RedeemerTag) -> String {
|
pub fn eval_redeemer(
|
||||||
match redeemer_tag {
|
|
||||||
RedeemerTag::Spend => "Spend".to_string(),
|
|
||||||
RedeemerTag::Mint => "Mint".to_string(),
|
|
||||||
RedeemerTag::Cert => "Cert".to_string(),
|
|
||||||
RedeemerTag::Reward => "Reward".to_string(),
|
|
||||||
tag => todo!("redeemer_tag_to_string for {tag:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum ScriptVersion {
|
|
||||||
Native(NativeScript),
|
|
||||||
V1(PlutusV1Script),
|
|
||||||
V2(PlutusV2Script),
|
|
||||||
V3(PlutusV3Script),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_script_purpose(
|
|
||||||
redeemer: &Redeemer,
|
|
||||||
inputs: &[TransactionInput],
|
|
||||||
mint: &Option<Mint>,
|
|
||||||
dcert: &Option<NonEmptySet<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;
|
|
||||||
let index = redeemer.index;
|
|
||||||
match tag {
|
|
||||||
RedeemerTag::Mint => {
|
|
||||||
// sort lexical by policy id
|
|
||||||
let mut policy_ids = mint
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&NonEmptyKeyValuePairs::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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RedeemerTag::Reward => {
|
|
||||||
// sort lexical by reward account
|
|
||||||
let mut reward_accounts = wdrl
|
|
||||||
.as_ref()
|
|
||||||
.unwrap_or(&NonEmptyKeyValuePairs::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),
|
|
||||||
};
|
|
||||||
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_deref().unwrap_or(&vec![]).get(index as usize) {
|
|
||||||
Some(cert) => Ok(ScriptPurpose::Certifying(cert.clone())),
|
|
||||||
None => Err(Error::ExtraneousRedeemer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tag => todo!("get_script_purpose for {tag:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(out_ref.clone())),
|
|
||||||
};
|
|
||||||
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,
|
tx: &MintedTx,
|
||||||
utxos: &[ResolvedInput],
|
utxos: &[ResolvedInput],
|
||||||
) -> DataLookupTable {
|
slot_config: &SlotConfig,
|
||||||
let mut datum = HashMap::new();
|
redeemer: &Redeemer,
|
||||||
let mut scripts = HashMap::new();
|
lookup_table: &DataLookupTable,
|
||||||
|
cost_mdls_opt: Option<&CostMdls>,
|
||||||
// discovery in witness set
|
initial_budget: &ExBudget,
|
||||||
|
) -> Result<Redeemer, Error> {
|
||||||
let plutus_data_witnesses = tx
|
fn do_eval_redeemer(
|
||||||
.transaction_witness_set
|
|
||||||
.plutus_data
|
|
||||||
.clone()
|
|
||||||
.map(|s| s.to_vec())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let scripts_native_witnesses = tx
|
|
||||||
.transaction_witness_set
|
|
||||||
.native_script
|
|
||||||
.clone()
|
|
||||||
.map(|s| s.to_vec())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let scripts_v1_witnesses = tx
|
|
||||||
.transaction_witness_set
|
|
||||||
.plutus_v1_script
|
|
||||||
.clone()
|
|
||||||
.map(|s| s.to_vec())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let scripts_v2_witnesses = tx
|
|
||||||
.transaction_witness_set
|
|
||||||
.plutus_v2_script
|
|
||||||
.clone()
|
|
||||||
.map(|s| s.to_vec())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let scripts_v3_witnesses = tx
|
|
||||||
.transaction_witness_set
|
|
||||||
.plutus_v3_script
|
|
||||||
.clone()
|
|
||||||
.map(|s| s.to_vec())
|
|
||||||
.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().unwrap()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for script in scripts_v3_witnesses.iter() {
|
|
||||||
scripts.insert(script.compute_hash(), ScriptVersion::V3(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 {
|
|
||||||
PseudoScript::NativeScript(ns) => {
|
|
||||||
scripts.insert(ns.compute_hash(), ScriptVersion::Native(ns.clone()));
|
|
||||||
}
|
|
||||||
PseudoScript::PlutusV1Script(v1) => {
|
|
||||||
scripts.insert(v1.compute_hash(), ScriptVersion::V1(v1.clone()));
|
|
||||||
}
|
|
||||||
PseudoScript::PlutusV2Script(v2) => {
|
|
||||||
scripts.insert(v2.compute_hash(), ScriptVersion::V2(v2.clone()));
|
|
||||||
}
|
|
||||||
PseudoScript::PlutusV3Script(v3) => {
|
|
||||||
scripts.insert(v3.compute_hash(), ScriptVersion::V3(v3.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DataLookupTable { datum, scripts }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mk_redeemer_with_datum(
|
|
||||||
cost_mdl_opt: Option<&CostModel>,
|
cost_mdl_opt: Option<&CostModel>,
|
||||||
initial_budget: &ExBudget,
|
initial_budget: &ExBudget,
|
||||||
lang: &Language,
|
lang: &Language,
|
||||||
datum: PlutusData,
|
datum: Option<PlutusData>,
|
||||||
(redeemer, purpose): (&Redeemer, ScriptPurpose),
|
redeemer: &Redeemer,
|
||||||
tx_info: TxInfo,
|
tx_info: TxInfo,
|
||||||
program: Program<NamedDeBruijn>,
|
program: Program<NamedDeBruijn>,
|
||||||
) -> Result<Redeemer, Error> {
|
) -> Result<Redeemer, Error> {
|
||||||
|
let purpose = tx_info
|
||||||
|
.purpose(redeemer)
|
||||||
|
.expect("redeemer's purpose shall be known by this point.");
|
||||||
|
|
||||||
let script_context = ScriptContext { tx_info, purpose };
|
let script_context = ScriptContext { tx_info, purpose };
|
||||||
|
|
||||||
let program = program
|
let program = if let Some(datum) = datum {
|
||||||
.apply_data(datum)
|
program.apply_data(datum)
|
||||||
|
} else {
|
||||||
|
program
|
||||||
|
}
|
||||||
.apply_data(redeemer.data.clone())
|
.apply_data(redeemer.data.clone())
|
||||||
.apply_data(script_context.to_plutus_data());
|
.apply_data(script_context.to_plutus_data());
|
||||||
|
|
||||||
|
@ -414,25 +71,7 @@ fn mk_redeemer_with_datum(
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(new_redeemer)
|
Ok(new_redeemer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_redeemer(
|
|
||||||
tx: &MintedTx,
|
|
||||||
utxos: &[ResolvedInput],
|
|
||||||
slot_config: &SlotConfig,
|
|
||||||
redeemer: &Redeemer,
|
|
||||||
lookup_table: &DataLookupTable,
|
|
||||||
cost_mdls_opt: Option<&CostMdls>,
|
|
||||||
initial_budget: &ExBudget,
|
|
||||||
) -> Result<Redeemer, Error> {
|
|
||||||
let result = || {
|
|
||||||
let purpose = get_script_purpose(
|
|
||||||
redeemer,
|
|
||||||
&tx.transaction_body.inputs,
|
|
||||||
&tx.transaction_body.mint,
|
|
||||||
&tx.transaction_body.certificates,
|
|
||||||
&tx.transaction_body.withdrawals,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let program = |script: Bytes| {
|
let program = |script: Bytes| {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
|
@ -440,29 +79,27 @@ pub fn eval_redeemer(
|
||||||
.map(Into::<Program<NamedDeBruijn>>::into)
|
.map(Into::<Program<NamedDeBruijn>>::into)
|
||||||
};
|
};
|
||||||
|
|
||||||
let execution_purpose: ExecutionPurpose =
|
match find_script(redeemer, tx, utxos, lookup_table)? {
|
||||||
get_execution_purpose(utxos, &purpose, lookup_table)?;
|
(ScriptVersion::Native(_), _) => Err(Error::NativeScriptPhaseTwo),
|
||||||
|
|
||||||
match execution_purpose {
|
(ScriptVersion::V1(script), datum) => do_eval_redeemer(
|
||||||
ExecutionPurpose::WithDatum(script_version, datum) => match script_version {
|
|
||||||
ScriptVersion::V1(script) => mk_redeemer_with_datum(
|
|
||||||
cost_mdls_opt
|
cost_mdls_opt
|
||||||
.map(|cost_mdls| {
|
.map(|cost_mdls| {
|
||||||
cost_mdls
|
cost_mdls
|
||||||
.plutus_v1
|
.plutus_v1
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(Error::CostModelNotFound(Language::PlutusV1))
|
.ok_or(Error::CostModelNotFound(Language::PlutusV2))
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
initial_budget,
|
initial_budget,
|
||||||
&Language::PlutusV1,
|
&Language::PlutusV1,
|
||||||
datum,
|
datum,
|
||||||
(redeemer, purpose),
|
redeemer,
|
||||||
TxInfoV1::from_transaction(tx, utxos, slot_config)?,
|
TxInfoV1::from_transaction(tx, utxos, slot_config)?,
|
||||||
program(script.0)?,
|
program(script.0)?,
|
||||||
),
|
),
|
||||||
|
|
||||||
ScriptVersion::V2(script) => mk_redeemer_with_datum(
|
(ScriptVersion::V2(script), datum) => do_eval_redeemer(
|
||||||
cost_mdls_opt
|
cost_mdls_opt
|
||||||
.map(|cost_mdls| {
|
.map(|cost_mdls| {
|
||||||
cost_mdls
|
cost_mdls
|
||||||
|
@ -474,139 +111,28 @@ pub fn eval_redeemer(
|
||||||
initial_budget,
|
initial_budget,
|
||||||
&Language::PlutusV2,
|
&Language::PlutusV2,
|
||||||
datum,
|
datum,
|
||||||
(redeemer, purpose),
|
redeemer,
|
||||||
TxInfoV2::from_transaction(tx, utxos, slot_config)?,
|
TxInfoV2::from_transaction(tx, utxos, slot_config)?,
|
||||||
program(script.0)?,
|
program(script.0)?,
|
||||||
),
|
),
|
||||||
|
|
||||||
ScriptVersion::V3(script) => mk_redeemer_with_datum(
|
(ScriptVersion::V3(_script), _datum) => todo!(),
|
||||||
cost_mdls_opt
|
|
||||||
.map(|cost_mdls| {
|
|
||||||
cost_mdls
|
|
||||||
.plutus_v3
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(Error::CostModelNotFound(Language::PlutusV3))
|
|
||||||
})
|
|
||||||
.transpose()?,
|
|
||||||
initial_budget,
|
|
||||||
&Language::PlutusV2,
|
|
||||||
datum,
|
|
||||||
(redeemer, purpose),
|
|
||||||
TxInfoV3::from_transaction(tx, utxos, slot_config)?,
|
|
||||||
program(script.0)?,
|
|
||||||
),
|
|
||||||
|
|
||||||
ScriptVersion::Native(_) => Err(Error::NativeScriptPhaseTwo),
|
|
||||||
},
|
|
||||||
ExecutionPurpose::NoDatum(script_version) => match script_version {
|
|
||||||
ScriptVersion::V1(script) => {
|
|
||||||
let tx_info = TxInfoV1::from_transaction(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 mut eval_result = if let Some(cost_mdls) = cost_mdls_opt {
|
|
||||||
let costs = if let Some(costs) = &cost_mdls.plutus_v1 {
|
|
||||||
costs
|
|
||||||
} else {
|
|
||||||
return Err(Error::CostModelNotFound(Language::PlutusV1));
|
|
||||||
};
|
|
||||||
|
|
||||||
program.eval_as(&Language::PlutusV1, costs, Some(initial_budget))
|
|
||||||
} else {
|
|
||||||
program.eval_version(ExBudget::default(), &Language::PlutusV1)
|
|
||||||
};
|
|
||||||
|
|
||||||
let cost = eval_result.cost();
|
|
||||||
let logs = eval_result.logs();
|
|
||||||
|
|
||||||
match eval_result.result() {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => return Err(Error::Machine(err, cost, logs)),
|
|
||||||
}
|
}
|
||||||
|
.map_err(|err| Error::RedeemerError {
|
||||||
let new_redeemer = Redeemer {
|
|
||||||
tag: redeemer.tag,
|
|
||||||
index: redeemer.index,
|
|
||||||
data: redeemer.data.clone(),
|
|
||||||
ex_units: ExUnits {
|
|
||||||
mem: cost.mem as u64,
|
|
||||||
steps: cost.cpu as u64,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(new_redeemer)
|
|
||||||
}
|
|
||||||
ScriptVersion::V2(script) => {
|
|
||||||
let tx_info = TxInfoV2::from_transaction(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 mut eval_result = if let Some(cost_mdls) = cost_mdls_opt {
|
|
||||||
let costs = if let Some(costs) = &cost_mdls.plutus_v2 {
|
|
||||||
costs
|
|
||||||
} else {
|
|
||||||
return Err(Error::CostModelNotFound(Language::PlutusV2));
|
|
||||||
};
|
|
||||||
|
|
||||||
program.eval_as(&Language::PlutusV2, costs, Some(initial_budget))
|
|
||||||
} else {
|
|
||||||
program.eval(ExBudget::default())
|
|
||||||
};
|
|
||||||
|
|
||||||
let cost = eval_result.cost();
|
|
||||||
let logs = eval_result.logs();
|
|
||||||
|
|
||||||
match eval_result.result() {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => return Err(Error::Machine(err, cost, logs)),
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_redeemer = Redeemer {
|
|
||||||
tag: redeemer.tag,
|
|
||||||
index: redeemer.index,
|
|
||||||
data: redeemer.data.clone(),
|
|
||||||
ex_units: ExUnits {
|
|
||||||
mem: cost.mem as u64,
|
|
||||||
steps: cost.cpu as u64,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(new_redeemer)
|
|
||||||
}
|
|
||||||
ScriptVersion::V3(_script) => todo!(),
|
|
||||||
ScriptVersion::Native(_) => Err(Error::NativeScriptPhaseTwo),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match result() {
|
|
||||||
Ok(r) => Ok(r),
|
|
||||||
Err(err) => Err(Error::RedeemerError {
|
|
||||||
tag: redeemer_tag_to_string(&redeemer.tag),
|
tag: redeemer_tag_to_string(&redeemer.tag),
|
||||||
index: redeemer.index,
|
index: redeemer.index,
|
||||||
err: Box::new(err),
|
err: Box::new(err),
|
||||||
}),
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn redeemer_tag_to_string(redeemer_tag: &RedeemerTag) -> String {
|
||||||
|
match redeemer_tag {
|
||||||
|
RedeemerTag::Spend => "Spend",
|
||||||
|
RedeemerTag::Mint => "Mint",
|
||||||
|
RedeemerTag::Reward => "Withdraw",
|
||||||
|
RedeemerTag::Cert => "Publish",
|
||||||
|
RedeemerTag::Propose => "Propose",
|
||||||
|
RedeemerTag::Vote => "Vote",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
error::Error,
|
error::Error,
|
||||||
eval::{DataLookupTable, ScriptVersion},
|
script_context::{DataLookupTable, ResolvedInput, ScriptPurpose, ScriptVersion},
|
||||||
script_context::{ResolvedInput, ScriptPurpose},
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pallas_addresses::{Address, ScriptHash, ShelleyPaymentPart, StakePayload};
|
use pallas_addresses::{Address, ScriptHash, ShelleyPaymentPart, StakePayload};
|
||||||
|
|
|
@ -7,14 +7,14 @@ use pallas_primitives::{
|
||||||
alonzo,
|
alonzo,
|
||||||
conway::{
|
conway::{
|
||||||
AddrKeyhash, Certificate, Coin, DatumHash, DatumOption, Mint, MintedTransactionBody,
|
AddrKeyhash, Certificate, Coin, DatumHash, DatumOption, Mint, MintedTransactionBody,
|
||||||
MintedTransactionOutput, MintedTx, MintedWitnessSet, PlutusData, PolicyId,
|
MintedTransactionOutput, MintedTx, MintedWitnessSet, NativeScript, PlutusData,
|
||||||
PostAlonzoTransactionOutput, PseudoDatumOption, Redeemer, RedeemerTag, RedeemersKey,
|
PlutusV1Script, PlutusV2Script, PlutusV3Script, PolicyId, PostAlonzoTransactionOutput,
|
||||||
RequiredSigners, RewardAccount, StakeCredential, TransactionInput, TransactionOutput,
|
PseudoDatumOption, PseudoScript, Redeemer, RedeemerTag, RedeemersKey, RequiredSigners,
|
||||||
Value,
|
RewardAccount, ScriptHash, StakeCredential, TransactionInput, TransactionOutput, Value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use pallas_traverse::OriginalHash;
|
use pallas_traverse::{ComputeHash, OriginalHash};
|
||||||
use std::{cmp::Ordering, ops::Deref};
|
use std::{cmp::Ordering, collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ResolvedInput {
|
pub struct ResolvedInput {
|
||||||
|
@ -33,6 +33,30 @@ pub enum TxOut {
|
||||||
V2(TransactionOutput),
|
V2(TransactionOutput),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TxOut {
|
||||||
|
pub fn address(&self) -> Address {
|
||||||
|
let address_from_output = |output: &TransactionOutput| match output {
|
||||||
|
TransactionOutput::Legacy(x) => Address::from_bytes(&x.address).unwrap(),
|
||||||
|
TransactionOutput::PostAlonzo(x) => Address::from_bytes(&x.address).unwrap(),
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
TxOut::V1(output) => address_from_output(output),
|
||||||
|
TxOut::V2(output) => address_from_output(output),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn datum(&self) -> Option<DatumOption> {
|
||||||
|
let datum_from_output = |output: &TransactionOutput| match output {
|
||||||
|
TransactionOutput::Legacy(x) => x.datum_hash.map(DatumOption::Hash),
|
||||||
|
TransactionOutput::PostAlonzo(x) => x.datum_option.clone(),
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
TxOut::V1(output) => datum_from_output(output),
|
||||||
|
TxOut::V2(output) => datum_from_output(output),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum ScriptPurpose {
|
pub enum ScriptPurpose {
|
||||||
Minting(PolicyId),
|
Minting(PolicyId),
|
||||||
|
@ -41,17 +65,133 @@ pub enum ScriptPurpose {
|
||||||
Certifying(Certificate),
|
Certifying(Certificate),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum ScriptVersion {
|
||||||
|
Native(NativeScript),
|
||||||
|
V1(PlutusV1Script),
|
||||||
|
V2(PlutusV2Script),
|
||||||
|
V3(PlutusV3Script),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataLookupTable {
|
||||||
|
datum: HashMap<DatumHash, PlutusData>,
|
||||||
|
scripts: HashMap<ScriptHash, ScriptVersion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataLookupTable {
|
||||||
|
pub fn from_transaction(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()
|
||||||
|
.map(|s| s.to_vec())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let scripts_native_witnesses = tx
|
||||||
|
.transaction_witness_set
|
||||||
|
.native_script
|
||||||
|
.clone()
|
||||||
|
.map(|s| s.to_vec())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let scripts_v1_witnesses = tx
|
||||||
|
.transaction_witness_set
|
||||||
|
.plutus_v1_script
|
||||||
|
.clone()
|
||||||
|
.map(|s| s.to_vec())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let scripts_v2_witnesses = tx
|
||||||
|
.transaction_witness_set
|
||||||
|
.plutus_v2_script
|
||||||
|
.clone()
|
||||||
|
.map(|s| s.to_vec())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let scripts_v3_witnesses = tx
|
||||||
|
.transaction_witness_set
|
||||||
|
.plutus_v3_script
|
||||||
|
.clone()
|
||||||
|
.map(|s| s.to_vec())
|
||||||
|
.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().unwrap()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for script in scripts_v3_witnesses.iter() {
|
||||||
|
scripts.insert(script.compute_hash(), ScriptVersion::V3(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 {
|
||||||
|
PseudoScript::NativeScript(ns) => {
|
||||||
|
scripts
|
||||||
|
.insert(ns.compute_hash(), ScriptVersion::Native(ns.clone()));
|
||||||
|
}
|
||||||
|
PseudoScript::PlutusV1Script(v1) => {
|
||||||
|
scripts.insert(v1.compute_hash(), ScriptVersion::V1(v1.clone()));
|
||||||
|
}
|
||||||
|
PseudoScript::PlutusV2Script(v2) => {
|
||||||
|
scripts.insert(v2.compute_hash(), ScriptVersion::V2(v2.clone()));
|
||||||
|
}
|
||||||
|
PseudoScript::PlutusV3Script(v3) => {
|
||||||
|
scripts.insert(v3.compute_hash(), ScriptVersion::V3(v3.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataLookupTable { datum, scripts }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataLookupTable {
|
||||||
|
pub fn scripts(&self) -> HashMap<ScriptHash, ScriptVersion> {
|
||||||
|
self.scripts.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TxInfoV1 {
|
pub struct TxInfoV1 {
|
||||||
pub inputs: Vec<TxInInfo>,
|
pub inputs: Vec<TxInInfo>,
|
||||||
pub outputs: Vec<TxOut>,
|
pub outputs: Vec<TxOut>,
|
||||||
pub fee: Value,
|
pub fee: Value,
|
||||||
pub mint: MintValue,
|
pub mint: MintValue,
|
||||||
pub dcert: Vec<Certificate>,
|
pub certificates: Vec<Certificate>,
|
||||||
pub wdrl: Vec<(Address, Coin)>,
|
pub withdrawals: Vec<(Address, Coin)>,
|
||||||
pub valid_range: TimeRange,
|
pub valid_range: TimeRange,
|
||||||
pub signatories: Vec<AddrKeyhash>,
|
pub signatories: Vec<AddrKeyhash>,
|
||||||
pub data: Vec<(DatumHash, PlutusData)>,
|
pub data: Vec<(DatumHash, PlutusData)>,
|
||||||
|
pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
|
||||||
pub id: Hash<32>,
|
pub id: Hash<32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,16 +205,28 @@ impl TxInfoV1 {
|
||||||
return Err(Error::ScriptAndInputRefNotAllowed);
|
return Err(Error::ScriptAndInputRefNotAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inputs = get_tx_in_info_v1(&tx.transaction_body.inputs, utxos)?;
|
||||||
|
let certificates = get_certificates_info(&tx.transaction_body.certificates);
|
||||||
|
let withdrawals =
|
||||||
|
KeyValuePairs::from(get_withdrawal_info(&tx.transaction_body.withdrawals));
|
||||||
|
let mint = get_mint_info(&tx.transaction_body.mint);
|
||||||
|
|
||||||
|
let redeemers = get_redeemers_info(
|
||||||
|
&tx.transaction_witness_set,
|
||||||
|
script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(TxInfo::V1(TxInfoV1 {
|
Ok(TxInfo::V1(TxInfoV1 {
|
||||||
inputs: get_tx_in_info_v1(&tx.transaction_body.inputs, utxos)?,
|
inputs,
|
||||||
outputs: get_outputs_info(TxOut::V1, &tx.transaction_body.outputs[..]),
|
outputs: get_outputs_info(TxOut::V1, &tx.transaction_body.outputs[..]),
|
||||||
fee: get_fee_info(&tx.transaction_body.fee),
|
fee: get_fee_info(&tx.transaction_body.fee),
|
||||||
mint: get_mint_info(&tx.transaction_body.mint),
|
mint,
|
||||||
dcert: get_certificates_info(&tx.transaction_body.certificates),
|
certificates,
|
||||||
wdrl: get_withdrawal_info(&tx.transaction_body.withdrawals),
|
withdrawals: withdrawals.into(),
|
||||||
valid_range: get_validity_range_info(&tx.transaction_body, slot_config),
|
valid_range: get_validity_range_info(&tx.transaction_body, slot_config),
|
||||||
signatories: get_signatories_info(&tx.transaction_body.required_signers),
|
signatories: get_signatories_info(&tx.transaction_body.required_signers),
|
||||||
data: get_data_info(&tx.transaction_witness_set),
|
data: get_data_info(&tx.transaction_witness_set),
|
||||||
|
redeemers,
|
||||||
id: tx.transaction_body.original_hash(),
|
id: tx.transaction_body.original_hash(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -87,8 +239,8 @@ pub struct TxInfoV2 {
|
||||||
pub outputs: Vec<TxOut>,
|
pub outputs: Vec<TxOut>,
|
||||||
pub fee: Value,
|
pub fee: Value,
|
||||||
pub mint: MintValue,
|
pub mint: MintValue,
|
||||||
pub dcert: Vec<Certificate>,
|
pub certificates: Vec<Certificate>,
|
||||||
pub wdrl: KeyValuePairs<Address, Coin>,
|
pub withdrawals: KeyValuePairs<Address, Coin>,
|
||||||
pub valid_range: TimeRange,
|
pub valid_range: TimeRange,
|
||||||
pub signatories: Vec<AddrKeyhash>,
|
pub signatories: Vec<AddrKeyhash>,
|
||||||
pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
|
pub redeemers: KeyValuePairs<ScriptPurpose, Redeemer>,
|
||||||
|
@ -103,13 +255,14 @@ impl TxInfoV2 {
|
||||||
slot_config: &SlotConfig,
|
slot_config: &SlotConfig,
|
||||||
) -> Result<TxInfo, Error> {
|
) -> Result<TxInfo, Error> {
|
||||||
let inputs = get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)?;
|
let inputs = get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)?;
|
||||||
let dcert = get_certificates_info(&tx.transaction_body.certificates);
|
let certificates = get_certificates_info(&tx.transaction_body.certificates);
|
||||||
let wdrl = KeyValuePairs::from(get_withdrawal_info(&tx.transaction_body.withdrawals));
|
let withdrawals =
|
||||||
|
KeyValuePairs::from(get_withdrawal_info(&tx.transaction_body.withdrawals));
|
||||||
let mint = get_mint_info(&tx.transaction_body.mint);
|
let mint = get_mint_info(&tx.transaction_body.mint);
|
||||||
|
|
||||||
let redeemers = get_redeemers_info(
|
let redeemers = get_redeemers_info(
|
||||||
&tx.transaction_witness_set,
|
&tx.transaction_witness_set,
|
||||||
script_purpose_builder(&inputs[..], &mint, &dcert, &wdrl),
|
script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let reference_inputs = tx
|
let reference_inputs = tx
|
||||||
|
@ -126,8 +279,8 @@ impl TxInfoV2 {
|
||||||
outputs: get_outputs_info(TxOut::V2, &tx.transaction_body.outputs[..]),
|
outputs: get_outputs_info(TxOut::V2, &tx.transaction_body.outputs[..]),
|
||||||
fee: get_fee_info(&tx.transaction_body.fee),
|
fee: get_fee_info(&tx.transaction_body.fee),
|
||||||
mint,
|
mint,
|
||||||
dcert,
|
certificates,
|
||||||
wdrl,
|
withdrawals,
|
||||||
valid_range: get_validity_range_info(&tx.transaction_body, slot_config),
|
valid_range: get_validity_range_info(&tx.transaction_body, slot_config),
|
||||||
signatories: get_signatories_info(&tx.transaction_body.required_signers),
|
signatories: get_signatories_info(&tx.transaction_body.required_signers),
|
||||||
data: KeyValuePairs::from(get_data_info(&tx.transaction_witness_set)),
|
data: KeyValuePairs::from(get_data_info(&tx.transaction_witness_set)),
|
||||||
|
@ -155,6 +308,50 @@ pub enum TxInfo {
|
||||||
V2(TxInfoV2),
|
V2(TxInfoV2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TxInfo {
|
||||||
|
pub fn purpose(&self, needle: &Redeemer) -> Option<ScriptPurpose> {
|
||||||
|
match self {
|
||||||
|
TxInfo::V1(TxInfoV1 { redeemers, .. }) | TxInfo::V2(TxInfoV2 { redeemers, .. }) => {
|
||||||
|
redeemers.iter().find_map(|(purpose, redeemer)| {
|
||||||
|
if redeemer == needle {
|
||||||
|
Some(purpose.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputs(&self) -> &[TxInInfo] {
|
||||||
|
match self {
|
||||||
|
TxInfo::V1(info) => &info.inputs,
|
||||||
|
TxInfo::V2(info) => &info.inputs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mint(&self) -> &MintValue {
|
||||||
|
match self {
|
||||||
|
TxInfo::V1(info) => &info.mint,
|
||||||
|
TxInfo::V2(info) => &info.mint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn withdrawals(&self) -> &[(Address, Coin)] {
|
||||||
|
match self {
|
||||||
|
TxInfo::V1(info) => &info.withdrawals[..],
|
||||||
|
TxInfo::V2(info) => &info.withdrawals[..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn certificates(&self) -> &[Certificate] {
|
||||||
|
match self {
|
||||||
|
TxInfo::V1(info) => &info.certificates[..],
|
||||||
|
TxInfo::V2(info) => &info.certificates[..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ScriptContext {
|
pub struct ScriptContext {
|
||||||
pub tx_info: TxInfo,
|
pub tx_info: TxInfo,
|
||||||
|
@ -235,7 +432,7 @@ pub fn get_tx_in_info_v1(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tx_in_info_v2(
|
pub fn get_tx_in_info_v2(
|
||||||
inputs: &[TransactionInput],
|
inputs: &[TransactionInput],
|
||||||
utxos: &[ResolvedInput],
|
utxos: &[ResolvedInput],
|
||||||
) -> Result<Vec<TxInInfo>, Error> {
|
) -> Result<Vec<TxInInfo>, Error> {
|
||||||
|
@ -271,7 +468,7 @@ fn get_tx_in_info_v2(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mint_info(mint: &Option<Mint>) -> MintValue {
|
pub fn get_mint_info(mint: &Option<Mint>) -> MintValue {
|
||||||
MintValue {
|
MintValue {
|
||||||
mint_value: mint
|
mint_value: mint
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -280,7 +477,7 @@ fn get_mint_info(mint: &Option<Mint>) -> MintValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_outputs_info(
|
pub fn get_outputs_info(
|
||||||
to_tx_out: fn(TransactionOutput) -> TxOut,
|
to_tx_out: fn(TransactionOutput) -> TxOut,
|
||||||
outputs: &[MintedTransactionOutput],
|
outputs: &[MintedTransactionOutput],
|
||||||
) -> Vec<TxOut> {
|
) -> Vec<TxOut> {
|
||||||
|
@ -291,15 +488,15 @@ fn get_outputs_info(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fee_info(fee: &Coin) -> Value {
|
pub fn get_fee_info(fee: &Coin) -> Value {
|
||||||
Value::Coin(*fee)
|
Value::Coin(*fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_certificates_info(certificates: &Option<NonEmptySet<Certificate>>) -> Vec<Certificate> {
|
pub fn get_certificates_info(certificates: &Option<NonEmptySet<Certificate>>) -> Vec<Certificate> {
|
||||||
certificates.clone().map(|s| s.to_vec()).unwrap_or_default()
|
certificates.clone().map(|s| s.to_vec()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_withdrawal_info(
|
pub fn get_withdrawal_info(
|
||||||
withdrawals: &Option<NonEmptyKeyValuePairs<RewardAccount, Coin>>,
|
withdrawals: &Option<NonEmptyKeyValuePairs<RewardAccount, Coin>>,
|
||||||
) -> Vec<(Address, Coin)> {
|
) -> Vec<(Address, Coin)> {
|
||||||
withdrawals
|
withdrawals
|
||||||
|
@ -313,7 +510,10 @@ fn get_withdrawal_info(
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_validity_range_info(body: &MintedTransactionBody, slot_config: &SlotConfig) -> TimeRange {
|
pub fn get_validity_range_info(
|
||||||
|
body: &MintedTransactionBody,
|
||||||
|
slot_config: &SlotConfig,
|
||||||
|
) -> TimeRange {
|
||||||
fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> u64 {
|
fn slot_to_begin_posix_time(slot: u64, sc: &SlotConfig) -> u64 {
|
||||||
let ms_after_begin = (slot - sc.zero_slot) * sc.slot_length as u64;
|
let ms_after_begin = (slot - sc.zero_slot) * sc.slot_length as u64;
|
||||||
sc.zero_time + ms_after_begin
|
sc.zero_time + ms_after_begin
|
||||||
|
@ -339,14 +539,14 @@ fn get_validity_range_info(body: &MintedTransactionBody, slot_config: &SlotConfi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_signatories_info(signers: &Option<RequiredSigners>) -> Vec<AddrKeyhash> {
|
pub fn get_signatories_info(signers: &Option<RequiredSigners>) -> Vec<AddrKeyhash> {
|
||||||
signers
|
signers
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|s| s.iter().cloned().sorted().collect())
|
.map(|s| s.iter().cloned().sorted().collect())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_data_info(witness_set: &MintedWitnessSet) -> Vec<(DatumHash, PlutusData)> {
|
pub fn get_data_info(witness_set: &MintedWitnessSet) -> Vec<(DatumHash, PlutusData)> {
|
||||||
witness_set
|
witness_set
|
||||||
.plutus_data
|
.plutus_data
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
@ -360,7 +560,7 @@ fn get_data_info(witness_set: &MintedWitnessSet) -> Vec<(DatumHash, PlutusData)>
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_redeemers_info<'a>(
|
pub fn get_redeemers_info<'a>(
|
||||||
witness_set: &'a MintedWitnessSet,
|
witness_set: &'a MintedWitnessSet,
|
||||||
to_script_purpose: impl Fn(&'a RedeemersKey) -> Result<ScriptPurpose, Error>,
|
to_script_purpose: impl Fn(&'a RedeemersKey) -> Result<ScriptPurpose, Error>,
|
||||||
) -> Result<KeyValuePairs<ScriptPurpose, Redeemer>, Error> {
|
) -> Result<KeyValuePairs<ScriptPurpose, Redeemer>, Error> {
|
||||||
|
@ -391,8 +591,8 @@ fn get_redeemers_info<'a>(
|
||||||
fn script_purpose_builder<'a>(
|
fn script_purpose_builder<'a>(
|
||||||
inputs: &'a [TxInInfo],
|
inputs: &'a [TxInInfo],
|
||||||
mint: &'a MintValue,
|
mint: &'a MintValue,
|
||||||
dcert: &'a [Certificate],
|
certificates: &'a [Certificate],
|
||||||
wdrl: &'a KeyValuePairs<Address, Coin>,
|
withdrawals: &'a KeyValuePairs<Address, Coin>,
|
||||||
) -> impl Fn(&'a RedeemersKey) -> Result<ScriptPurpose, Error> {
|
) -> impl Fn(&'a RedeemersKey) -> Result<ScriptPurpose, Error> {
|
||||||
move |redeemer: &'a RedeemersKey| {
|
move |redeemer: &'a RedeemersKey| {
|
||||||
let tag = redeemer.tag;
|
let tag = redeemer.tag;
|
||||||
|
@ -406,8 +606,11 @@ fn script_purpose_builder<'a>(
|
||||||
.get(index)
|
.get(index)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|i| ScriptPurpose::Spending(i.out_ref)),
|
.map(|i| ScriptPurpose::Spending(i.out_ref)),
|
||||||
RedeemerTag::Cert => dcert.get(index).cloned().map(ScriptPurpose::Certifying),
|
RedeemerTag::Cert => certificates
|
||||||
RedeemerTag::Reward => wdrl
|
.get(index)
|
||||||
|
.cloned()
|
||||||
|
.map(ScriptPurpose::Certifying),
|
||||||
|
RedeemerTag::Reward => withdrawals
|
||||||
.get(index)
|
.get(index)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|(address, _)| match address {
|
.map(|(address, _)| match address {
|
||||||
|
@ -415,7 +618,7 @@ fn script_purpose_builder<'a>(
|
||||||
StakePayload::Script(script_hash) => Ok(ScriptPurpose::Rewarding(
|
StakePayload::Script(script_hash) => Ok(ScriptPurpose::Rewarding(
|
||||||
StakeCredential::Scripthash(*script_hash),
|
StakeCredential::Scripthash(*script_hash),
|
||||||
)),
|
)),
|
||||||
StakePayload::Stake(_) => Err(Error::ScriptKeyHash),
|
StakePayload::Stake(_) => Err(Error::NonScriptWithdrawal),
|
||||||
},
|
},
|
||||||
_ => Err(Error::BadWithdrawalAddress),
|
_ => Err(Error::BadWithdrawalAddress),
|
||||||
})
|
})
|
||||||
|
@ -426,6 +629,108 @@ fn script_purpose_builder<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_script(
|
||||||
|
redeemer: &Redeemer,
|
||||||
|
tx: &MintedTx,
|
||||||
|
utxos: &[ResolvedInput],
|
||||||
|
lookup_table: &DataLookupTable,
|
||||||
|
) -> Result<(ScriptVersion, Option<PlutusData>), Error> {
|
||||||
|
let lookup_script = |script_hash: &ScriptHash| match lookup_table.scripts.get(script_hash) {
|
||||||
|
Some(s) => Ok((s.clone(), None)),
|
||||||
|
None => Err(Error::MissingRequiredScript {
|
||||||
|
hash: script_hash.to_string(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let lookup_datum = |datum: Option<DatumOption>| match datum {
|
||||||
|
Some(DatumOption::Hash(hash)) => match lookup_table.datum.get(&hash) {
|
||||||
|
Some(d) => Ok(d.clone()),
|
||||||
|
None => Err(Error::MissingRequiredDatum {
|
||||||
|
hash: hash.to_string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Some(DatumOption::Data(data)) => Ok(data.0.clone()),
|
||||||
|
_ => Err(Error::MissingRequiredInlineDatumOrHash),
|
||||||
|
};
|
||||||
|
|
||||||
|
match redeemer.tag {
|
||||||
|
RedeemerTag::Mint => get_mint_info(&tx.transaction_body.mint)
|
||||||
|
.mint_value
|
||||||
|
.get(redeemer.index as usize)
|
||||||
|
.ok_or(Error::MissingScriptForRedeemer)
|
||||||
|
.and_then(|(policy_id, _)| {
|
||||||
|
let policy_id_array: [u8; 28] = policy_id.to_vec().try_into().unwrap();
|
||||||
|
let hash = Hash::from(policy_id_array);
|
||||||
|
lookup_script(&hash)
|
||||||
|
}),
|
||||||
|
|
||||||
|
RedeemerTag::Reward => get_withdrawal_info(&tx.transaction_body.withdrawals)
|
||||||
|
.get(redeemer.index as usize)
|
||||||
|
.ok_or(Error::MissingScriptForRedeemer)
|
||||||
|
.and_then(|(addr, _)| {
|
||||||
|
let stake_addr = if let Address::Stake(stake_addr) = addr {
|
||||||
|
stake_addr
|
||||||
|
} else {
|
||||||
|
unreachable!("withdrawal always contains stake addresses")
|
||||||
|
};
|
||||||
|
|
||||||
|
if let StakePayload::Script(hash) = stake_addr.payload() {
|
||||||
|
lookup_script(hash)
|
||||||
|
} else {
|
||||||
|
Err(Error::NonScriptWithdrawal)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
RedeemerTag::Cert => get_certificates_info(&tx.transaction_body.certificates)
|
||||||
|
.get(redeemer.index as usize)
|
||||||
|
.ok_or(Error::MissingScriptForRedeemer)
|
||||||
|
.and_then(|cert| match cert {
|
||||||
|
Certificate::StakeDeregistration(stake_credential) => match stake_credential {
|
||||||
|
StakeCredential::Scripthash(hash) => Ok(hash),
|
||||||
|
_ => Err(Error::NonScriptStakeCredential),
|
||||||
|
},
|
||||||
|
Certificate::StakeDelegation(stake_credential, _) => match stake_credential {
|
||||||
|
StakeCredential::Scripthash(hash) => Ok(hash),
|
||||||
|
_ => Err(Error::NonScriptStakeCredential),
|
||||||
|
},
|
||||||
|
Certificate::PoolRetirement { .. } | Certificate::PoolRegistration { .. } => {
|
||||||
|
Err(Error::UnsupportedCertificateType)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
todo!("remaining certificate types")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(lookup_script),
|
||||||
|
|
||||||
|
RedeemerTag::Spend => get_tx_in_info_v2(&tx.transaction_body.inputs, utxos)
|
||||||
|
.or_else(|err| {
|
||||||
|
if matches!(err, Error::ByronAddressNotAllowed) {
|
||||||
|
get_tx_in_info_v1(&tx.transaction_body.inputs, utxos)
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
.get(redeemer.index as usize)
|
||||||
|
.ok_or(Error::MissingScriptForRedeemer)
|
||||||
|
.and_then(|input| match input.resolved.address() {
|
||||||
|
Address::Shelley(shelley_address) => {
|
||||||
|
let hash = shelley_address.payment().as_hash();
|
||||||
|
|
||||||
|
let script = lookup_script(hash);
|
||||||
|
|
||||||
|
let datum = lookup_datum(input.resolved.datum());
|
||||||
|
|
||||||
|
script.and_then(|(script, _)| Ok((script, Some(datum?))))
|
||||||
|
}
|
||||||
|
_ => Err(Error::NonScriptStakeCredential),
|
||||||
|
}),
|
||||||
|
|
||||||
|
RedeemerTag::Propose => todo!("find_script: RedeemerTag::Propose"),
|
||||||
|
|
||||||
|
RedeemerTag::Vote => todo!("find_script: RedeemerTag::Vote"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from_alonzo_value(value: &alonzo::Value) -> Value {
|
fn from_alonzo_value(value: &alonzo::Value) -> Value {
|
||||||
match value {
|
match value {
|
||||||
alonzo::Value::Coin(coin) => Value::Coin(*coin),
|
alonzo::Value::Coin(coin) => Value::Coin(*coin),
|
||||||
|
|
|
@ -591,8 +591,8 @@ impl ToPlutusData for TxInfo {
|
||||||
tx_info.outputs.to_plutus_data(),
|
tx_info.outputs.to_plutus_data(),
|
||||||
tx_info.fee.to_plutus_data(),
|
tx_info.fee.to_plutus_data(),
|
||||||
tx_info.mint.to_plutus_data(),
|
tx_info.mint.to_plutus_data(),
|
||||||
tx_info.dcert.to_plutus_data(),
|
tx_info.certificates.to_plutus_data(),
|
||||||
tx_info.wdrl.to_plutus_data(),
|
tx_info.withdrawals.to_plutus_data(),
|
||||||
tx_info.valid_range.to_plutus_data(),
|
tx_info.valid_range.to_plutus_data(),
|
||||||
tx_info.signatories.to_plutus_data(),
|
tx_info.signatories.to_plutus_data(),
|
||||||
tx_info.data.to_plutus_data(),
|
tx_info.data.to_plutus_data(),
|
||||||
|
@ -607,8 +607,8 @@ impl ToPlutusData for TxInfo {
|
||||||
tx_info.outputs.to_plutus_data(),
|
tx_info.outputs.to_plutus_data(),
|
||||||
tx_info.fee.to_plutus_data(),
|
tx_info.fee.to_plutus_data(),
|
||||||
tx_info.mint.to_plutus_data(),
|
tx_info.mint.to_plutus_data(),
|
||||||
tx_info.dcert.to_plutus_data(),
|
tx_info.certificates.to_plutus_data(),
|
||||||
tx_info.wdrl.to_plutus_data(),
|
tx_info.withdrawals.to_plutus_data(),
|
||||||
tx_info.valid_range.to_plutus_data(),
|
tx_info.valid_range.to_plutus_data(),
|
||||||
tx_info.signatories.to_plutus_data(),
|
tx_info.signatories.to_plutus_data(),
|
||||||
tx_info.redeemers.to_plutus_data(),
|
tx_info.redeemers.to_plutus_data(),
|
||||||
|
|
Loading…
Reference in New Issue