Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion pallas-validate/src/phase1/conway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::utils::{
ValidationError::{self, *},
ValidationResult,
};
use pallas_addresses::{ScriptHash, ShelleyAddress, ShelleyPaymentPart};
use itertools::Itertools;
use pallas_addresses::{
Address, Network, ScriptHash, ShelleyAddress, ShelleyPaymentPart, StakePayload,
};
use pallas_codec::utils::{Bytes, KeepRaw, NonEmptySet};
use pallas_primitives::{
babbage,
Expand All @@ -23,6 +26,7 @@ use pallas_primitives::{
AddrKeyhash, Hash, PlutusData, PlutusScript, PolicyId, PositiveCoin, TransactionInput,
};
use pallas_traverse::{MultiEraInput, MultiEraOutput, OriginalHash};
use std::cmp::Ordering;
use std::ops::Deref;

pub fn validate_conway_tx(
Expand Down Expand Up @@ -1129,6 +1133,7 @@ fn check_redeemers(

None => Vec::new(),
};

let plutus_scripts: Vec<RedeemersKey> = mk_plutus_script_redeemer_pointers(
plutus_v1_scripts,
plutus_v2_scripts,
Expand All @@ -1147,6 +1152,35 @@ fn sort_inputs(unsorted_inputs: &[TransactionInput]) -> Vec<TransactionInput> {
res
}

// Sorting function for reward accounts (withdrawals).
fn sort_reward_accounts(a: &Bytes, b: &Bytes) -> Ordering {
let addr_a = Address::from_bytes(a).expect("invalid reward address in withdrawals.");
let addr_b = Address::from_bytes(b).expect("invalid reward address in withdrawals.");

fn network_tag(network: Network) -> u8 {
match network {
Network::Testnet => 0,
Network::Mainnet => 1,
Network::Other(tag) => tag,
}
}

if let (Address::Stake(accnt_a), Address::Stake(accnt_b)) = (addr_a, addr_b) {
if accnt_a.network() != accnt_b.network() {
return network_tag(accnt_a.network()).cmp(&network_tag(accnt_b.network()));
}

match (accnt_a.payload(), accnt_b.payload()) {
(StakePayload::Script(..), StakePayload::Stake(..)) => Ordering::Less,
(StakePayload::Stake(..), StakePayload::Script(..)) => Ordering::Greater,
(StakePayload::Script(hash_a), StakePayload::Script(hash_b)) => hash_a.cmp(hash_b),
(StakePayload::Stake(hash_a), StakePayload::Stake(hash_b)) => hash_a.cmp(hash_b),
}
} else {
unreachable!("invalid reward address in withdrawals.");
}
}

fn mk_plutus_script_redeemer_pointers(
plutus_v1_scripts: &[PolicyId],
plutus_v2_scripts: &[PolicyId],
Expand Down Expand Up @@ -1190,6 +1224,31 @@ fn mk_plutus_script_redeemer_pointers(
}
}
}
if let Some(withdrawals) = &tx_body.withdrawals {
for (index, (stake_key_hash_bytes, _amount)) in withdrawals
.iter()
.sorted_by(|(accnt_a, _), (accnt_b, _)| sort_reward_accounts(accnt_a, accnt_b))
.enumerate()
{
if let Ok(Address::Stake(stake_addr)) = Address::from_bytes(stake_key_hash_bytes) {
if stake_addr.is_script() {
let script_hash = stake_addr.payload().as_hash();
if is_phase_2_script(
&script_hash,
plutus_v1_scripts,
plutus_v2_scripts,
plutus_v3_scripts,
reference_scripts,
) {
Comment on lines +1234 to +1242
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Filter reference scripts to Plutus before adding Reward pointers.

is_phase_2_script treats any hash present in reference_scripts as redeemer-requiring, but get_reference_script_hashes also collects native reference scripts. For a withdrawal authorized by a native script supplied through a reference input, this branch will emit a Reward pointer and phase-1 will incorrectly fail with RedeemerMissing. Pass only Plutus reference hashes here, or make the helper distinguish native vs Plutus refs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pallas-validate/src/phase1/conway.rs` around lines 1234 - 1242, The code
currently passes reference_scripts (which may include native-script hashes) into
is_phase_2_script causing native reference scripts to be treated like Plutus and
emit a Reward pointer; update the call site around stake_addr.is_script() so you
pass only Plutus reference hashes (e.g., filter reference_scripts to include
only hashes present in plutus_v1_scripts || plutus_v2_scripts ||
plutus_v3_scripts, or derive a pluts_only_reference_hashes collection from
get_reference_script_hashes) before calling is_phase_2_script(stake_hash,
plutus_v1_scripts, plutus_v2_scripts, plutus_v3_scripts,
pluts_only_reference_hashes), or alternatively modify is_phase_2_script to
distinguish native vs Plutus refs internally.

res.push(RedeemersKey {
tag: pallas_primitives::conway::RedeemerTag::Reward,
index: index as u32,
})
}
}
}
}
}
res
}

Expand Down
Loading