diff --git a/chain/rust/src/builders/certificate_builder.rs b/chain/rust/src/builders/certificate_builder.rs index f20701e1..677ccd3d 100644 --- a/chain/rust/src/builders/certificate_builder.rs +++ b/chain/rust/src/builders/certificate_builder.rs @@ -183,9 +183,14 @@ pub fn add_cert_vkeys( vkeys.insert(*hash); } }, - Certificate::RegDrepCert(_cert) => { - // does not need a witness - } + Certificate::RegDrepCert(cert) => match &cert.drep_credential { + StakeCredential::Script { hash, .. } => { + return Err(CertBuilderError::ExpectedKeyHash(*hash)) + } + StakeCredential::PubKey { hash, .. } => { + vkeys.insert(*hash); + } + }, Certificate::UnregDrepCert(cert) => match &cert.drep_credential { StakeCredential::Script { hash, .. } => { return Err(CertBuilderError::ExpectedKeyHash(*hash)) diff --git a/chain/rust/src/builders/input_builder.rs b/chain/rust/src/builders/input_builder.rs index 5687f097..b17f7d03 100644 --- a/chain/rust/src/builders/input_builder.rs +++ b/chain/rust/src/builders/input_builder.rs @@ -2,6 +2,7 @@ use crate::builders::witness_builder::{InputAggregateWitnessData, PartialPlutusW use super::{ tx_builder::TransactionUnspentOutput, + utils::required_wits_from_required_signers, witness_builder::{NativeScriptWitnessInfo, RequiredWitnessSet}, }; @@ -138,11 +139,7 @@ impl SingleInputBuilder { required_signers: RequiredSigners, datum: Option, ) -> Result { - let mut required_wits = RequiredWitnessSet::default(); - required_signers - .as_ref() - .iter() - .for_each(|required_signer| required_wits.add_vkey_key_hash(*required_signer)); + let mut required_wits = required_wits_from_required_signers(&required_signers); input_required_wits(&self.utxo_info, &mut required_wits); let mut required_wits_left = required_wits.clone(); diff --git a/chain/rust/src/builders/mod.rs b/chain/rust/src/builders/mod.rs index 30911f48..13f63202 100644 --- a/chain/rust/src/builders/mod.rs +++ b/chain/rust/src/builders/mod.rs @@ -2,7 +2,10 @@ pub mod certificate_builder; pub mod input_builder; pub mod mint_builder; pub mod output_builder; +pub mod proposal_builder; pub mod redeemer_builder; pub mod tx_builder; +mod utils; +pub mod vote_builder; pub mod withdrawal_builder; pub mod witness_builder; diff --git a/chain/rust/src/builders/proposal_builder.rs b/chain/rust/src/builders/proposal_builder.rs new file mode 100644 index 00000000..93f7739c --- /dev/null +++ b/chain/rust/src/builders/proposal_builder.rs @@ -0,0 +1,165 @@ +use crate::{ + crypto::hash::hash_plutus_data, governance::ProposalProcedure, plutus::PlutusData, + transaction::NativeScript, RequiredSigners, +}; + +use super::{ + utils::required_wits_from_required_signers, + witness_builder::{ + InputAggregateWitnessData, NativeScriptWitnessInfo, PartialPlutusWitness, + RequiredWitnessSet, + }, +}; + +#[derive(Debug, thiserror::Error)] +pub enum ProposalBuilderError { + #[error("Proposal uses script. Call with_plutus_proposal() instead.")] + ProposalIsScript, + #[error("Proposal uses key hash. Call with_proposal() instead.")] + ProposalIsKeyHash, + #[error("Missing the following witnesses for the input: {0:?}")] + MissingWitnesses(Box), +} + +#[derive(Clone, Debug, Default)] +pub struct ProposalBuilderResult { + pub proposals: Vec, + pub required_wits: RequiredWitnessSet, + pub aggregate_witnesses: Vec, +} + +#[derive(Clone, Debug)] +pub struct ProposalBuilder { + result: ProposalBuilderResult, +} + +impl Default for ProposalBuilder { + fn default() -> Self { + Self::new() + } +} + +impl ProposalBuilder { + pub fn new() -> Self { + Self { + result: ProposalBuilderResult::default(), + } + } + + pub fn with_proposal( + mut self, + proposal: ProposalProcedure, + ) -> Result { + if proposal.gov_action.script_hash().is_some() { + return Err(ProposalBuilderError::ProposalIsScript); + } + + self.result.proposals.push(proposal.clone()); + + Ok(self) + } + + pub fn with_native_script_proposal( + mut self, + proposal: ProposalProcedure, + native_script: NativeScript, + witness_info: NativeScriptWitnessInfo, + ) -> Result { + if let Some(script_hash) = proposal.gov_action.script_hash() { + if *script_hash != native_script.hash() { + let mut err_req_wits = RequiredWitnessSet::new(); + err_req_wits.add_script_hash(*script_hash); + return Err(ProposalBuilderError::MissingWitnesses(Box::new( + err_req_wits, + ))); + } + self.result.required_wits.add_script_hash(*script_hash); + } else { + return Err(ProposalBuilderError::ProposalIsKeyHash); + } + + self.result.proposals.push(proposal); + + self.result + .aggregate_witnesses + .push(InputAggregateWitnessData::NativeScript( + native_script, + witness_info, + )); + + Ok(self) + } + + pub fn with_plutus_proposal( + self, + proposal: ProposalProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + datum: PlutusData, + ) -> Result { + self.with_plutus_proposal_impl(proposal, partial_witness, required_signers, Some(datum)) + } + + pub fn with_plutus_proposal_inline_datum( + self, + proposal: ProposalProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + ) -> Result { + self.with_plutus_proposal_impl(proposal, partial_witness, required_signers, None) + } + + fn with_plutus_proposal_impl( + mut self, + proposal: ProposalProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + datum: Option, + ) -> Result { + let mut required_wits = required_wits_from_required_signers(&required_signers); + if let Some(script_hash) = proposal.gov_action.script_hash() { + required_wits.add_script_hash(*script_hash); + } else { + return Err(ProposalBuilderError::ProposalIsKeyHash); + } + + let mut required_wits_left = required_wits.clone(); + + // no way to know these at this time + required_wits_left.vkeys.clear(); + + let script_hash = partial_witness.script.hash(); + + // check the user provided all the required witnesses + required_wits_left.scripts.remove(&script_hash); + if let Some(datum) = &datum { + required_wits_left + .plutus_data + .remove(&hash_plutus_data(datum)); + } + + if required_wits_left.len() > 0 { + return Err(ProposalBuilderError::MissingWitnesses(Box::new( + required_wits_left, + ))); + } + + self.result.proposals.push(proposal); + + self.result.required_wits.add_all(required_wits); + + self.result + .aggregate_witnesses + .push(InputAggregateWitnessData::PlutusScript( + partial_witness, + required_signers, + datum, + )); + + Ok(self) + } + + pub fn build(self) -> ProposalBuilderResult { + self.result + } +} diff --git a/chain/rust/src/builders/redeemer_builder.rs b/chain/rust/src/builders/redeemer_builder.rs index d83e15ad..89e62138 100644 --- a/chain/rust/src/builders/redeemer_builder.rs +++ b/chain/rust/src/builders/redeemer_builder.rs @@ -1,6 +1,7 @@ use super::{ certificate_builder::CertificateBuilderResult, input_builder::InputBuilderResult, - mint_builder::MintBuilderResult, withdrawal_builder::WithdrawalBuilderResult, + mint_builder::MintBuilderResult, proposal_builder::ProposalBuilderResult, + vote_builder::VoteBuilderResult, withdrawal_builder::WithdrawalBuilderResult, }; use crate::{ address::RewardAddress, @@ -97,6 +98,10 @@ pub struct RedeemerSetBuilder { // certificates in the DCert list are indexed in the order in which they arranged in the (full, unfiltered) // list of certificates inside the transaction cert: Vec>, + + proposals: Vec>, + + votes: Vec>, } impl RedeemerSetBuilder { @@ -143,10 +148,18 @@ impl RedeemerSetBuilder { ))); } RedeemerTag::Proposing => { - todo!("https://github.com/dcSpark/cardano-multiplatform-lib/issues/323") + let entry = self.proposals.get_mut(key.index as usize).unwrap(); + *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new( + entry.as_ref().unwrap().data().clone(), + ex_units, + ))); } RedeemerTag::Voting => { - todo!("https://github.com/dcSpark/cardano-multiplatform-lib/issues/323") + let entry = self.votes.get_mut(key.index as usize).unwrap(); + *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new( + entry.as_ref().unwrap().data().clone(), + ex_units, + ))); } } } @@ -217,6 +230,28 @@ impl RedeemerSetBuilder { } } + pub fn add_proposal(&mut self, result: &ProposalBuilderResult) { + for aggregate_witness in &result.aggregate_witnesses { + if let Some(data) = aggregate_witness.redeemer_plutus_data() { + self.proposals + .push(Some(UntaggedRedeemerPlaceholder::JustData(data.clone()))); + } else { + self.proposals.push(None); + } + } + } + + pub fn add_vote(&mut self, result: &VoteBuilderResult) { + for aggregate_witness in &result.aggregate_witnesses { + if let Some(data) = aggregate_witness.redeemer_plutus_data() { + self.votes + .push(Some(UntaggedRedeemerPlaceholder::JustData(data.clone()))); + } else { + self.votes.push(None); + } + } + } + pub fn build(&self, default_to_dummy_exunits: bool) -> Result { let mut redeemers = Vec::new(); // Calling iter on a BTreeMap returns a list of sorted keys @@ -244,6 +279,18 @@ impl RedeemerSetBuilder { &mut self.cert.iter().map(|entry| (&(), entry)), default_to_dummy_exunits, )?; + self.remove_placeholders_and_tag( + &mut redeemers, + RedeemerTag::Proposing, + &mut self.proposals.iter().map(|entry| (&(), entry)), + default_to_dummy_exunits, + )?; + self.remove_placeholders_and_tag( + &mut redeemers, + RedeemerTag::Voting, + &mut self.votes.iter().map(|entry| (&(), entry)), + default_to_dummy_exunits, + )?; Ok(Redeemers::new_arr_legacy_redeemer(redeemers)) } diff --git a/chain/rust/src/builders/tx_builder.rs b/chain/rust/src/builders/tx_builder.rs index d91b1981..a324846f 100644 --- a/chain/rust/src/builders/tx_builder.rs +++ b/chain/rust/src/builders/tx_builder.rs @@ -2,9 +2,11 @@ use super::certificate_builder::*; use super::input_builder::InputBuilderResult; use super::mint_builder::MintBuilderResult; use super::output_builder::{OutputBuilderError, SingleOutputBuilderResult}; +use super::proposal_builder::ProposalBuilderResult; use super::redeemer_builder::RedeemerBuilderError; use super::redeemer_builder::RedeemerSetBuilder; use super::redeemer_builder::RedeemerWitnessKey; +use super::vote_builder::VoteBuilderResult; use super::withdrawal_builder::WithdrawalBuilderResult; use super::witness_builder::merge_fake_witness; use super::witness_builder::PlutusScriptWitness; @@ -21,6 +23,7 @@ use crate::crypto::hash::{calc_script_data_hash, hash_auxiliary_data, ScriptData use crate::crypto::{BootstrapWitness, Vkeywitness}; use crate::deposit::{internal_get_deposit, internal_get_implicit_input}; use crate::fees::LinearFee; +use crate::governance::{ProposalProcedure, VotingProcedures}; use crate::min_ada::min_ada_required; use crate::plutus::{CostModels, ExUnits, Language}; use crate::plutus::{PlutusData, Redeemers}; @@ -385,6 +388,8 @@ pub struct TransactionBuilder { ttl: Option, // absolute slot number certs: Option>, withdrawals: Option, + proposals: Option>, + votes: Option, auxiliary_data: Option, validity_start_interval: Option, mint: Option, @@ -876,6 +881,60 @@ impl TransactionBuilder { .add_required_wits(result.required_wits); } + pub fn add_proposal(&mut self, mut result: ProposalBuilderResult) { + self.witness_builders + .redeemer_set_builder + .add_proposal(&result); + if self.proposals.is_none() { + self.proposals = Some(Vec::new()); + } + self.proposals + .as_mut() + .unwrap() + .append(&mut result.proposals); + for data in result.aggregate_witnesses { + self.witness_builders + .witness_set_builder + .add_input_aggregate_real_witness_data(&data); + self.witness_builders + .fake_required_witnesses + .add_input_aggregate_fake_witness_data(&data); + if let InputAggregateWitnessData::PlutusScript(_, required_signers, _) = data { + required_signers + .iter() + .for_each(|signer| self.add_required_signer(*signer)); + } + } + self.witness_builders + .witness_set_builder + .add_required_wits(result.required_wits); + } + + pub fn add_vote(&mut self, result: VoteBuilderResult) { + self.witness_builders.redeemer_set_builder.add_vote(&result); + if let Some(votes) = self.votes.as_mut() { + votes.extend(result.votes.take()); + } else { + self.votes = Some(result.votes); + } + for data in result.aggregate_witnesses { + self.witness_builders + .witness_set_builder + .add_input_aggregate_real_witness_data(&data); + self.witness_builders + .fake_required_witnesses + .add_input_aggregate_fake_witness_data(&data); + if let InputAggregateWitnessData::PlutusScript(_, required_signers, _) = data { + required_signers + .iter() + .for_each(|signer| self.add_required_signer(*signer)); + } + } + self.witness_builders + .witness_set_builder + .add_required_wits(result.required_wits); + } + pub fn get_withdrawals(&self) -> Option { self.withdrawals.clone() } @@ -977,6 +1036,8 @@ impl TransactionBuilder { ttl: None, certs: None, withdrawals: None, + proposals: None, + votes: None, auxiliary_data: None, validity_start_interval: None, mint: None, @@ -1124,6 +1185,7 @@ impl TransactionBuilder { pub fn get_deposit(&self) -> Result { internal_get_deposit( self.certs.as_deref(), + self.proposals.as_deref(), self.config.pool_deposit, self.config.key_deposit, ) @@ -1258,8 +1320,11 @@ impl TransactionBuilder { .collect::>() .into() }), - voting_procedures: None, - proposal_procedures: None, + voting_procedures: self.votes.clone(), + proposal_procedures: self + .proposals + .as_ref() + .map(|proposals| proposals.clone().into()), current_treasury_value: None, donation: None, encodings: None, diff --git a/chain/rust/src/builders/utils.rs b/chain/rust/src/builders/utils.rs new file mode 100644 index 00000000..f5be4b53 --- /dev/null +++ b/chain/rust/src/builders/utils.rs @@ -0,0 +1,14 @@ +use crate::RequiredSigners; + +use super::witness_builder::RequiredWitnessSet; + +pub(crate) fn required_wits_from_required_signers( + required_signers: &RequiredSigners, +) -> RequiredWitnessSet { + let mut required_wits = RequiredWitnessSet::default(); + required_signers + .as_ref() + .iter() + .for_each(|required_signer| required_wits.add_vkey_key_hash(*required_signer)); + required_wits +} diff --git a/chain/rust/src/builders/vote_builder.rs b/chain/rust/src/builders/vote_builder.rs new file mode 100644 index 00000000..4aef9c99 --- /dev/null +++ b/chain/rust/src/builders/vote_builder.rs @@ -0,0 +1,221 @@ +use crate::{ + crypto::hash::hash_plutus_data, + governance::{GovActionId, Voter, VotingProcedure, VotingProcedures}, + plutus::PlutusData, + transaction::NativeScript, + RequiredSigners, +}; + +use super::{ + utils::required_wits_from_required_signers, + witness_builder::{ + InputAggregateWitnessData, NativeScriptWitnessInfo, PartialPlutusWitness, + RequiredWitnessSet, + }, +}; + +#[derive(Debug, thiserror::Error)] +pub enum VoteBuilderError { + #[error("Voter is script. Call with_plutus_vote() instead.")] + VoterIsScript, + #[error("Voter is key hash. Call with_vote() instead.")] + VoterIsKeyHash, + #[error("Vote already exists")] + VoteAlreayExists, + #[error("Missing the following witnesses for the input: {0:?}")] + MissingWitnesses(Box), +} + +#[derive(Clone, Debug, Default)] +pub struct VoteBuilderResult { + pub votes: VotingProcedures, + pub required_wits: RequiredWitnessSet, + pub aggregate_witnesses: Vec, +} + +#[derive(Clone, Debug)] +pub struct VoteBuilder { + result: VoteBuilderResult, +} + +impl Default for VoteBuilder { + fn default() -> Self { + Self::new() + } +} + +impl VoteBuilder { + pub fn new() -> Self { + Self { + result: VoteBuilderResult::default(), + } + } + + /// Add a vote using a voter with a key hash + /// Will throw an error if the voter is script-hash based + pub fn with_vote( + mut self, + voter: Voter, + gov_action_id: GovActionId, + procedure: VotingProcedure, + ) -> Result { + if let Some(key_hash) = voter.key_hash() { + self.result.required_wits.add_vkey_key_hash(*key_hash); + } else { + return Err(VoteBuilderError::VoterIsScript); + } + if self + .result + .votes + .entry(voter) + .or_default() + .insert(gov_action_id, procedure) + .is_some() + { + return Err(VoteBuilderError::VoteAlreayExists); + } + Ok(self) + } + + pub fn with_native_script_vote( + mut self, + voter: Voter, + gov_action_id: GovActionId, + procedure: VotingProcedure, + native_script: NativeScript, + witness_info: NativeScriptWitnessInfo, + ) -> Result { + if let Some(script_hash) = voter.script_hash() { + if *script_hash != native_script.hash() { + let mut err_req_wits = RequiredWitnessSet::new(); + err_req_wits.add_script_hash(*script_hash); + return Err(VoteBuilderError::MissingWitnesses(Box::new(err_req_wits))); + } + self.result.required_wits.add_script_hash(*script_hash); + } else { + return Err(VoteBuilderError::VoterIsKeyHash); + } + + if self + .result + .votes + .entry(voter) + .or_default() + .insert(gov_action_id, procedure) + .is_some() + { + return Err(VoteBuilderError::VoteAlreayExists); + } + + self.result + .aggregate_witnesses + .push(InputAggregateWitnessData::NativeScript( + native_script, + witness_info, + )); + + Ok(self) + } + + pub fn with_plutus_vote( + self, + voter: Voter, + gov_action_id: GovActionId, + procedure: VotingProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + datum: PlutusData, + ) -> Result { + self.with_plutus_vote_impl( + voter, + gov_action_id, + procedure, + partial_witness, + required_signers, + Some(datum), + ) + } + + pub fn with_plutus_vote_inline_datum( + self, + voter: Voter, + gov_action_id: GovActionId, + procedure: VotingProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + ) -> Result { + self.with_plutus_vote_impl( + voter, + gov_action_id, + procedure, + partial_witness, + required_signers, + None, + ) + } + + fn with_plutus_vote_impl( + mut self, + voter: Voter, + gov_action_id: GovActionId, + procedure: VotingProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + datum: Option, + ) -> Result { + let mut required_wits = required_wits_from_required_signers(&required_signers); + if let Some(script_hash) = voter.script_hash() { + required_wits.add_script_hash(*script_hash); + } else { + return Err(VoteBuilderError::VoterIsKeyHash); + } + + let mut required_wits_left = required_wits.clone(); + + // no way to know these at this time + required_wits_left.vkeys.clear(); + + let script_hash = partial_witness.script.hash(); + + // check the user provided all the required witnesses + required_wits_left.scripts.remove(&script_hash); + if let Some(datum) = &datum { + required_wits_left + .plutus_data + .remove(&hash_plutus_data(datum)); + } + + if required_wits_left.len() > 0 { + return Err(VoteBuilderError::MissingWitnesses(Box::new( + required_wits_left, + ))); + } + + if self + .result + .votes + .entry(voter) + .or_default() + .insert(gov_action_id, procedure) + .is_some() + { + return Err(VoteBuilderError::VoteAlreayExists); + } + + self.result.required_wits.add_all(required_wits); + + self.result + .aggregate_witnesses + .push(InputAggregateWitnessData::PlutusScript( + partial_witness, + required_signers, + datum, + )); + + Ok(self) + } + + pub fn build(self) -> VoteBuilderResult { + self.result + } +} diff --git a/chain/rust/src/deposit.rs b/chain/rust/src/deposit.rs index 3e40b394..211fc6dd 100644 --- a/chain/rust/src/deposit.rs +++ b/chain/rust/src/deposit.rs @@ -1,6 +1,9 @@ use cml_core::ArithmeticError; -use crate::{certs::Certificate, transaction::TransactionBody, Coin, Value, Withdrawals}; +use crate::{ + certs::Certificate, governance::ProposalProcedure, transaction::TransactionBody, Coin, Value, + Withdrawals, +}; pub fn internal_get_implicit_input( withdrawals: Option<&Withdrawals>, @@ -22,7 +25,10 @@ pub fn internal_get_implicit_input( .try_fold(0u64, |acc, cert| match cert { Certificate::PoolRetirement(_cert) => acc.checked_add(pool_deposit), Certificate::StakeDeregistration(_cert) => acc.checked_add(key_deposit), - Certificate::UnregCert(_cert) => acc.checked_add(key_deposit), + Certificate::UnregCert(cert) => acc.checked_add(cert.coin), + Certificate::UnregDrepCert(cert) => acc.checked_add(cert.coin), + // TODO: is this the case? + Certificate::ResignCommitteeColdCert(_cert) => acc.checked_add(key_deposit), _ => Some(acc), }) .ok_or(ArithmeticError::IntegerOverflow)?, @@ -36,6 +42,7 @@ pub fn internal_get_implicit_input( pub fn internal_get_deposit( certs: Option<&[Certificate]>, + proposals: Option<&[ProposalProcedure]>, pool_deposit: Coin, // // protocol parameter key_deposit: Coin, // protocol parameter ) -> Result { @@ -46,13 +53,25 @@ pub fn internal_get_deposit( .try_fold(0u64, |acc, cert| match cert { Certificate::PoolRegistration(_cert) => acc.checked_add(pool_deposit), Certificate::StakeRegistration(_cert) => acc.checked_add(key_deposit), - Certificate::RegCert(_cert) => acc.checked_add(key_deposit), - Certificate::StakeRegDelegCert(_cert) => acc.checked_add(key_deposit), + Certificate::RegCert(cert) => acc.checked_add(cert.coin), + Certificate::StakeRegDelegCert(cert) => acc.checked_add(cert.coin), + Certificate::RegDrepCert(cert) => acc.checked_add(cert.coin), + Certificate::VoteRegDelegCert(cert) => acc.checked_add(cert.coin), + Certificate::StakeVoteRegDelegCert(cert) => acc.checked_add(cert.coin), _ => Some(acc), }) .ok_or(ArithmeticError::IntegerOverflow)?, }; - Ok(certificate_refund) + let proposal_refund = match proposals { + None => 0, + Some(proposals) => proposals + .iter() + .try_fold(0u64, |acc, proposal| acc.checked_add(proposal.deposit)) + .ok_or(ArithmeticError::IntegerOverflow)?, + }; + certificate_refund + .checked_add(proposal_refund) + .ok_or(ArithmeticError::IntegerOverflow) } pub fn get_implicit_input( @@ -75,6 +94,10 @@ pub fn get_deposit( ) -> Result { internal_get_deposit( txbody.certs.as_ref().map(|certs| certs.as_ref()), + txbody + .proposal_procedures + .as_ref() + .map(|proposals| proposals.as_ref()), pool_deposit, key_deposit, ) diff --git a/chain/rust/src/governance/mod.rs b/chain/rust/src/governance/mod.rs index 6a997590..d0b24400 100644 --- a/chain/rust/src/governance/mod.rs +++ b/chain/rust/src/governance/mod.rs @@ -3,6 +3,7 @@ pub mod cbor_encodings; pub mod serialization; +pub mod utils; use crate::address::RewardAccount; use crate::assets::Coin; diff --git a/chain/rust/src/governance/utils.rs b/chain/rust/src/governance/utils.rs new file mode 100644 index 00000000..12391008 --- /dev/null +++ b/chain/rust/src/governance/utils.rs @@ -0,0 +1,48 @@ +use cml_crypto::{Ed25519KeyHash, ScriptHash}; + +use super::{GovAction, Voter}; + +impl GovAction { + pub fn script_hash(&self) -> Option<&ScriptHash> { + match self { + Self::ParameterChangeAction(action) => action.policy_hash.as_ref(), + Self::HardForkInitiationAction(_action) => None, + Self::TreasuryWithdrawalsAction(action) => action.policy_hash.as_ref(), + Self::NoConfidence(_action) => None, + // TODO: unsure if these count? they can be credentials but maybe it's not needed to sign + Self::UpdateCommittee(_action) => None, + // TODO: unsure if this counts? + //Self::NewConstitution(action) => action.constitution.script_hash, + Self::NewConstitution(_action) => None, + Self::InfoAction { .. } => None, + } + } +} + +impl Voter { + pub fn key_hash(&self) -> Option<&Ed25519KeyHash> { + match self { + Self::ConstitutionalCommitteeHotKeyHash { + ed25519_key_hash, .. + } => Some(ed25519_key_hash), + Self::ConstitutionalCommitteeHotScriptHash { .. } => None, + Self::DRepKeyHash { + ed25519_key_hash, .. + } => Some(ed25519_key_hash), + Self::DRepScriptHash { .. } => None, + Self::StakingPoolKeyHash { + ed25519_key_hash, .. + } => Some(ed25519_key_hash), + } + } + + pub fn script_hash(&self) -> Option<&ScriptHash> { + match self { + Self::ConstitutionalCommitteeHotKeyHash { .. } => None, + Self::ConstitutionalCommitteeHotScriptHash { script_hash, .. } => Some(script_hash), + Self::DRepKeyHash { .. } => None, + Self::DRepScriptHash { script_hash, .. } => Some(script_hash), + Self::StakingPoolKeyHash { .. } => None, + } + } +} diff --git a/chain/wasm/src/builders/mod.rs b/chain/wasm/src/builders/mod.rs index 30911f48..960df211 100644 --- a/chain/wasm/src/builders/mod.rs +++ b/chain/wasm/src/builders/mod.rs @@ -2,7 +2,9 @@ pub mod certificate_builder; pub mod input_builder; pub mod mint_builder; pub mod output_builder; +pub mod proposal_builder; pub mod redeemer_builder; pub mod tx_builder; +pub mod vote_builder; pub mod withdrawal_builder; pub mod witness_builder; diff --git a/chain/wasm/src/builders/proposal_builder.rs b/chain/wasm/src/builders/proposal_builder.rs new file mode 100644 index 00000000..abf6d23d --- /dev/null +++ b/chain/wasm/src/builders/proposal_builder.rs @@ -0,0 +1,99 @@ +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; + +use cml_core_wasm::impl_wasm_conversions; + +use crate::{ + governance::ProposalProcedure, plutus::PlutusData, transaction::NativeScript, RequiredSigners, +}; + +use super::witness_builder::{NativeScriptWitnessInfo, PartialPlutusWitness}; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct ProposalBuilderResult(cml_chain::builders::proposal_builder::ProposalBuilderResult); + +impl_wasm_conversions!( + cml_chain::builders::proposal_builder::ProposalBuilderResult, + ProposalBuilderResult +); + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct ProposalBuilder(cml_chain::builders::proposal_builder::ProposalBuilder); + +impl_wasm_conversions!( + cml_chain::builders::proposal_builder::ProposalBuilder, + ProposalBuilder +); + +#[wasm_bindgen] +impl ProposalBuilder { + pub fn new() -> Self { + Self(cml_chain::builders::proposal_builder::ProposalBuilder::new()) + } + + pub fn with_proposal(&self, proposal: ProposalProcedure) -> Result { + self.0 + .clone() + .with_proposal(proposal.clone().into()) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_native_script_proposal( + &self, + proposal: ProposalProcedure, + native_script: NativeScript, + witness_info: NativeScriptWitnessInfo, + ) -> Result { + self.0 + .clone() + .with_native_script_proposal( + proposal.clone().into(), + native_script.clone().into(), + witness_info.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_plutus_proposal( + &self, + proposal: &ProposalProcedure, + partial_witness: &PartialPlutusWitness, + required_signers: &RequiredSigners, + datum: &PlutusData, + ) -> Result { + self.0 + .clone() + .with_plutus_proposal( + proposal.clone().into(), + partial_witness.clone().into(), + required_signers.clone().into(), + datum.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_plutus_proposal_inline_datum( + &self, + proposal: ProposalProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + ) -> Result { + self.0 + .clone() + .with_plutus_proposal_inline_datum( + proposal.clone().into(), + partial_witness.clone().into(), + required_signers.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn build(&self) -> ProposalBuilderResult { + self.0.clone().build().into() + } +} diff --git a/chain/wasm/src/builders/redeemer_builder.rs b/chain/wasm/src/builders/redeemer_builder.rs index 7c6be9b1..26e673a4 100644 --- a/chain/wasm/src/builders/redeemer_builder.rs +++ b/chain/wasm/src/builders/redeemer_builder.rs @@ -1,6 +1,7 @@ use super::{ certificate_builder::CertificateBuilderResult, input_builder::InputBuilderResult, - mint_builder::MintBuilderResult, withdrawal_builder::WithdrawalBuilderResult, + mint_builder::MintBuilderResult, proposal_builder::ProposalBuilderResult, + vote_builder::VoteBuilderResult, withdrawal_builder::WithdrawalBuilderResult, }; use crate::plutus::{ExUnits, LegacyRedeemer, PlutusData, RedeemerTag, Redeemers}; use cml_core_wasm::impl_wasm_conversions; @@ -92,6 +93,14 @@ impl RedeemerSetBuilder { self.0.add_cert(result.as_ref()); } + pub fn add_proposal(&mut self, result: &ProposalBuilderResult) { + self.0.add_proposal(result.as_ref()); + } + + pub fn add_vote(&mut self, result: &VoteBuilderResult) { + self.0.add_vote(result.as_ref()); + } + pub fn build(&self, default_to_dummy_exunits: bool) -> Result { self.0 .build(default_to_dummy_exunits) diff --git a/chain/wasm/src/builders/tx_builder.rs b/chain/wasm/src/builders/tx_builder.rs index 1d0e73f4..b6091e33 100644 --- a/chain/wasm/src/builders/tx_builder.rs +++ b/chain/wasm/src/builders/tx_builder.rs @@ -10,7 +10,8 @@ use crate::{ builders::{ certificate_builder::CertificateBuilderResult, input_builder::InputBuilderResult, mint_builder::MintBuilderResult, output_builder::SingleOutputBuilderResult, - redeemer_builder::RedeemerWitnessKey, withdrawal_builder::WithdrawalBuilderResult, + proposal_builder::ProposalBuilderResult, redeemer_builder::RedeemerWitnessKey, + vote_builder::VoteBuilderResult, withdrawal_builder::WithdrawalBuilderResult, witness_builder::TransactionWitnessSetBuilder, }, crypto::{BootstrapWitness, Vkeywitness}, @@ -211,6 +212,14 @@ impl TransactionBuilder { self.0.add_cert(result.clone().into()) } + pub fn add_proposal(&mut self, result: ProposalBuilderResult) { + self.0.add_proposal(result.clone().into()) + } + + pub fn add_vote(&mut self, result: VoteBuilderResult) { + self.0.add_vote(result.clone().into()) + } + pub fn get_withdrawals(&self) -> Option { self.0.get_withdrawals().map(|wd| wd.into()) } diff --git a/chain/wasm/src/builders/vote_builder.rs b/chain/wasm/src/builders/vote_builder.rs new file mode 100644 index 00000000..88774f97 --- /dev/null +++ b/chain/wasm/src/builders/vote_builder.rs @@ -0,0 +1,120 @@ +use wasm_bindgen::{prelude::wasm_bindgen, JsError}; + +use cml_core_wasm::impl_wasm_conversions; + +use crate::{ + governance::{GovActionId, Voter, VotingProcedure}, + plutus::PlutusData, + transaction::NativeScript, + RequiredSigners, +}; + +use super::witness_builder::{NativeScriptWitnessInfo, PartialPlutusWitness}; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct VoteBuilderResult(cml_chain::builders::vote_builder::VoteBuilderResult); + +impl_wasm_conversions!( + cml_chain::builders::vote_builder::VoteBuilderResult, + VoteBuilderResult +); + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct VoteBuilder(cml_chain::builders::vote_builder::VoteBuilder); + +impl_wasm_conversions!(cml_chain::builders::vote_builder::VoteBuilder, VoteBuilder); + +#[wasm_bindgen] +impl VoteBuilder { + pub fn new() -> Self { + Self(cml_chain::builders::vote_builder::VoteBuilder::new()) + } + + pub fn with_vote( + &self, + voter: &Voter, + gov_action_id: &GovActionId, + procedure: &VotingProcedure, + ) -> Result { + self.0 + .clone() + .with_vote( + voter.clone().into(), + gov_action_id.clone().into(), + procedure.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_native_script_vote( + &self, + voter: &Voter, + gov_action_id: &GovActionId, + procedure: &VotingProcedure, + native_script: NativeScript, + witness_info: NativeScriptWitnessInfo, + ) -> Result { + self.0 + .clone() + .with_native_script_vote( + voter.clone().into(), + gov_action_id.clone().into(), + procedure.clone().into(), + native_script.clone().into(), + witness_info.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_plutus_vote( + &self, + voter: &Voter, + gov_action_id: &GovActionId, + procedure: &VotingProcedure, + partial_witness: &PartialPlutusWitness, + required_signers: &RequiredSigners, + datum: &PlutusData, + ) -> Result { + self.0 + .clone() + .with_plutus_vote( + voter.clone().into(), + gov_action_id.clone().into(), + procedure.clone().into(), + partial_witness.clone().into(), + required_signers.clone().into(), + datum.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn with_plutus_vote_inline_datum( + &self, + voter: &Voter, + gov_action_id: &GovActionId, + procedure: &VotingProcedure, + partial_witness: PartialPlutusWitness, + required_signers: RequiredSigners, + ) -> Result { + self.0 + .clone() + .with_plutus_vote_inline_datum( + voter.clone().into(), + gov_action_id.clone().into(), + procedure.clone().into(), + partial_witness.clone().into(), + required_signers.clone().into(), + ) + .map(Into::into) + .map_err(Into::into) + } + + pub fn build(&self) -> VoteBuilderResult { + self.0.clone().build().into() + } +} diff --git a/chain/wasm/src/governance/mod.rs b/chain/wasm/src/governance/mod.rs index a21870d8..99595b4e 100644 --- a/chain/wasm/src/governance/mod.rs +++ b/chain/wasm/src/governance/mod.rs @@ -15,6 +15,8 @@ use cml_core::ordered_hash_map::OrderedHashMap; use cml_core_wasm::{impl_wasm_cbor_json_api, impl_wasm_conversions}; use wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue}; +pub mod utils; + #[derive(Clone, Debug)] #[wasm_bindgen] pub struct Anchor(cml_chain::governance::Anchor); diff --git a/chain/wasm/src/governance/utils.rs b/chain/wasm/src/governance/utils.rs new file mode 100644 index 00000000..ec905d7e --- /dev/null +++ b/chain/wasm/src/governance/utils.rs @@ -0,0 +1,23 @@ +use wasm_bindgen::prelude::wasm_bindgen; + +use cml_crypto_wasm::{Ed25519KeyHash, ScriptHash}; + +use super::{GovAction, Voter}; + +#[wasm_bindgen] +impl GovAction { + pub fn script_hash(&self) -> Option { + self.0.script_hash().map(|hash| (*hash).into()) + } +} + +#[wasm_bindgen] +impl Voter { + pub fn key_hash(&self) -> Option { + self.0.key_hash().map(|hash| (*hash).into()) + } + + pub fn script_hash(&self) -> Option { + self.0.script_hash().map(|hash| (*hash).into()) + } +}