From 4fbca7b8cefe6a1744773e5993e50e153e27aa86 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 15 Jul 2024 20:47:25 -0400 Subject: [PATCH] fix(interop): map missing u5c redeemers (#490) --- pallas-traverse/Cargo.toml | 1 + pallas-traverse/src/tx.rs | 22 +++++++++++++ pallas-traverse/src/witnesses.rs | 20 ++++++++++++ pallas-utxorpc/Cargo.toml | 1 + pallas-utxorpc/src/lib.rs | 55 ++++++++++++++++++++++++-------- test_data/u5c1.json | 10 +++++- 6 files changed, 95 insertions(+), 14 deletions(-) diff --git a/pallas-traverse/Cargo.toml b/pallas-traverse/Cargo.toml index 2660c8c..7b0acfb 100644 --- a/pallas-traverse/Cargo.toml +++ b/pallas-traverse/Cargo.toml @@ -18,6 +18,7 @@ pallas-codec = { version = "=0.28.0", path = "../pallas-codec" } hex = "0.4.3" thiserror = "1.0.31" paste = "1.0.14" +itertools = "0.13.0" # TODO: remove once GenesisValue moves into new genesis crate serde = "1.0.155" diff --git a/pallas-traverse/src/tx.rs b/pallas-traverse/src/tx.rs index aa0b967..d427980 100644 --- a/pallas-traverse/src/tx.rs +++ b/pallas-traverse/src/tx.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, collections::HashSet, ops::Deref}; +use itertools::Itertools; use pallas_codec::{minicbor, utils::KeepRaw}; use pallas_crypto::hash::Hash; use pallas_primitives::{ @@ -209,6 +210,27 @@ impl<'b> MultiEraTx<'b> { raw } + pub fn mints_sorted_set(&self) -> Vec { + let mut raw = self.mints(); + + raw.sort_by_key(|m| *m.policy()); + + raw + } + + pub fn withdrawals_sorted_set(&self) -> Vec<(&[u8], u64)> { + match self.withdrawals() { + MultiEraWithdrawals::NotApplicable | MultiEraWithdrawals::Empty => { + std::iter::empty().collect() + } + MultiEraWithdrawals::AlonzoCompatible(x) => x + .iter() + .map(|(k, v)| (k.as_slice(), *v)) + .sorted_by_key(|(k, _)| *k) + .collect(), + } + } + /// Return the transaction reference inputs /// /// NOTE: It is possible for this to return duplicates. See diff --git a/pallas-traverse/src/witnesses.rs b/pallas-traverse/src/witnesses.rs index 7f78ed5..b681c03 100644 --- a/pallas-traverse/src/witnesses.rs +++ b/pallas-traverse/src/witnesses.rs @@ -161,6 +161,26 @@ impl<'b> MultiEraTx<'b> { }) } + pub fn find_mint_redeemer(&self, mint_order: u32) -> Option { + self.redeemers().into_iter().find(|r| { + r.tag() == pallas_primitives::conway::RedeemerTag::Mint && r.index() == mint_order + }) + } + + pub fn find_withdrawal_redeemer(&self, withdrawal_order: u32) -> Option { + self.redeemers().into_iter().find(|r| { + r.tag() == pallas_primitives::conway::RedeemerTag::Reward + && r.index() == withdrawal_order + }) + } + + pub fn find_certificate_redeemer(&self, certificate_order: u32) -> Option { + self.redeemers().into_iter().find(|r| { + r.tag() == pallas_primitives::conway::RedeemerTag::Cert + && r.index() == certificate_order + }) + } + pub fn plutus_v2_scripts(&self) -> &[PlutusV2Script] { match self { Self::Byron(_) => &[], diff --git a/pallas-utxorpc/Cargo.toml b/pallas-utxorpc/Cargo.toml index d5b84ea..b219218 100644 --- a/pallas-utxorpc/Cargo.toml +++ b/pallas-utxorpc/Cargo.toml @@ -21,4 +21,5 @@ utxorpc-spec = { version = "0.7.0" } [dev-dependencies] hex = "0.4.3" serde_json = "1.0.120" +pretty_assertions = "1.4.0" # utxorpc-spec = { path = "../../../utxorpc/spec/gen/rust" } diff --git a/pallas-utxorpc/src/lib.rs b/pallas-utxorpc/src/lib.rs index 06ef5f5..516d714 100644 --- a/pallas-utxorpc/src/lib.rs +++ b/pallas-utxorpc/src/lib.rs @@ -191,7 +191,12 @@ impl Mapper { } } - pub fn map_cert(&self, x: &trv::MultiEraCert) -> u5c::Certificate { + pub fn map_cert( + &self, + x: &trv::MultiEraCert, + tx: &trv::MultiEraTx, + order: u32, + ) -> u5c::Certificate { let inner = match x.as_alonzo().unwrap() { babbage::Certificate::StakeRegistration(a) => { u5c::certificate::Certificate::StakeRegistration(self.map_stake_credential(a)) @@ -279,15 +284,24 @@ impl Mapper { u5c::Certificate { certificate: inner.into(), - redeemer: None, // TODO + redeemer: tx + .find_certificate_redeemer(order) + .map(|r| self.map_redeemer(&r)), } } - pub fn map_withdrawals(&self, x: &(&[u8], u64)) -> u5c::Withdrawal { + pub fn map_withdrawals( + &self, + x: &(&[u8], u64), + tx: &trv::MultiEraTx, + order: u32, + ) -> u5c::Withdrawal { u5c::Withdrawal { reward_account: Vec::from(x.0).into(), coin: x.1, - redeemer: None, // TODO + redeemer: tx + .find_withdrawal_redeemer(order) + .map(|x| self.map_redeemer(&x)), } } @@ -303,7 +317,7 @@ impl Mapper { u5c::Multiasset { policy_id: x.policy().to_vec().into(), assets: x.assets().iter().map(|x| self.map_asset(x)).collect(), - redeemer: None, // TODO + redeemer: None, } } @@ -534,17 +548,31 @@ impl Mapper { .map(|(order, i)| self.map_tx_input(i, tx, order as u32, &resolved)) .collect(), outputs: tx.outputs().iter().map(|x| self.map_tx_output(x)).collect(), - certificates: tx.certs().iter().map(|x| self.map_cert(x)).collect(), - withdrawals: tx - .withdrawals() - .collect::>() + certificates: tx + .certs() .iter() - .map(|x| self.map_withdrawals(x)) + .enumerate() + .map(|(order, x)| self.map_cert(x, tx, order as u32)) + .collect(), + withdrawals: tx + .withdrawals_sorted_set() + .iter() + .enumerate() + .map(|(order, x)| self.map_withdrawals(x, tx, order as u32)) .collect(), mint: tx - .mints() + .mints_sorted_set() .iter() - .map(|x| self.map_policy_assets(x)) + .enumerate() + .map(|(order, x)| { + let mut ma = self.map_policy_assets(x); + + ma.redeemer = tx + .find_mint_redeemer(order as u32) + .map(|r| self.map_redeemer(&r)); + + ma + }) .collect(), reference_inputs: tx .reference_inputs() @@ -619,6 +647,7 @@ impl Mapper { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; #[derive(Clone)] struct NoLedger; @@ -642,7 +671,7 @@ mod tests { let current = serde_json::json!(mapper.map_block(&block)); let expected: serde_json::Value = serde_json::from_str(&json_str).unwrap(); - assert_eq!(current, expected) + assert_eq!(expected, current) } } } diff --git a/test_data/u5c1.json b/test_data/u5c1.json index 1f881cd..153bfc7 100644 --- a/test_data/u5c1.json +++ b/test_data/u5c1.json @@ -1341,7 +1341,15 @@ "mint": [ { "assets": [{ "mintCoin": "5000000000", "name": "VFVOQQ==" }], - "policyId": "J5+ELDPu2QVLnjxwzWo7MimCWcJLeLiVy0HZGg==" + "policyId": "J5+ELDPu2QVLnjxwzWo7MimCWcJLeLiVy0HZGg==", + "redeemer": { + "datum": { + "constr": { + "tag": 121 + } + }, + "purpose": "REDEEMER_PURPOSE_MINT" + } } ], "outputs": [