cml_chain/builders/
proposal_builder.rs

1use crate::{
2    crypto::hash::hash_plutus_data, governance::ProposalProcedure, plutus::PlutusData,
3    transaction::NativeScript, RequiredSigners,
4};
5
6use super::{
7    utils::required_wits_from_required_signers,
8    witness_builder::{
9        InputAggregateWitnessData, NativeScriptWitnessInfo, PartialPlutusWitness,
10        RequiredWitnessSet,
11    },
12};
13
14#[derive(Debug, thiserror::Error)]
15pub enum ProposalBuilderError {
16    #[error("Proposal uses script. Call with_plutus_proposal() instead.")]
17    ProposalIsScript,
18    #[error("Proposal uses key hash. Call with_proposal() instead.")]
19    ProposalIsKeyHash,
20    #[error("Missing the following witnesses for the input: {0:?}")]
21    MissingWitnesses(Box<RequiredWitnessSet>),
22}
23
24#[derive(Clone, Debug, Default)]
25pub struct ProposalBuilderResult {
26    pub proposals: Vec<ProposalProcedure>,
27    pub required_wits: RequiredWitnessSet,
28    pub aggregate_witnesses: Vec<InputAggregateWitnessData>,
29}
30
31#[derive(Clone, Debug)]
32pub struct ProposalBuilder {
33    result: ProposalBuilderResult,
34}
35
36impl Default for ProposalBuilder {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl ProposalBuilder {
43    pub fn new() -> Self {
44        Self {
45            result: ProposalBuilderResult::default(),
46        }
47    }
48
49    pub fn with_proposal(
50        mut self,
51        proposal: ProposalProcedure,
52    ) -> Result<Self, ProposalBuilderError> {
53        if proposal.gov_action.script_hash().is_some() {
54            return Err(ProposalBuilderError::ProposalIsScript);
55        }
56
57        self.result.proposals.push(proposal.clone());
58
59        Ok(self)
60    }
61
62    pub fn with_native_script_proposal(
63        mut self,
64        proposal: ProposalProcedure,
65        native_script: NativeScript,
66        witness_info: NativeScriptWitnessInfo,
67    ) -> Result<Self, ProposalBuilderError> {
68        if let Some(script_hash) = proposal.gov_action.script_hash() {
69            if *script_hash != native_script.hash() {
70                let mut err_req_wits = RequiredWitnessSet::new();
71                err_req_wits.add_script_hash(*script_hash);
72                return Err(ProposalBuilderError::MissingWitnesses(Box::new(
73                    err_req_wits,
74                )));
75            }
76            self.result.required_wits.add_script_hash(*script_hash);
77        } else {
78            return Err(ProposalBuilderError::ProposalIsKeyHash);
79        }
80
81        self.result.proposals.push(proposal);
82
83        self.result
84            .aggregate_witnesses
85            .push(InputAggregateWitnessData::NativeScript(
86                native_script,
87                witness_info,
88            ));
89
90        Ok(self)
91    }
92
93    pub fn with_plutus_proposal(
94        self,
95        proposal: ProposalProcedure,
96        partial_witness: PartialPlutusWitness,
97        required_signers: RequiredSigners,
98        datum: PlutusData,
99    ) -> Result<Self, ProposalBuilderError> {
100        self.with_plutus_proposal_impl(proposal, partial_witness, required_signers, Some(datum))
101    }
102
103    pub fn with_plutus_proposal_inline_datum(
104        self,
105        proposal: ProposalProcedure,
106        partial_witness: PartialPlutusWitness,
107        required_signers: RequiredSigners,
108    ) -> Result<Self, ProposalBuilderError> {
109        self.with_plutus_proposal_impl(proposal, partial_witness, required_signers, None)
110    }
111
112    fn with_plutus_proposal_impl(
113        mut self,
114        proposal: ProposalProcedure,
115        partial_witness: PartialPlutusWitness,
116        required_signers: RequiredSigners,
117        datum: Option<PlutusData>,
118    ) -> Result<Self, ProposalBuilderError> {
119        let mut required_wits = required_wits_from_required_signers(&required_signers);
120        if let Some(script_hash) = proposal.gov_action.script_hash() {
121            required_wits.add_script_hash(*script_hash);
122        } else {
123            return Err(ProposalBuilderError::ProposalIsKeyHash);
124        }
125
126        let mut required_wits_left = required_wits.clone();
127
128        // no way to know these at this time
129        required_wits_left.vkeys.clear();
130
131        let script_hash = partial_witness.script.hash();
132
133        // check the user provided all the required witnesses
134        required_wits_left.scripts.remove(&script_hash);
135        if let Some(datum) = &datum {
136            required_wits_left
137                .plutus_data
138                .remove(&hash_plutus_data(datum));
139        }
140
141        if required_wits_left.len() > 0 {
142            return Err(ProposalBuilderError::MissingWitnesses(Box::new(
143                required_wits_left,
144            )));
145        }
146
147        self.result.proposals.push(proposal);
148
149        self.result.required_wits.add_all(required_wits);
150
151        self.result
152            .aggregate_witnesses
153            .push(InputAggregateWitnessData::PlutusScript(
154                partial_witness,
155                required_signers,
156                datum,
157            ));
158
159        Ok(self)
160    }
161
162    pub fn build(self) -> ProposalBuilderResult {
163        self.result
164    }
165}