feat(txbuilder): compute ScriptDataHash including edge cases (#525)
Co-authored-by: kalomaaan <kalomaaan@gmail.com>
This commit is contained in:
parent
061a7796d6
commit
698d7a4933
9 changed files with 282 additions and 94 deletions
|
|
@ -232,6 +232,14 @@ where
|
||||||
pub fn to_vec(self) -> Vec<(K, V)> {
|
pub fn to_vec(self) -> Vec<(K, V)> {
|
||||||
self.into()
|
self.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(x: Vec<(K, V)>) -> Option<Self> {
|
||||||
|
if x.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(NonEmptyKeyValuePairs::Def(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> From<NonEmptyKeyValuePairs<K, V>> for Vec<(K, V)>
|
impl<K, V> From<NonEmptyKeyValuePairs<K, V>> for Vec<(K, V)>
|
||||||
|
|
@ -777,6 +785,14 @@ impl<T> NonEmptySet<T> {
|
||||||
pub fn to_vec(self) -> Vec<T> {
|
pub fn to_vec(self) -> Vec<T> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(x: Vec<T>) -> Option<Self> {
|
||||||
|
if x.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for NonEmptySet<T> {
|
impl<T> Deref for NonEmptySet<T> {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use pallas_codec::utils::{CborWrap, KeyValuePairs};
|
use pallas_codec::utils::CborWrap;
|
||||||
use pallas_crypto::hash::Hash;
|
use pallas_crypto::hash::Hash;
|
||||||
use pallas_primitives::{
|
use pallas_primitives::{
|
||||||
babbage::{
|
conway::{
|
||||||
DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId, PlutusData, PlutusScript,
|
DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId, NonZeroInt, PlutusData,
|
||||||
PostAlonzoTransactionOutput, PseudoScript as PallasScript, PseudoTransactionOutput,
|
PlutusScript, PostAlonzoTransactionOutput, PseudoScript as PallasScript,
|
||||||
Redeemer, RedeemerTag, TransactionBody, TransactionInput, Tx as BabbageTx, Value,
|
PseudoTransactionOutput, Redeemer, RedeemerTag, TransactionBody, TransactionInput,
|
||||||
WitnessSet,
|
Tx as BabbageTx, Value, WitnessSet,
|
||||||
},
|
},
|
||||||
Fragment,
|
Fragment, NonEmptyKeyValuePairs, NonEmptySet, PositiveCoin,
|
||||||
};
|
};
|
||||||
use pallas_traverse::ComputeHash;
|
use pallas_traverse::ComputeHash;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
scriptdata,
|
||||||
transaction::{
|
transaction::{
|
||||||
model::{
|
model::{
|
||||||
BuilderEra, BuiltTransaction, DatumKind, ExUnits, Output, RedeemerPurpose, ScriptKind,
|
BuilderEra, BuiltTransaction, DatumKind, ExUnits, Output, RedeemerPurpose, ScriptKind,
|
||||||
StagingTransaction,
|
StagingTransaction,
|
||||||
},
|
},
|
||||||
opt_if_empty, Bytes, Bytes32, TransactionStatus,
|
Bytes, Bytes32, TransactionStatus,
|
||||||
},
|
},
|
||||||
TxBuilderError,
|
TxBuilderError,
|
||||||
};
|
};
|
||||||
|
|
@ -52,40 +53,43 @@ impl BuildBabbage for StagingTransaction {
|
||||||
.map(Output::build_babbage_raw)
|
.map(Output::build_babbage_raw)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let mint: Option<KeyValuePairs<Hash<28>, KeyValuePairs<_, _>>> = self.mint.map(|massets| {
|
let mint = NonEmptyKeyValuePairs::from_vec(
|
||||||
massets
|
self.mint
|
||||||
.deref()
|
|
||||||
.iter()
|
.iter()
|
||||||
|
.flat_map(|x| x.deref().iter())
|
||||||
.map(|(pid, assets)| {
|
.map(|(pid, assets)| {
|
||||||
(
|
(
|
||||||
pid.0.into(),
|
Hash::<28>::from(pid.0),
|
||||||
assets
|
NonEmptyKeyValuePairs::from_vec(
|
||||||
.iter()
|
assets
|
||||||
.map(|(n, x)| (n.clone().into(), *x))
|
.iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|(n, x)| (n.clone().into(), NonZeroInt::try_from(*x).unwrap()))
|
||||||
.into(),
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>(),
|
||||||
.into()
|
);
|
||||||
});
|
|
||||||
|
|
||||||
let collateral = self
|
let collateral = NonEmptySet::from_vec(
|
||||||
.collateral_inputs
|
self.collateral_inputs
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| TransactionInput {
|
.map(|x| TransactionInput {
|
||||||
transaction_id: x.tx_hash.0.into(),
|
transaction_id: x.tx_hash.0.into(),
|
||||||
index: x.txo_index,
|
index: x.txo_index,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
let required_signers = self
|
let required_signers = NonEmptySet::from_vec(
|
||||||
.disclosed_signers
|
self.disclosed_signers
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.0.into())
|
.map(|x| x.0.into())
|
||||||
.collect();
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
let network_id = if let Some(nid) = self.network_id {
|
let network_id = if let Some(nid) = self.network_id {
|
||||||
match NetworkId::try_from(nid) {
|
match NetworkId::try_from(nid) {
|
||||||
|
|
@ -102,18 +106,19 @@ impl BuildBabbage for StagingTransaction {
|
||||||
.map(Output::build_babbage_raw)
|
.map(Output::build_babbage_raw)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let reference_inputs = self
|
let reference_inputs = NonEmptySet::from_vec(
|
||||||
.reference_inputs
|
self.reference_inputs
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| TransactionInput {
|
.map(|x| TransactionInput {
|
||||||
transaction_id: x.tx_hash.0.into(),
|
transaction_id: x.tx_hash.0.into(),
|
||||||
index: x.txo_index,
|
index: x.txo_index,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
let (mut native_script, mut plutus_v1_script, mut plutus_v2_script) =
|
let (mut native_script, mut plutus_v1_script, mut plutus_v2_script, mut plutus_v3_script) =
|
||||||
(vec![], vec![], vec![]);
|
(vec![], vec![], vec![], vec![]);
|
||||||
|
|
||||||
for (_, script) in self.scripts.unwrap_or_default() {
|
for (_, script) in self.scripts.unwrap_or_default() {
|
||||||
match script.kind {
|
match script.kind {
|
||||||
|
|
@ -133,6 +138,11 @@ impl BuildBabbage for StagingTransaction {
|
||||||
|
|
||||||
plutus_v2_script.push(script)
|
plutus_v2_script.push(script)
|
||||||
}
|
}
|
||||||
|
ScriptKind::PlutusV3 => {
|
||||||
|
let script = PlutusScript::<3>(script.bytes.into());
|
||||||
|
|
||||||
|
plutus_v3_script.push(script)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,11 +157,11 @@ impl BuildBabbage for StagingTransaction {
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let mut mint_policies = mint
|
let mut mint_policies = mint
|
||||||
.clone()
|
|
||||||
.unwrap_or(vec![].into())
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(p, _)| **p)
|
.flat_map(|x| x.deref().iter())
|
||||||
|
.map(|(p, _)| *p)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
mint_policies.sort_unstable_by_key(|x| *x);
|
mint_policies.sort_unstable_by_key(|x| *x);
|
||||||
|
|
||||||
let mut redeemers = vec![];
|
let mut redeemers = vec![];
|
||||||
|
|
@ -190,7 +200,7 @@ impl BuildBabbage for StagingTransaction {
|
||||||
RedeemerPurpose::Mint(pid) => {
|
RedeemerPurpose::Mint(pid) => {
|
||||||
let index = mint_policies
|
let index = mint_policies
|
||||||
.iter()
|
.iter()
|
||||||
.position(|x| *x == pid.0)
|
.position(|x| x.as_slice() == pid.0)
|
||||||
.ok_or(TxBuilderError::RedeemerTargetMissing)?
|
.ok_or(TxBuilderError::RedeemerTargetMissing)?
|
||||||
as u32;
|
as u32;
|
||||||
|
|
||||||
|
|
@ -205,34 +215,51 @@ impl BuildBabbage for StagingTransaction {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let witness_set_redeemers = pallas_primitives::conway::Redeemers::List(
|
||||||
|
pallas_codec::utils::MaybeIndefArray::Def(redeemers.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let script_data_hash = self.language_view.map(|language_view| {
|
||||||
|
scriptdata::ScriptData {
|
||||||
|
redeemers: witness_set_redeemers.clone(),
|
||||||
|
datums: Some(plutus_data.clone()),
|
||||||
|
language_view,
|
||||||
|
}
|
||||||
|
.hash()
|
||||||
|
});
|
||||||
|
|
||||||
let mut pallas_tx = BabbageTx {
|
let mut pallas_tx = BabbageTx {
|
||||||
transaction_body: TransactionBody {
|
transaction_body: TransactionBody {
|
||||||
inputs,
|
inputs: pallas_primitives::Set::from(inputs),
|
||||||
outputs,
|
outputs,
|
||||||
ttl: self.invalid_from_slot,
|
ttl: self.invalid_from_slot,
|
||||||
validity_interval_start: self.valid_from_slot,
|
validity_interval_start: self.valid_from_slot,
|
||||||
fee: self.fee.unwrap_or_default(),
|
fee: self.fee.unwrap_or_default(),
|
||||||
certificates: None, // TODO
|
certificates: None, // TODO
|
||||||
withdrawals: None, // TODO
|
withdrawals: None, // TODO
|
||||||
update: None, // TODO
|
|
||||||
auxiliary_data_hash: None, // TODO (accept user input)
|
auxiliary_data_hash: None, // TODO (accept user input)
|
||||||
mint,
|
mint,
|
||||||
script_data_hash: self.script_data_hash.map(|x| x.0.into()),
|
script_data_hash,
|
||||||
collateral: opt_if_empty(collateral),
|
collateral,
|
||||||
required_signers: opt_if_empty(required_signers),
|
required_signers,
|
||||||
network_id,
|
network_id,
|
||||||
collateral_return,
|
collateral_return,
|
||||||
total_collateral: None, // TODO
|
reference_inputs,
|
||||||
reference_inputs: opt_if_empty(reference_inputs),
|
total_collateral: None, // TODO
|
||||||
|
voting_procedures: None, // TODO
|
||||||
|
proposal_procedures: None, // TODO
|
||||||
|
treasury_value: None, // TODO
|
||||||
|
donation: None, // TODO
|
||||||
},
|
},
|
||||||
transaction_witness_set: WitnessSet {
|
transaction_witness_set: WitnessSet {
|
||||||
vkeywitness: None,
|
vkeywitness: None,
|
||||||
native_script: opt_if_empty(native_script),
|
native_script: NonEmptySet::from_vec(native_script),
|
||||||
bootstrap_witness: None,
|
bootstrap_witness: None,
|
||||||
plutus_v1_script: opt_if_empty(plutus_v1_script),
|
plutus_v1_script: NonEmptySet::from_vec(plutus_v1_script),
|
||||||
plutus_v2_script: opt_if_empty(plutus_v2_script),
|
plutus_v2_script: NonEmptySet::from_vec(plutus_v2_script),
|
||||||
plutus_data: opt_if_empty(plutus_data),
|
plutus_v3_script: NonEmptySet::from_vec(plutus_v3_script),
|
||||||
redeemer: opt_if_empty(redeemers),
|
plutus_data: NonEmptySet::from_vec(plutus_data),
|
||||||
|
redeemer: Some(witness_set_redeemers),
|
||||||
},
|
},
|
||||||
success: true, // TODO
|
success: true, // TODO
|
||||||
auxiliary_data: None.into(), // TODO
|
auxiliary_data: None.into(), // TODO
|
||||||
|
|
@ -264,26 +291,27 @@ impl Output {
|
||||||
pub fn build_babbage_raw(
|
pub fn build_babbage_raw(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<PseudoTransactionOutput<PostAlonzoTransactionOutput>, TxBuilderError> {
|
) -> Result<PseudoTransactionOutput<PostAlonzoTransactionOutput>, TxBuilderError> {
|
||||||
let value = if let Some(ref assets) = self.assets {
|
let assets = NonEmptyKeyValuePairs::from_vec(
|
||||||
let txb_assets = assets
|
self.assets
|
||||||
.deref()
|
|
||||||
.iter()
|
.iter()
|
||||||
|
.flat_map(|x| x.deref().iter())
|
||||||
.map(|(pid, assets)| {
|
.map(|(pid, assets)| {
|
||||||
(
|
(
|
||||||
pid.0.into(),
|
pid.0.into(),
|
||||||
assets
|
assets
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(n, x)| (n.clone().into(), *x))
|
.map(|(n, x)| (n.clone().into(), PositiveCoin::try_from(*x).unwrap()))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into(),
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>(),
|
||||||
.into();
|
);
|
||||||
|
|
||||||
Value::Multiasset(self.lovelace, txb_assets)
|
let value = match assets {
|
||||||
} else {
|
Some(assets) => Value::Multiasset(self.lovelace, assets),
|
||||||
Value::Coin(self.lovelace)
|
None => Value::Coin(self.lovelace),
|
||||||
};
|
};
|
||||||
|
|
||||||
let datum_option = if let Some(ref d) = self.datum {
|
let datum_option = if let Some(ref d) = self.datum {
|
||||||
|
|
@ -318,6 +346,9 @@ impl Output {
|
||||||
ScriptKind::PlutusV2 => PallasScript::PlutusV2Script(PlutusScript::<2>(
|
ScriptKind::PlutusV2 => PallasScript::PlutusV2Script(PlutusScript::<2>(
|
||||||
s.bytes.as_ref().to_vec().into(),
|
s.bytes.as_ref().to_vec().into(),
|
||||||
)),
|
)),
|
||||||
|
ScriptKind::PlutusV3 => PallasScript::PlutusV3Script(PlutusScript::<3>(
|
||||||
|
s.bytes.as_ref().to_vec().into(),
|
||||||
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(CborWrap(script))
|
Some(CborWrap(script))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod babbage;
|
mod babbage;
|
||||||
|
mod scriptdata;
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
|
||||||
pub use babbage::BuildBabbage;
|
pub use babbage::BuildBabbage;
|
||||||
|
|
|
||||||
143
pallas-txbuilder/src/scriptdata.rs
Normal file
143
pallas-txbuilder/src/scriptdata.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
use pallas_codec::minicbor::{self, Encode};
|
||||||
|
use pallas_primitives::conway::{CostModel, PlutusData, Redeemers};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub type PlutusVersion = u8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct LanguageView(pub PlutusVersion, pub CostModel);
|
||||||
|
|
||||||
|
impl<C> Encode<C> for LanguageView {
|
||||||
|
fn encode<W: minicbor::encode::Write>(
|
||||||
|
&self,
|
||||||
|
e: &mut minicbor::Encoder<W>,
|
||||||
|
ctx: &mut C,
|
||||||
|
) -> Result<(), minicbor::encode::Error<W::Error>> {
|
||||||
|
match self.0 {
|
||||||
|
0 => {
|
||||||
|
let mut inner = vec![];
|
||||||
|
let mut sub = minicbor::Encoder::new(&mut inner);
|
||||||
|
|
||||||
|
sub.begin_array().unwrap();
|
||||||
|
for v in self.1.iter() {
|
||||||
|
sub.encode_with(v, ctx).unwrap();
|
||||||
|
}
|
||||||
|
sub.end().unwrap();
|
||||||
|
|
||||||
|
e.map(1)?;
|
||||||
|
e.bytes(&minicbor::to_vec(0).unwrap())?;
|
||||||
|
e.bytes(&inner)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
e.map(1)?;
|
||||||
|
e.encode(self.0)?;
|
||||||
|
e.encode(&self.1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScriptData {
|
||||||
|
pub redeemers: Redeemers,
|
||||||
|
pub datums: Option<Vec<PlutusData>>,
|
||||||
|
pub language_view: LanguageView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScriptData {
|
||||||
|
pub fn hash(&self) -> pallas_crypto::hash::Hash<32> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
|
||||||
|
minicbor::encode(&self.redeemers, &mut buf).unwrap(); // infallible
|
||||||
|
|
||||||
|
if let Some(datums) = &self.datums {
|
||||||
|
minicbor::encode(datums, &mut buf).unwrap(); // infallible
|
||||||
|
}
|
||||||
|
|
||||||
|
minicbor::encode(&self.language_view, &mut buf).unwrap(); // infallible
|
||||||
|
|
||||||
|
pallas_crypto::hash::Hasher::<256>::hash(&buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use pallas_primitives::conway;
|
||||||
|
use pallas_traverse::MultiEraTx;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const COST_MODEL_PLUTUS_V1: LazyLock<Vec<i64>> = LazyLock::new(|| {
|
||||||
|
vec![
|
||||||
|
100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
|
||||||
|
16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
|
||||||
|
16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189, 769,
|
||||||
|
4, 2, 85848, 228465, 122, 0, 1, 1, 1000, 42921, 4, 2, 24548, 29498, 38, 1, 898148,
|
||||||
|
27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1, 141895, 32, 83150, 32, 15299, 32,
|
||||||
|
76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1, 43285, 552, 1, 44749, 541,
|
||||||
|
1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32, 11546, 32, 85848, 228465, 122,
|
||||||
|
0, 1, 1, 90434, 519, 0, 1, 74433, 32, 85848, 228465, 122, 0, 1, 1, 85848, 228465, 122,
|
||||||
|
0, 1, 1, 270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0, 141992, 32, 100788, 420,
|
||||||
|
1, 1, 81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32, 25933, 32, 24623, 32,
|
||||||
|
53384111, 14333, 10,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
static COST_MODEL_PLUTUS_V2: LazyLock<Vec<i64>> = LazyLock::new(|| {
|
||||||
|
vec![
|
||||||
|
100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
|
||||||
|
16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
|
||||||
|
16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189, 769,
|
||||||
|
4, 2, 85848, 228465, 122, 0, 1, 1, 1000, 42921, 4, 2, 24548, 29498, 38, 1, 898148,
|
||||||
|
27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1, 141895, 32, 83150, 32, 15299, 32,
|
||||||
|
76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1, 43285, 552, 1, 44749, 541,
|
||||||
|
1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32, 11546, 32, 85848, 228465, 122,
|
||||||
|
0, 1, 1, 90434, 519, 0, 1, 74433, 32, 85848, 228465, 122, 0, 1, 1, 85848, 228465, 122,
|
||||||
|
0, 1, 1, 955506, 213312, 0, 2, 270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0,
|
||||||
|
141992, 32, 100788, 420, 1, 1, 81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32,
|
||||||
|
25933, 32, 24623, 32, 43053543, 10, 53384111, 14333, 10, 43574283, 26308, 10,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const TEST_VECTORS: LazyLock<Vec<(Vec<u8>, LanguageView)>> = LazyLock::new(|| {
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
hex::decode(include_str!("../../test_data/conway1.tx")).unwrap(),
|
||||||
|
LanguageView(1, COST_MODEL_PLUTUS_V2.clone()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
hex::decode(include_str!("../../test_data/conway2.tx")).unwrap(),
|
||||||
|
LanguageView(0, COST_MODEL_PLUTUS_V1.clone()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
fn assert_script_data_hash_matches(bytes: &[u8], language_view: &LanguageView) {
|
||||||
|
let tx = MultiEraTx::decode(&bytes).unwrap();
|
||||||
|
let tx = tx.as_conway().unwrap();
|
||||||
|
|
||||||
|
let witness = conway::WitnessSet::from(tx.transaction_witness_set.clone().unwrap());
|
||||||
|
|
||||||
|
let script_data = ScriptData {
|
||||||
|
redeemers: witness.redeemer.unwrap(),
|
||||||
|
datums: witness.plutus_data.map(|x| x.iter().cloned().collect()),
|
||||||
|
language_view: language_view.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let obtained = script_data.hash();
|
||||||
|
|
||||||
|
let expected = tx.transaction_body.script_data_hash.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(obtained, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_script_data_hash() {
|
||||||
|
for (bytes, language_view) in TEST_VECTORS.iter() {
|
||||||
|
assert_script_data_hash_matches(bytes, language_view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ pub struct Bytes64(pub [u8; 64]);
|
||||||
type PublicKey = Bytes32;
|
type PublicKey = Bytes32;
|
||||||
type Signature = Bytes64;
|
type Signature = Bytes64;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||||
pub struct Hash28(pub [u8; 28]);
|
pub struct Hash28(pub [u8; 28]);
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
|
@ -52,12 +52,3 @@ pub type PolicyId = ScriptHash;
|
||||||
pub type DatumHash = Bytes32;
|
pub type DatumHash = Bytes32;
|
||||||
pub type DatumBytes = Bytes;
|
pub type DatumBytes = Bytes;
|
||||||
pub type AssetName = Bytes;
|
pub type AssetName = Bytes;
|
||||||
|
|
||||||
/// If a Vec is empty, returns None, or Some(Vec) if not empty
|
|
||||||
pub fn opt_if_empty<T>(v: Vec<T>) -> Option<Vec<T>> {
|
|
||||||
if v.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use std::{collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::TxBuilderError;
|
use crate::{scriptdata, TxBuilderError};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AssetName, Bytes, Bytes32, Bytes64, DatumBytes, DatumHash, Hash28, PolicyId, PubKeyHash,
|
AssetName, Bytes, Bytes32, Bytes64, DatumBytes, DatumHash, Hash28, PolicyId, PubKeyHash,
|
||||||
|
|
@ -39,6 +39,7 @@ pub struct StagingTransaction {
|
||||||
pub script_data_hash: Option<Bytes32>,
|
pub script_data_hash: Option<Bytes32>,
|
||||||
pub signature_amount_override: Option<u8>,
|
pub signature_amount_override: Option<u8>,
|
||||||
pub change_address: Option<Address>,
|
pub change_address: Option<Address>,
|
||||||
|
pub language_view: Option<scriptdata::LanguageView>,
|
||||||
// pub certificates: TODO
|
// pub certificates: TODO
|
||||||
// pub withdrawals: TODO
|
// pub withdrawals: TODO
|
||||||
// pub updates: TODO
|
// pub updates: TODO
|
||||||
|
|
@ -233,6 +234,7 @@ impl StagingTransaction {
|
||||||
ScriptKind::Native => Hasher::<224>::hash_tagged(bytes.as_ref(), 0),
|
ScriptKind::Native => Hasher::<224>::hash_tagged(bytes.as_ref(), 0),
|
||||||
ScriptKind::PlutusV1 => Hasher::<224>::hash_tagged(bytes.as_ref(), 1),
|
ScriptKind::PlutusV1 => Hasher::<224>::hash_tagged(bytes.as_ref(), 1),
|
||||||
ScriptKind::PlutusV2 => Hasher::<224>::hash_tagged(bytes.as_ref(), 2),
|
ScriptKind::PlutusV2 => Hasher::<224>::hash_tagged(bytes.as_ref(), 2),
|
||||||
|
ScriptKind::PlutusV3 => Hasher::<224>::hash_tagged(bytes.as_ref(), 3),
|
||||||
};
|
};
|
||||||
|
|
||||||
scripts.insert(
|
scripts.insert(
|
||||||
|
|
@ -284,6 +286,17 @@ impl StagingTransaction {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn language_view(mut self, plutus_version: ScriptKind, cost_model: Vec<i64>) -> Self {
|
||||||
|
self.language_view = match plutus_version {
|
||||||
|
ScriptKind::PlutusV1 => Some(scriptdata::LanguageView(0, cost_model)),
|
||||||
|
ScriptKind::PlutusV2 => Some(scriptdata::LanguageView(1, cost_model)),
|
||||||
|
ScriptKind::PlutusV3 => Some(scriptdata::LanguageView(2, cost_model)),
|
||||||
|
ScriptKind::Native => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_spend_redeemer(
|
pub fn add_spend_redeemer(
|
||||||
mut self,
|
mut self,
|
||||||
input: Input,
|
input: Input,
|
||||||
|
|
@ -340,17 +353,6 @@ impl StagingTransaction {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: script_data_hash computation
|
|
||||||
pub fn script_data_hash(mut self, hash: Hash<32>) -> Self {
|
|
||||||
self.script_data_hash = Some(Bytes32(*hash));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_script_data_hash(mut self) -> Self {
|
|
||||||
self.script_data_hash = None;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn signature_amount_override(mut self, amount: u8) -> Self {
|
pub fn signature_amount_override(mut self, amount: u8) -> Self {
|
||||||
self.signature_amount_override = Some(amount);
|
self.signature_amount_override = Some(amount);
|
||||||
self
|
self
|
||||||
|
|
@ -514,6 +516,7 @@ pub enum ScriptKind {
|
||||||
Native,
|
Native,
|
||||||
PlutusV1,
|
PlutusV1,
|
||||||
PlutusV2,
|
PlutusV2,
|
||||||
|
PlutusV3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -479,6 +479,7 @@ mod tests {
|
||||||
signature_amount_override: Some(5),
|
signature_amount_override: Some(5),
|
||||||
change_address: Some(Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap())),
|
change_address: Some(Address(PallasAddress::from_str("addr1g9ekml92qyvzrjmawxkh64r2w5xr6mg9ngfmxh2khsmdrcudevsft64mf887333adamant").unwrap())),
|
||||||
script_data_hash: Some(Bytes32([0; 32])),
|
script_data_hash: Some(Bytes32([0; 32])),
|
||||||
|
language_view: Some(crate::scriptdata::LanguageView(1, vec![1, 2, 3])),
|
||||||
};
|
};
|
||||||
|
|
||||||
let serialised_tx = serde_json::to_string(&tx).unwrap();
|
let serialised_tx = serde_json::to_string(&tx).unwrap();
|
||||||
|
|
|
||||||
1
test_data/conway1.tx
Normal file
1
test_data/conway1.tx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
84a8008282582014f21123920de0ab51306f060daf332b2bc3daeba0a5933616cfd0a6fa05d57f00825820455363dd5e1a5b321908bb7ff6840c4a6c35d1d6b83eec5b2164ec741f5f7bac000183a200583901490c6c94eb76477d9316b80a2be491a830fdc56059539d318b32ea463c4b5f3adc80ca7729e1d4d67efab9244f254d32f446ef1db74037b301821a001a3a0ba1581cb1c62afb4c4e4af8881af2aec30205786b495b608157f254c0906670a14a465544676520436f696e1a02af707aa300583931905ab869961b094f1b8197278cfe15b45cbe49fa8f32c6b014f85a2db2f6abf60ccde92eae1a2f4fdf65f2eaf6208d872c6f0e597cc10b0701821a3b324430a2581c63f947b8d9535bc4e4ce6919e3dc056547e8d30ada12f29aa5f826b8a158207d1ee25d64f4cc5b1953b8fb307a7be9301d26646bf4092c122a231634623b5b01581cb1c62afb4c4e4af8881af2aec30205786b495b608157f254c0906670a14a465544676520436f696e1a2a37e777028201d81858e8d87989d87982581c63f947b8d9535bc4e4ce6919e3dc056547e8d30ada12f29aa5f826b858207d1ee25d64f4cc5b1953b8fb307a7be9301d26646bf4092c122a231634623b5bd879824040d87982581cb1c62afb4c4e4af8881af2aec30205786b495b608157f254c09066704a465544676520436f696e1b0000001166b125ce1a001373c2581cedbf33f5d6e083970648e39175c49ec1c093df76b6e6a0f1473e47761b000000028f200558581c8807fbe6e36b1c35ad6f36f0993e2fc67ab6f2db06041cfa3a53c04a581c554daf3e27e50c8c779713be6ba70f95c35f23f0552a28e5a7a340148258390122aea2da15e494e01767145d48bda16b6d437f1c449823a044193daf299a82ef56311aa10adf04c0072d4870eb9f4d5ff315132434841b741a00325aa0021a0005d11505a1581df196f5c1bee23481335ff4aece32fe1dfa1aa40a944a66d2d6edc9a9a5000b58203a98f88216e5025db28f06e90c56b3def9f35e3ac2beef37aef7f09ace7a60c00d81825820dd325cdb8c4b2d7fbc66d6e04f2abce41e52fbf55807948aed6b9abb726eb699000e81581cedbf33f5d6e083970648e39175c49ec1c093df76b6e6a0f1473e47761283825820c4a540ac2e06c217dd4fb3f39ca3863da394ba134677dafa9b98830ca71d584d03825820b91eda29d145ab6c0bc0d6b7093cb24b131440b7b015033205476f39c690a51f00825820b91eda29d145ab6c0bc0d6b7093cb24b131440b7b015033205476f39c690a51f01a2008182582014585da9857b30cf57edccac19d3012b602bb5650c7726a5f1504d9614d205225840563d66631ddd21c04ef19e2644bfaa28dd472c5fb6d72d6b290935f81b37b14eabe87d677b32bc73cdc244082d75a8d7fb8f73930b43f906cb9addd5a8fda2070583840000d87a80821a000186a01a01c9c380840001d879830101d87980821a000864701a0d1cef0084030080821a000668a01a09896800f5f6
|
||||||
1
test_data/conway2.tx
Normal file
1
test_data/conway2.tx
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue