diff --git a/crates/uplc/src/tx/phase_one.rs b/crates/uplc/src/tx/phase_one.rs index 5b58bdf8..b64305be 100644 --- a/crates/uplc/src/tx/phase_one.rs +++ b/crates/uplc/src/tx/phase_one.rs @@ -1,13 +1,13 @@ use super::{ error::Error, - script_context::{DataLookupTable, ResolvedInput, ScriptPurpose, ScriptVersion}, + script_context::{sort_voters, DataLookupTable, ResolvedInput, ScriptPurpose, ScriptVersion}, }; use itertools::Itertools; use pallas_addresses::{Address, ScriptHash, ShelleyPaymentPart, StakePayload}; use pallas_codec::utils::Nullable; use pallas_primitives::conway::{ Certificate, GovAction, MintedTx, PolicyId, RedeemerTag, RedeemersKey, RewardAccount, - StakeCredential, TransactionOutput, + StakeCredential, TransactionOutput, Voter, }; use std::collections::HashMap; @@ -166,14 +166,29 @@ pub fn scripts_needed(tx: &MintedTx, utxos: &[ResolvedInput]) -> Result { + Some((ScriptPurpose::Voting(voter.clone()), *hash)) + } + Voter::ConstitutionalCommitteeKey(_) + | Voter::DRepKey(_) + | Voter::StakePoolKey(_) => None, + }) + .collect::() + }) + .unwrap_or_default(); needed.append(&mut spend); needed.append(&mut reward); needed.append(&mut cert); needed.append(&mut mint); needed.append(&mut propose); + needed.append(&mut voting); Ok(needed) } @@ -329,6 +344,24 @@ fn build_redeemer_key( Ok(redeemer_key) } + ScriptPurpose::Voting(v) => { + let redeemer_key = tx_body + .voting_procedures + .as_deref() + .map(|m| { + m.iter() + .sorted_by(|(a, _), (b, _)| sort_voters(a, b)) + .position(|x| &x.0 == v) + }) + .unwrap_or_default() + .map(|index| RedeemersKey { + tag: RedeemerTag::Vote, + index: index as u32, + }); + + Ok(redeemer_key) + } + ScriptPurpose::Proposing(_, procedure) => { let redeemer_key = tx_body .proposal_procedures diff --git a/crates/uplc/src/tx/script_context.rs b/crates/uplc/src/tx/script_context.rs index a73b4268..5a7792fa 100644 --- a/crates/uplc/src/tx/script_context.rs +++ b/crates/uplc/src/tx/script_context.rs @@ -8,12 +8,12 @@ use pallas_crypto::hash::Hash; use pallas_primitives::{ alonzo, conway::{ - AddrKeyhash, Certificate, Coin, DatumHash, DatumOption, GovAction, Mint, + AddrKeyhash, Certificate, Coin, DatumHash, DatumOption, GovAction, GovActionId, Mint, MintedTransactionBody, MintedTransactionOutput, MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusV1Script, PlutusV2Script, PlutusV3Script, PolicyId, PostAlonzoTransactionOutput, ProposalProcedure, PseudoDatumOption, PseudoScript, Redeemer, RedeemerTag, RedeemersKey, RequiredSigners, RewardAccount, ScriptHash, StakeCredential, - TransactionInput, TransactionOutput, Value, + TransactionInput, TransactionOutput, Value, Voter, VotingProcedure, }, }; use pallas_traverse::{ComputeHash, OriginalHash}; @@ -53,6 +53,7 @@ pub enum ScriptInfo { Spending(TransactionInput, T), Rewarding(StakeCredential), Certifying(usize, Certificate), + Voting(Voter), Proposing(usize, ProposalProcedure), } @@ -67,6 +68,7 @@ impl ScriptPurpose { } Self::Rewarding(stake_credential) => ScriptInfo::Rewarding(stake_credential), Self::Certifying(ix, certificate) => ScriptInfo::Certifying(ix, certificate), + Self::Voting(voter) => ScriptInfo::Voting(voter), Self::Proposing(ix, procedure) => ScriptInfo::Proposing(ix, procedure), } } @@ -220,7 +222,7 @@ impl TxInfoV1 { let redeemers = get_redeemers_info( &tx.transaction_witness_set, - script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[]), + script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[], &[]), )?; Ok(TxInfo::V1(TxInfoV1 { @@ -269,7 +271,7 @@ impl TxInfoV2 { let redeemers = get_redeemers_info( &tx.transaction_witness_set, - script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[]), + script_purpose_builder(&inputs[..], &mint, &certificates, &withdrawals, &[], &[]), )?; let reference_inputs = tx @@ -310,12 +312,11 @@ pub struct TxInfoV3 { pub signatories: Vec, pub redeemers: KeyValuePairs, pub data: KeyValuePairs, - pub proposal_procedures: Vec, pub id: Hash<32>, + pub votes: KeyValuePairs>, + pub proposal_procedures: Vec, pub current_treasury_amount: Option, pub treasury_donation: Option, - // TODO: - // votes : KeyValuePairs> } impl TxInfoV3 { @@ -336,6 +337,8 @@ impl TxInfoV3 { let proposal_procedures = get_proposal_procedures_info(&tx.transaction_body.proposal_procedures); + let votes = get_votes_info(&tx.transaction_body.voting_procedures); + let redeemers = get_redeemers_info( &tx.transaction_witness_set, script_purpose_builder( @@ -344,6 +347,7 @@ impl TxInfoV3 { &certificates, &withdrawals, &proposal_procedures, + &votes.iter().map(|(k, _v)| k).collect_vec()[..], ), )?; @@ -368,6 +372,7 @@ impl TxInfoV3 { data: KeyValuePairs::from(get_data_info(&tx.transaction_witness_set)), redeemers, proposal_procedures, + votes, current_treasury_amount: get_current_treasury_amount_info( &tx.transaction_body.treasury_value, ), @@ -713,12 +718,44 @@ pub fn get_redeemers_info<'a>( )) } +pub fn get_votes_info( + votes: &Option< + NonEmptyKeyValuePairs>, + >, +) -> KeyValuePairs> { + KeyValuePairs::from( + votes + .as_deref() + .map(|votes| { + votes + .iter() + .sorted_by(|(a, _), (b, _)| sort_voters(a, b)) + .cloned() + .map(|(voter, actions)| { + ( + voter, + KeyValuePairs::from( + actions + .iter() + .sorted_by(|(a, _), (b, _)| sort_gov_action_id(a, b)) + .cloned() + .collect::>(), + ), + ) + }) + .collect_vec() + }) + .unwrap_or_default(), + ) +} + fn script_purpose_builder<'a>( inputs: &'a [TxInInfo], mint: &'a MintValue, certificates: &'a [Certificate], withdrawals: &'a KeyValuePairs, proposal_procedures: &'a [ProposalProcedure], + votes: &'a [&'a Voter], ) -> impl Fn(&'a RedeemersKey) -> Result { move |redeemer: &'a RedeemersKey| { let tag = redeemer.tag; @@ -754,12 +791,16 @@ fn script_purpose_builder<'a>( }) .transpose()?, + RedeemerTag::Vote => votes + .get(index) + .cloned() + .cloned() + .map(ScriptPurpose::Voting), + RedeemerTag::Propose => proposal_procedures - .get(redeemer.index as usize) + .get(index) .cloned() .map(|p| ScriptPurpose::Proposing(index, p)), - - tag => todo!("get_script_purpose for {tag:?}"), } .ok_or(Error::ExtraneousRedeemer) } @@ -867,6 +908,18 @@ pub fn find_script( _ => Err(Error::NonScriptStakeCredential), }), + RedeemerTag::Vote => get_votes_info(&tx.transaction_body.voting_procedures) + .get(redeemer.index as usize) + .ok_or(Error::MissingScriptForRedeemer) + .and_then(|(voter, _)| match voter { + Voter::ConstitutionalCommitteeScript(hash) => Ok(hash), + Voter::ConstitutionalCommitteeKey(..) => Err(Error::NonScriptStakeCredential), + Voter::DRepScript(hash) => Ok(hash), + Voter::DRepKey(..) => Err(Error::NonScriptStakeCredential), + Voter::StakePoolKey(..) => Err(Error::NonScriptStakeCredential), + }) + .and_then(lookup_script), + RedeemerTag::Propose => { get_proposal_procedures_info(&tx.transaction_body.proposal_procedures) .get(redeemer.index as usize) @@ -884,8 +937,6 @@ pub fn find_script( }) .and_then(lookup_script) } - - RedeemerTag::Vote => todo!("find_script: RedeemerTag::Vote"), } } @@ -1006,6 +1057,35 @@ fn sort_redeemers(a: &RedeemersKey, b: &RedeemersKey) -> Ordering { } } +pub fn sort_voters(a: &Voter, b: &Voter) -> Ordering { + fn explode(voter: &Voter) -> (usize, &Hash<28>) { + match voter { + Voter::ConstitutionalCommitteeScript(hash) => (0, hash), + Voter::ConstitutionalCommitteeKey(hash) => (1, hash), + Voter::DRepScript(hash) => (2, hash), + Voter::DRepKey(hash) => (3, hash), + Voter::StakePoolKey(hash) => (4, hash), + } + } + + let (tag_a, hash_a) = explode(a); + let (tag_b, hash_b) = explode(b); + + if tag_a == tag_b { + hash_a.cmp(hash_b) + } else { + tag_a.cmp(&tag_b) + } +} + +fn sort_gov_action_id(a: &GovActionId, b: &GovActionId) -> Ordering { + if a.transaction_id == b.transaction_id { + a.action_index.cmp(&b.action_index) + } else { + a.transaction_id.cmp(&b.transaction_id) + } +} + #[cfg(test)] mod tests { use crate::{ @@ -1346,4 +1426,57 @@ mod tests { // from the Haskell ledger / cardano node. insta::assert_debug_snapshot!(script_context.to_plutus_data()); } + + #[test] + fn script_context_voting() { + let redeemer = Redeemer { + tag: RedeemerTag::Vote, + index: 0, + data: Data::constr(0, vec![Data::integer(42.into())]), + ex_units: ExUnits { + mem: 1000000, + steps: 100000000, + }, + }; + + // NOTE: The transaction also contains treasury donation and current treasury amount + let script_context = fixture_tx_info( + "84a4008182582000000000000000000000000000000000000000000000000000\ + 0000000000000000018002182a13a58200581c00000000000000000000000000\ + 000000000000000000000000000000a182582099999999999999999999999999\ + 9999999999999999999999999999999999999918988200827668747470733a2f\ + 2f61696b656e2d6c616e672e6f72675820000000000000000000000000000000\ + 00000000000000000000000000000000008202581c0000000000000000000000\ + 0000000000000000000000000000000000a38258209999999999999999999999\ + 999999999999999999999999999999999999999999008202f682582088888888\ + 88888888888888888888888888888888888888888888888888888888018202f6\ + 8258207777777777777777777777777777777777777777777777777777777777\ + 777777028202f68203581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c\ + 85966de0f2ba1ea1825820999999999999999999999999999999999999999999\ + 9999999999999999999999038200f68204581c00000000000000000000000000\ + 000000000000000000000000000000a182582099999999999999999999999999\ + 99999999999999999999999999999999999999048201f68201581c43fa47afc6\ + 8a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1ea18258209999999999\ + 999999999999999999999999999999999999999999999999999999018201f6a2\ + 0582840402d87980821a000f42401a05f5e100840400d87981182a821a000f42\ + 401a05f5e1000781587d587b0101003232323232323225333333008001153330\ + 033370e900018029baa001153330073006375400224a66600894452615330054\ + 911856616c696461746f722072657475726e65642066616c7365001365600200\ + 2002002002002153300249010b5f746d70303a20566f696400165734ae7155ce\ + aab9e5573eae91f5f6", + "8182582000000000000000000000000000000000000000000000000000000000\ + 0000000000", + "81a200581d600000000000000000000000000000000000000000000000000000\ + 0000011a000f4240", + ) + .into_script_context(&redeemer, None) + .unwrap(); + + // NOTE: The initial snapshot has been generated using the Haskell + // implementation of the ledger library for that same serialized + // transactions. It is meant to control that our construction of the + // script context and its serialization matches exactly those + // from the Haskell ledger / cardano node. + insta::assert_debug_snapshot!(script_context.to_plutus_data()); + } } diff --git a/crates/uplc/src/tx/snapshots/uplc__tx__script_context__tests__script_context_voting.snap b/crates/uplc/src/tx/snapshots/uplc__tx__script_context__tests__script_context_voting.snap new file mode 100644 index 00000000..70312288 --- /dev/null +++ b/crates/uplc/src/tx/snapshots/uplc__tx__script_context__tests__script_context_voting.snap @@ -0,0 +1,1317 @@ +--- +source: crates/uplc/src/tx/script_context.rs +expression: script_context.to_plutus_data() +--- +Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Array( + [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 0, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ], + }, + ), + Map( + Def( + [ + ( + BoundedBytes( + BoundedBytes( + [], + ), + ), + Map( + Def( + [ + ( + BoundedBytes( + BoundedBytes( + [], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 1000000, + }, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ], + }, + ), + ], + }, + ), + ], + ), + Array( + [], + ), + Array( + [], + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 42, + }, + ), + ), + ), + Map( + Def( + [], + ), + ), + Array( + [], + ), + Map( + Def( + [], + ), + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 123, + any_constructor: None, + fields: [], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ], + }, + ), + ], + }, + ), + Array( + [], + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 125, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 67, + 250, + 71, + 175, + 198, + 138, + 121, + 19, + 251, + 226, + 244, + 0, + 227, + 132, + 156, + 180, + 146, + 217, + 162, + 97, + 12, + 133, + 150, + 109, + 224, + 242, + 186, + 30, + ], + ), + ), + ], + }, + ), + ], + }, + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BigInt( + Int( + Int( + Int { + neg: false, + val: 42, + }, + ), + ), + ), + ], + }, + ), + ), + ( + Constr( + Constr { + tag: 125, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 67, + 250, + 71, + 175, + 198, + 138, + 121, + 19, + 251, + 226, + 244, + 0, + 227, + 132, + 156, + 180, + 146, + 217, + 162, + 97, + 12, + 133, + 150, + 109, + 224, + 242, + 186, + 30, + ], + ), + ), + ], + }, + ), + ], + }, + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + Map( + Def( + [], + ), + ), + BoundedBytes( + BoundedBytes( + [ + 224, + 66, + 234, + 62, + 149, + 207, + 129, + 86, + 132, + 46, + 53, + 99, + 151, + 120, + 68, + 27, + 236, + 175, + 60, + 134, + 44, + 185, + 69, + 158, + 231, + 75, + 68, + 174, + 235, + 136, + 198, + 115, + ], + ), + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 67, + 250, + 71, + 175, + 198, + 138, + 121, + 19, + 251, + 226, + 244, + 0, + 227, + 132, + 156, + 180, + 146, + 217, + 162, + 97, + 12, + 133, + 150, + 109, + 224, + 242, + 186, + 30, + ], + ), + ), + ], + }, + ), + ], + }, + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 1, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + ), + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ), + ), + ], + }, + ), + ], + }, + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 152, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + ), + ( + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 67, + 250, + 71, + 175, + 198, + 138, + 121, + 19, + 251, + 226, + 244, + 0, + 227, + 132, + 156, + 180, + 146, + 217, + 162, + 97, + 12, + 133, + 150, + 109, + 224, + 242, + 186, + 30, + ], + ), + ), + ], + }, + ), + ], + }, + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 3, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + ), + ( + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ), + ), + ], + }, + ), + ], + }, + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 2, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 123, + any_constructor: None, + fields: [], + }, + ), + ), + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + 136, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 1, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 123, + any_constructor: None, + fields: [], + }, + ), + ), + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 0, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 123, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + ), + ( + Constr( + Constr { + tag: 123, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + ), + ), + ], + }, + ), + Map( + Def( + [ + ( + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + 153, + ], + ), + ), + BigInt( + Int( + Int( + Int { + neg: false, + val: 4, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ), + ], + ), + ), + ), + ], + ), + ), + Array( + [], + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [], + }, + ), + ], + }, + ), + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + BigInt( + Int( + Int( + Int { + neg: false, + val: 42, + }, + ), + ), + ), + ], + }, + ), + Constr( + Constr { + tag: 125, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 121, + any_constructor: None, + fields: [ + Constr( + Constr { + tag: 122, + any_constructor: None, + fields: [ + BoundedBytes( + BoundedBytes( + [ + 67, + 250, + 71, + 175, + 198, + 138, + 121, + 19, + 251, + 226, + 244, + 0, + 227, + 132, + 156, + 180, + 146, + 217, + 162, + 97, + 12, + 133, + 150, + 109, + 224, + 242, + 186, + 30, + ], + ), + ), + ], + }, + ), + ], + }, + ), + ], + }, + ), + ], + }, +) diff --git a/crates/uplc/src/tx/to_plutus_data.rs b/crates/uplc/src/tx/to_plutus_data.rs index 7dc4d78e..52fc6d3f 100644 --- a/crates/uplc/src/tx/to_plutus_data.rs +++ b/crates/uplc/src/tx/to_plutus_data.rs @@ -18,7 +18,8 @@ use pallas_primitives::conway::{ AssetName, BigInt, Certificate, Coin, Constitution, Constr, DRep, DRepVotingThresholds, DatumOption, ExUnitPrices, ExUnits, GovAction, GovActionId, Mint, PlutusData, PolicyId, PoolVotingThresholds, ProposalProcedure, ProtocolParamUpdate, PseudoScript, RationalNumber, - Redeemer, ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value, + Redeemer, ScriptRef, StakeCredential, TransactionInput, TransactionOutput, Value, Vote, Voter, + VotingProcedure, }; use pallas_traverse::ComputeHash; @@ -953,6 +954,9 @@ impl ToPlutusData for ScriptPurpose { ScriptPurpose::Certifying(ix, dcert) => { wrap_multiple_with_constr(3, vec![ix.to_plutus_data(), dcert.to_plutus_data()]) } + ScriptPurpose::Voting(voter) => { + wrap_multiple_with_constr(4, vec![voter.to_plutus_data()]) + } ScriptPurpose::Proposing(ix, procedure) => { wrap_multiple_with_constr(5, vec![ix.to_plutus_data(), procedure.to_plutus_data()]) } @@ -1293,6 +1297,42 @@ impl<'a> ToPlutusData for WithWrappedStakeCredential<'a, KeyValuePairs PlutusData { + match self { + Voter::ConstitutionalCommitteeScript(hash) => { + wrap_with_constr(0, StakeCredential::Scripthash(*hash).to_plutus_data()) + } + Voter::ConstitutionalCommitteeKey(hash) => { + wrap_with_constr(0, StakeCredential::AddrKeyhash(*hash).to_plutus_data()) + } + Voter::DRepScript(hash) => { + wrap_with_constr(1, StakeCredential::Scripthash(*hash).to_plutus_data()) + } + Voter::DRepKey(hash) => { + wrap_with_constr(1, StakeCredential::AddrKeyhash(*hash).to_plutus_data()) + } + Voter::StakePoolKey(hash) => wrap_with_constr(2, hash.to_plutus_data()), + } + } +} + +impl ToPlutusData for VotingProcedure { + fn to_plutus_data(&self) -> PlutusData { + self.vote.to_plutus_data() + } +} + +impl ToPlutusData for Vote { + fn to_plutus_data(&self) -> PlutusData { + match self { + Vote::No => empty_constr(0), + Vote::Yes => empty_constr(1), + Vote::Abstain => empty_constr(2), + } + } +} + impl ToPlutusData for ScriptInfo where T: ToPlutusData, @@ -1309,6 +1349,7 @@ where ScriptInfo::Certifying(ix, dcert) => { wrap_multiple_with_constr(3, vec![ix.to_plutus_data(), dcert.to_plutus_data()]) } + ScriptInfo::Voting(voter) => wrap_multiple_with_constr(4, vec![voter.to_plutus_data()]), ScriptInfo::Proposing(ix, procedure) => { wrap_multiple_with_constr(5, vec![ix.to_plutus_data(), procedure.to_plutus_data()]) } @@ -1370,7 +1411,7 @@ impl ToPlutusData for TxInfo { tx_info.redeemers.to_plutus_data(), tx_info.data.to_plutus_data(), tx_info.id.to_plutus_data(), - Data::map(vec![]), // TODO tx_info.votes :: Map Voter (Map GovernanceActionId Vote) + tx_info.votes.to_plutus_data(), tx_info.proposal_procedures.to_plutus_data(), tx_info.current_treasury_amount.to_plutus_data(), tx_info.treasury_donation.to_plutus_data(), diff --git a/examples/acceptance_tests/script_context/v3/README.md b/examples/acceptance_tests/script_context/v3/README.md index 856efe2c..49eea575 100644 --- a/examples/acceptance_tests/script_context/v3/README.md +++ b/examples/acceptance_tests/script_context/v3/README.md @@ -9,14 +9,36 @@ Because we can't have fully static context (since they contain the validator and its hash), we define _templates_. Everything is a bit clunky, but steps have been captured in a `test.sh` script -for convenience. +for convenience. The test still assumes a few things. For any +`VALIDATOR_GROUP`: + +- There's a `ctx/{VALIDATOR_GROUP}/tx.template` and + `ctx/{VALIDATOR_GROUP}/resolved_inputs.template` respectively. + +- There's a corresponding validator `validators/{VALIDATOR_GROUP}.ak` + +- Templates may reference variables using a mustache-template-like syntax `{{ ... }}`. + Provided variables are: + - `{VALIDATOR_GROUP}.{VALIDATOR_TITLE}.hash` + - `{VALIDATOR_GROUP}.{VALIDATOR_TITLE}.cbor` + + Where `VALIDATOR_TITLE` corresponds to the validator Aiken's name. ## How to use ``` -./test.sh [VALIDATOR_TITLE] +./test.sh VALIDATOR_GROUP ``` +> ![TIP] +> By default, this recompiles the project in --release mode, which can be long +> when iterating / testing. You can provide a binary to use as a second +> argument. For a dev build, just do: +> +> ``` +> ./test.sh VALIDATOR_GROUP "cargo run --" +> ``` + ## Test Coverage - Purpose @@ -24,7 +46,7 @@ for convenience. - [x] mint - [ ] withdraw - [x] publish - - [ ] voting + - [x] voting - [x] proposing - Transaction body @@ -66,7 +88,7 @@ for convenience. - [x] datums - votes - [x] none - - [ ] some + - [x] some - proposal procedures - [x] none - [x] some @@ -122,14 +144,14 @@ for convenience. - [x] info action - Vote - - [ ] No - - [ ] Yes - - [ ] Abstain + - [x] No + - [x] Yes + - [x] Abstain - Voter - - [ ] CC - - [ ] DRep - - [ ] SPO + - [x] CC + - [x] DRep + - [x] SPO - ChangedParameters - [x] txFeePerByte diff --git a/examples/acceptance_tests/script_context/v3/ctx/voting/resolved_inputs.template b/examples/acceptance_tests/script_context/v3/ctx/voting/resolved_inputs.template new file mode 100644 index 00000000..dc99bdb5 --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/ctx/voting/resolved_inputs.template @@ -0,0 +1,5 @@ +[ + { 0: h'6000000000000000000000000000000000000000000000000000000000' + , 1: 1000000 + } +] diff --git a/examples/acceptance_tests/script_context/v3/ctx/voting/tx.template b/examples/acceptance_tests/script_context/v3/ctx/voting/tx.template new file mode 100644 index 00000000..8fef1ee7 --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/ctx/voting/tx.template @@ -0,0 +1,52 @@ + +[ + { 0: + [ [h'0000000000000000000000000000000000000000000000000000000000000000', 0] + ] + + , 1: + [] + + , 2: 42 + + , 19: + { [ 0, h'00000000000000000000000000000000000000000000000000000000' ]: + { [ h'9999999999999999999999999999999999999999999999999999999999999999', 152 ]: + [ 0 + , [ "https://aiken-lang.org" + , h'0000000000000000000000000000000000000000000000000000000000000000' + ] + ] + } + + , [ 2, h'00000000000000000000000000000000000000000000000000000000' ]: + { [ h'9999999999999999999999999999999999999999999999999999999999999999', 0 ]: [ 2, null ] + , [ h'8888888888888888888888888888888888888888888888888888888888888888', 1 ]: [ 2, null ] + , [ h'7777777777777777777777777777777777777777777777777777777777777777', 2 ]: [ 2, null ] + } + + , [ 3, h'{{ voting.script.hash }}' ]: + { [ h'9999999999999999999999999999999999999999999999999999999999999999', 3 ]: [ 0, null ] + } + + , [ 4, h'00000000000000000000000000000000000000000000000000000000' ]: + { [ h'9999999999999999999999999999999999999999999999999999999999999999', 4 ]: [ 1, null ] + } + + , [ 1, h'{{ voting.script.hash }}' ]: + { [ h'9999999999999999999999999999999999999999999999999999999999999999', 1 ]: [ 1, null ] + } + } + }, + + { 5: [ [4, 2, 121([]), [1000000, 100000000]] + , [4, 0, 121([42]), [1000000, 100000000]] + ] + + , 7: [h'{{ voting.script.cbor }}'] + }, + + true, + + null +] diff --git a/examples/acceptance_tests/script_context/v3/plutus.json b/examples/acceptance_tests/script_context/v3/plutus.json index e0e1a335..e186715d 100644 --- a/examples/acceptance_tests/script_context/v3/plutus.json +++ b/examples/acceptance_tests/script_context/v3/plutus.json @@ -6,7 +6,7 @@ "plutusVersion": "v3", "compiler": { "name": "Aiken", - "version": "v1.0.31-alpha+81c0152" + "version": "v1.0.31-alpha+bfc93bf" }, "license": "Apache-2.0" }, @@ -82,6 +82,17 @@ }, "compiledCode": "59099f010100323232323232323232323232322533333300e00115332330073001300937540042a66601660146ea8008494cccccc04000454ccc020c008c028dd50008a99980618059baa001125333009323232323253330123758600660226ea8c014c044dd50030a9998091bac3014301530113754600a60226ea801854ccc048dd6180a180a980a980a980a980a980a980a980a98089baa30053011375400c2a66601c66e1d2054375a6028602a602a602a60226ea8c014c044dd5003099299980799baf4c101a0003015301630163016301630163016301630163016301630123754600c60246ea801c5288a9980824932657870656374205b5d203d3d20646963742e746f5f7061697273286374782e7472616e73616374696f6e2e646174756d73290016533300e3375e98012fa1d87a9fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffffd8798000301430153015301530153015301530153015301530113754600a60226ea8018526153300f491e86578706563740a202020205b0a20202020202050616972285370656e64280a20202020202020204f75747075745265666572656e6365207b0a202020202020202020207472616e73616374696f6e5f69643a20232230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030222c0a202020202020202020206f75747075745f696e6465783a20302c0a20202020202020207d2c0a202020202020292c0a202020202020766f69642829292c0a202020205d203d3d2072656465656d6572730016153300f49120657870656374203432203d3d206374782e7472616e73616374696f6e2e6665650016153300f4912d657870656374205b5d203d206374782e7472616e73616374696f6e2e65787472615f7369676e61746f726965730016153300f4912c657870656374205b5d203d206374782e7472616e73616374696f6e2e7265666572656e63655f696e707574730016153300f49123657870656374205b5d203d206374782e7472616e73616374696f6e2e6f757470757473001632533301200100c132533301330160021323232325333013300d375a603260340042646464646464a66603266e1d2004301b3754603e0042a66466034600260386ea800854ccc068cdc7804245200000000000000000000000000000000000000000000000000000000000000000001533301a3375e6e98010dd3299980d25014bd6f7b6300991919800800a5eb7bdb180894ccc0840044cc088cdd82601014000374c00697adef6c60132323232533302133720910100002133026337609801014000374c00e00a2a66604266e3d22100002133026337609801014000374c00e00626604c66ec0dd48011ba600133006006003375660460066eb8c084008c094008c08c004c8cc0040052f5bded8c044a66604000226604266ec130010140004c01051a3b9aca00004bd6f7b630099191919299981019b90489000021330253376098010140004c01051a3b9aca0000005153330203371e9101000021330253376098010140004c01051a3b9aca0000003133025337606ea4008dd4000998030030019bad3022003375c60400046048004604400226464a6660386006603c6ea8c08800854ccc070c00cc078dd50008a4c03603660426044002603a6ea801854cc06d24013f657870656374207265736f6c7665645f696e7075745f76616c7565203d3d206173736574732e66726f6d5f6c6f76656c6163652831303030303030303030290016153301b491606578706563740a202020207472616e73616374696f6e5f6964203d3d20232230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030220016370e900100b80b980f180f800980f0011bab301c001301c002301a001301637540060226eb8c060004c050dd5180b801180b180b80098091baa00100d30140013758600860206ea8c010c040dd5002a99980619baf4c0132d87a9fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd87980ffff003001300f37540082930a99806a49cd6578706563740a202020205370656e64696e67280a2020202020204f75747075745265666572656e6365207b0a20202020202020207472616e73616374696f6e5f69643a20232230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030222c0a20202020202020206f75747075745f696e6465783a20302c0a2020202020207d2c0a202020202020536f6d6528766f69642829292c0a2020202029203d3d20696e666f00162301230133013001533300a3371e910120c6fbd346681a8f8337f6b3e51e6ec973f1509367eabc3a44c849af58a1d8471b00375c602060226022602260226022602260226022602260226022601a6ea8c004c034dd50010a4c2a66016921546578706563740a2020202023226336666264333436363831613866383333376636623365353165366563393733663135303933363765616263336134346338343961663538613164383437316222203d3d206964001623010001149854cc0292411856616c696461746f722072657475726e65642066616c73650013656004004004004004004004370e90000018018018018018a99802a4810b5f746d70323a20566f6964001615330044910b5f746d70313a20566f696400161533003491ff657870656374205b0a20202020496e707574207b0a2020202020206f75747075745f7265666572656e63653a204f75747075745265666572656e6365207b207472616e73616374696f6e5f69642c206f75747075745f696e6465783a2030207d2c0a2020202020206f75747075743a204f7574707574207b0a2020202020202020616464726573732c0a202020202020202076616c75653a207265736f6c7665645f696e7075745f76616c75652c0a2020202020202020646174756d3a20496e6c696e65446174756d285f292c0a20202020202020207265666572656e63655f7363726970743a204e6f6e652c0a2020202020207d2c0a202020207d2c0a200b205d203d20696e7075747300161533002491566578706563742041646472657373207b207061796d656e745f63726564656e7469616c3a20536372697074285f292c207374616b655f63726564656e7469616c3a204e6f6e65207d203d0a202020206164647265737300165734ae7155ceaab9e5573eae815d0aba257481", "hash": "cef0769e42ca13a9ffcd71a6ed03e3e03adb72f4cdf81bb28137c55a" + }, + { + "title": "voting.script", + "redeemer": { + "title": "_tmp0", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "compiledCode": "5908e2010100323232323232323232323232322533333300e00115332330073001300937540042a66601660146ea8008494ccc020c8c8c8c8c94ccc034c01cc03cdd500089919192999808180518091baa0021533233011300130133754602e60286ea800c54ccc0540080344c94ccc058c06400c4c8c8c94ccc054c014dd6980d980e0010a99980a9802980b9baa003153330153371e002910120999999999999999999999999999999999999999999999999999999999999999900153330153375e98158d8799f9fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa1d8799f582099999999999999999999999999999999999999999999999999999999999999991898ffd87980ffff003300a00c480084c94ccc058c040c060dd50008991919299980c9804980d9baa002153330193009301b3754603e60386ea800854ccc07400405854ccc064cdd7a61abd8799f9fd87a9fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa3d8799f5820777777777777777777777777777777777777777777777777777777777777777702ffd87b80d8799f5820888888888888888888888888888888888888888888888888888888888888888801ffd87b80d8799f5820999999999999999999999999999999999999999999999999999999999999999900ffd87b80ffff003300e010480184c94ccc068c050c070dd50008991919299980e99b8748010c07cdd5001099299981100100e0a99980f19b8f0014891c000000000000000000000000000000000000000000000000000000000014a22a6603e9211865787065637420706f6f6c5f6964203d3d206f6e6c7930730016375c604660406ea800806cdd5980f801180e8009919bb030210013021302200137586040603a6ea8004060cc0380412008153301a491ff6578706563740a202020202020536f6d65280a2020202020202020506169722844656c6567617465526570726573656e74617469766528566572696669636174696f6e4b6579286f6e6c79307329292c0a20202020202020205b0a202020202020202020205061697228476f7665726e616e6365416374696f6e4964207b207472616e73616374696f6e3a206f6e6c7937732c2070726f706f73616c5f70726f6365647572653a2032207d2c0a202020202020202020204162737461696e292c0a202020202020202020205061697228476f7665726e616e6365416374696f6e4964207b207472616e73616374696f6e3a206f6e6c7938732c2070726f706fb673616c5f70726f6365647572653a2031207d2c0a202020202020202020204162737461696e292c0a202020202020202020205061697228476f7665726e616e6365416374696f6e4964207b207472616e73616374696f6e3a206f6e6c7939732c2070726f706f73616c5f70726f6365647572653a2030207d2c0a202020202020202020204162737461696e292c0a20202020202020205d292c0a20202020202029203d3d206c6973742e617428766f7465732c20332900160160163756603600460320026466ec0c074004c074c078004dd6180e180c9baa0010133300a00c4801054cc0592401ff6578706563740a202020202020536f6d65280a20202020202020205061697228436f6e737469747574696f6e616c436f6d6d69747465654d656d62657228566572696669636174696f6e4b6579286f6e6c79307329292c0a20202020202020205b0a202020202020202020205061697228476f7665726e616e6365416374696f6e4964207b0a2020202020202020202020207472616e73616374696f6e3a206f6e6c7939732c0a20202020202020202020202070726f706f73616c5f70726f6365647572653a203135322c0a202020202020202020207d2c0a202020202020202020204e6f292c0a20202020202020205d292c0a20202020202029203d3d20116c6973742e617428766f7465732c203129001615330164911c657870656374207472616e73616374696f6e203d3d206f6e6c7939730016011011375c6034002602c6ea8c054008c054004038c05c008dc3a40040180186eacc048008c040004c8cdd8180a000980a180a8009bac301330103754002012660020069000180080091129998088010a60103d87a800013232533300f300900313374a90001980a1ba73301430110023301430120024bd7025eb804ccc014014004cdc0001a4002602a00660260046eacc03cc040c040c040c040c040c040c040c040c040c040c040c040c030dd5180798061baa0023253330093253333330120021533300a3004300c3754004264a66601e002004264a666666028002006006006264a66602260280062a66601a66e1d205400114a20080086eb400400cc044004c034dd500100080080080080089929999998090010a999805180218061baa0021533300e300d375400429440040040040040040045280a4c2a660149210f6578706563742069735f76616c69640016300e300f300b37540022930a99804a491856616c696461746f722072657475726e65642066616c73650013656006370e90000028028028028028a99802a481ab65787065637420536f6d65280a2020202020205061697228436f6e737469747574696f6e616c436f6d6d69747465654d656d62657228536372697074282e2e29292c0a2020202020205b5061697228476f7665726e616e6365416374696f6e4964207b207472616e73616374696f6e2c2070726f706f73616c5f70726f6365647572653a2031207d2c20596573295d292c0a2020202029203d206c6973742e617428766f7465732c203029001615330044915865787065637420536f6d6528506169722844656c6567617465526570726573656e74617469766528536372697074282e2e29292c205b5f2c202e2e5d2929203d0a2020202020206c6973742e617428766f7465732c203229001615330034914265787065637420536f6d652850616972285374616b65506f6f6c28706f6f6c5f6964292c205b5f2c202e2e5d2929203d206c6973742e617428766f7465732c203429001615330024910b5f746d70303a20566f696400165734ae7155ceaab9e5573eae815d0aba257481", + "hash": "849324b02de5c5d1cbf2226bd321e7ca5200de4a4c314864dbc24813" } ], "definitions": { diff --git a/examples/acceptance_tests/script_context/v3/test.sh b/examples/acceptance_tests/script_context/v3/test.sh index 98aaa6a3..cdd30e57 100755 --- a/examples/acceptance_tests/script_context/v3/test.sh +++ b/examples/acceptance_tests/script_context/v3/test.sh @@ -56,8 +56,11 @@ for ITEM in ${VALIDATORS[@]}; do done echo $RESOLVED_INPUTS | cbor-diag --to hex --from diag > ctx/$TITLE/resolved_inputs.cbor + echo $TRANSACTION | cbor-diag --to hex --from diag > ctx/$TITLE/tx.cbor +# ogmios inspect transaction $(cat ctx/$TITLE/tx.cbor) | jq ".votes" + $AIKEN tx simulate \ ctx/$TITLE/tx.cbor \ ctx/inputs.cbor \ diff --git a/examples/acceptance_tests/script_context/v3/validators/voting.ak b/examples/acceptance_tests/script_context/v3/validators/voting.ak new file mode 100644 index 00000000..9641316c --- /dev/null +++ b/examples/acceptance_tests/script_context/v3/validators/voting.ak @@ -0,0 +1,83 @@ +use aiken/collection/list +use cardano/credential.{Script, VerificationKey} +use cardano/governance.{ + Abstain, ConstitutionalCommitteeMember, DelegateRepresentative, + GovernanceActionId, No, StakePool, Yes, +} +use cardano/transaction.{ScriptContext} + +type Foo { + Foo(Int) +} + +const only0s = #"00000000000000000000000000000000000000000000000000000000" + +const only7s = + #"7777777777777777777777777777777777777777777777777777777777777777" + +const only8s = + #"8888888888888888888888888888888888888888888888888888888888888888" + +const only9s = + #"9999999999999999999999999999999999999999999999999999999999999999" + +validator { + fn script(_tmp0: Void, ctx: ScriptContext) { + assert_redeemer(ctx.redeemer) + + let votes = ctx.transaction.votes + + expect Some( + Pair(ConstitutionalCommitteeMember(Script(..)), + [Pair(GovernanceActionId { transaction, proposal_procedure: 1 }, Yes)]), + ) = list.at(votes, 0) + expect transaction == only9s + + expect + Some( + Pair(ConstitutionalCommitteeMember(VerificationKey(only0s)), + [ + Pair(GovernanceActionId { + transaction: only9s, + proposal_procedure: 152, + }, + No), + ]), + ) == list.at(votes, 1) + + expect Some(Pair(DelegateRepresentative(Script(..)), [_, ..])) = + list.at(votes, 2) + + expect + Some( + Pair(DelegateRepresentative(VerificationKey(only0s)), + [ + Pair(GovernanceActionId { transaction: only7s, proposal_procedure: 2 }, + Abstain), + Pair(GovernanceActionId { transaction: only8s, proposal_procedure: 1 }, + Abstain), + Pair(GovernanceActionId { transaction: only9s, proposal_procedure: 0 }, + Abstain), + ]), + ) == list.at(votes, 3) + + expect Some(Pair(StakePool(pool_id), [_, ..])) = list.at(votes, 4) + expect pool_id == only0s + + True + } +} + +fn assert_redeemer(data: Data) { + let is_valid = + if data is Foo(42): Foo { + True + } else if data is Void { + True + } else { + False + } + + expect is_valid + Void +}