From defce9be4e45b204a58ee4f1107e3658b809aa53 Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Wed, 11 Sep 2024 15:25:20 -0600 Subject: [PATCH 1/3] fix: Plutus V3 NoDatum - Error: missing required inline datum or datum hash in script input --- crates/uplc/src/tx/script_context.rs | 27 +++-- .../resolved_inputs.template | 5 + .../v3/ctx/simple_spend_no_datum/tx.template | 33 +++++++ .../v3/validators/simple_spend_no_datum.ak | 99 +++++++++++++++++++ 4 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/resolved_inputs.template create mode 100644 examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/tx.template create mode 100644 examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak diff --git a/crates/uplc/src/tx/script_context.rs b/crates/uplc/src/tx/script_context.rs index 992b86e2e..d36b0dbed 100644 --- a/crates/uplc/src/tx/script_context.rs +++ b/crates/uplc/src/tx/script_context.rs @@ -1,4 +1,7 @@ -use super::{to_plutus_data::MintValue, Error}; +use super::{ + to_plutus_data::{MintValue, ToPlutusData}, + Error, +}; use itertools::Itertools; use pallas_addresses::{Address, Network, StakePayload}; use pallas_codec::utils::{ @@ -839,6 +842,16 @@ pub fn find_script( Some(DatumOption::Data(data)) => Ok(data.0.clone()), _ => Err(Error::MissingRequiredInlineDatumOrHash), }; + let lookup_datum_v3 = |datum: Option| 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()), + _ => Ok(None::.to_plutus_data()), + }; match redeemer.tag { RedeemerTag::Mint => get_mint_info(&tx.transaction_body.mint) @@ -908,12 +921,12 @@ pub fn find_script( .and_then(|input| match output_address(&input.resolved) { Address::Shelley(shelley_address) => { let hash = shelley_address.payment().as_hash(); - - let script = lookup_script(hash); - - let datum = lookup_datum(output_datum(&input.resolved)); - - script.and_then(|(script, _)| Ok((script, Some(datum?)))) + let (script, _) = lookup_script(hash)?; + let datum = match script { + ScriptVersion::V3(_) => lookup_datum_v3(output_datum(&input.resolved)), + _ => lookup_datum(output_datum(&input.resolved)), + }?; + Ok((script, Some(datum))) } _ => Err(Error::NonScriptStakeCredential), }), diff --git a/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/resolved_inputs.template b/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/resolved_inputs.template new file mode 100644 index 000000000..ab2915ce8 --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/resolved_inputs.template @@ -0,0 +1,5 @@ +[ + { 0: h'70{{ simple_spend_no_datum.simple_spend.spend.hash }}' + , 1: 1000000000 + } +] diff --git a/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/tx.template b/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/tx.template new file mode 100644 index 000000000..7a4775016 --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/ctx/simple_spend_no_datum/tx.template @@ -0,0 +1,33 @@ +[ + { 0: + [ [h'0000000000000000000000000000000000000000000000000000000000000000', 0] + ] + + , 1: + [] + + , 2: 42 + + , 11: h'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + + , 13: + [ [h'0000000000000000000000000000000000000000000000000000000000000000', 0] + ] + + , 16: + [ h'6000000000000000000000000000000000000000000000000000000000', 1000000000 + ] + + , 17: 1 + }, + + { 5: [[0, 0, 121([]), [1000000, 100000000]]] + + , 7: [h'{{ simple_spend_no_datum.simple_spend.spend.cbor }}'] + + }, + + true, + + null +] diff --git a/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak b/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak new file mode 100644 index 000000000..c711d8c51 --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak @@ -0,0 +1,99 @@ +use aiken/collection/dict +use cardano/address.{Address, Script} +use cardano/assets +use cardano/transaction.{ + Input, NoDatum, Output, OutputReference, ScriptPurpose, Spend, Transaction, +} + +validator simple_spend { + spend( + _datum: Option, + _redeemer: Void, + output_ref: OutputReference, + transaction: Transaction, + ) { + assert_transaction_id(transaction.id) + + assert_script_info(output_ref) + + assert_inputs(transaction.inputs) + + expect [] = transaction.outputs + + expect [] = transaction.reference_inputs + + expect [] = transaction.extra_signatories + + expect 42 == transaction.fee + + assert_redeemers(transaction.redeemers) + + expect [] == dict.to_pairs(transaction.datums) + + True + } + + else(_ctx) { + fail + } +} + +fn assert_transaction_id(id: ByteArray) { + expect + #"c6fbd346681a8f8337f6b3e51e6ec973f1509367eabc3a44c849af58a1d8471b" == id + Void +} + +fn assert_script_info(info: OutputReference) { + expect + OutputReference { + transaction_id: #"0000000000000000000000000000000000000000000000000000000000000000", + output_index: 0, + } == info + Void +} + +fn assert_inputs(inputs: List) { + expect [ + Input { + output_reference: OutputReference { transaction_id, output_index: 0 }, + output: Output { + address, + value: resolved_input_value, + datum: NoDatum, + reference_script: None, + }, + }, + ] = inputs + + expect + transaction_id == #"0000000000000000000000000000000000000000000000000000000000000000" + + expect resolved_input_value == assets.from_lovelace(1000000000) + + expect Address { payment_credential: Script(_), stake_credential: None } = + address + + Void +} + +fn assert_redeemers(redeemers: Pairs) { + expect + [ + Pair( + Spend( + OutputReference { + transaction_id: #"0000000000000000000000000000000000000000000000000000000000000000", + output_index: 0, + }, + ), + void(), + ), + ] == redeemers + Void +} + +fn void() -> Data { + let void: Data = Void + void +} From 8a3bbfc89aaeedd62f33258017cd7f2255b1bf0d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 12 Sep 2024 17:19:17 +0200 Subject: [PATCH 2/3] Simplify optional datum extraction by removing duplication --- crates/uplc/src/tx/script_context.rs | 35 +++++++++++----------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/crates/uplc/src/tx/script_context.rs b/crates/uplc/src/tx/script_context.rs index d36b0dbed..a5f4c1cda 100644 --- a/crates/uplc/src/tx/script_context.rs +++ b/crates/uplc/src/tx/script_context.rs @@ -1,7 +1,4 @@ -use super::{ - to_plutus_data::{MintValue, ToPlutusData}, - Error, -}; +use super::{to_plutus_data::MintValue, Error}; use itertools::Itertools; use pallas_addresses::{Address, Network, StakePayload}; use pallas_codec::utils::{ @@ -834,23 +831,13 @@ pub fn find_script( let lookup_datum = |datum: Option| 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), - }; - let lookup_datum_v3 = |datum: Option| match datum { - Some(DatumOption::Hash(hash)) => match lookup_table.datum.get(&hash) { - Some(d) => Ok(d.clone()), + Some(d) => Ok(Some(d.clone())), None => Err(Error::MissingRequiredDatum { hash: hash.to_string(), }), }, - Some(DatumOption::Data(data)) => Ok(data.0.clone()), - _ => Ok(None::.to_plutus_data()), + Some(DatumOption::Data(data)) => Ok(Some(data.0.clone())), + None => Ok(None), }; match redeemer.tag { @@ -922,11 +909,15 @@ pub fn find_script( Address::Shelley(shelley_address) => { let hash = shelley_address.payment().as_hash(); let (script, _) = lookup_script(hash)?; - let datum = match script { - ScriptVersion::V3(_) => lookup_datum_v3(output_datum(&input.resolved)), - _ => lookup_datum(output_datum(&input.resolved)), - }?; - Ok((script, Some(datum))) + let datum = lookup_datum(output_datum(&input.resolved))?; + + if datum.is_none() + && matches!(script, ScriptVersion::V1(..) | ScriptVersion::V2(..)) + { + return Err(Error::MissingRequiredInlineDatumOrHash); + } + + Ok((script, datum)) } _ => Err(Error::NonScriptStakeCredential), }), From 6413f2c1cccde7193a5590d85a91b113ff68be85 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 12 Sep 2024 17:21:09 +0200 Subject: [PATCH 3/3] Make 'simple_spend_no_datum' acceptance test more to-the-point This new acceptance test is really meant to check for the behavior surrounding the optional datum. So I've added an extra expect on the received datum, and kept only assertions that were about or related to the datum. --- .../v3/validators/simple_spend_no_datum.ak | 58 ++----------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak b/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak index c711d8c51..94c457e78 100644 --- a/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak +++ b/examples/acceptance_tests/script_context/v3/validators/simple_spend_no_datum.ak @@ -1,33 +1,19 @@ use aiken/collection/dict use cardano/address.{Address, Script} use cardano/assets -use cardano/transaction.{ - Input, NoDatum, Output, OutputReference, ScriptPurpose, Spend, Transaction, -} +use cardano/transaction.{Input, NoDatum, Output, OutputReference, Transaction} validator simple_spend { spend( - _datum: Option, + datum: Option, _redeemer: Void, - output_ref: OutputReference, + _output_ref: OutputReference, transaction: Transaction, ) { - assert_transaction_id(transaction.id) - - assert_script_info(output_ref) + expect None = datum assert_inputs(transaction.inputs) - expect [] = transaction.outputs - - expect [] = transaction.reference_inputs - - expect [] = transaction.extra_signatories - - expect 42 == transaction.fee - - assert_redeemers(transaction.redeemers) - expect [] == dict.to_pairs(transaction.datums) True @@ -38,21 +24,6 @@ validator simple_spend { } } -fn assert_transaction_id(id: ByteArray) { - expect - #"c6fbd346681a8f8337f6b3e51e6ec973f1509367eabc3a44c849af58a1d8471b" == id - Void -} - -fn assert_script_info(info: OutputReference) { - expect - OutputReference { - transaction_id: #"0000000000000000000000000000000000000000000000000000000000000000", - output_index: 0, - } == info - Void -} - fn assert_inputs(inputs: List) { expect [ Input { @@ -76,24 +47,3 @@ fn assert_inputs(inputs: List) { Void } - -fn assert_redeemers(redeemers: Pairs) { - expect - [ - Pair( - Spend( - OutputReference { - transaction_id: #"0000000000000000000000000000000000000000000000000000000000000000", - output_index: 0, - }, - ), - void(), - ), - ] == redeemers - Void -} - -fn void() -> Data { - let void: Data = Void - void -}