cardano_serialization_lib/builders/
certificates_builder.rs

1use hashlink::LinkedHashMap;
2use crate::*;
3
4#[wasm_bindgen]
5#[derive(Clone, Debug)]
6pub struct CertificatesBuilder {
7    certs: LinkedHashMap<Certificate, Option<ScriptWitnessType>>,
8}
9
10#[wasm_bindgen]
11impl CertificatesBuilder {
12    pub fn new() -> Self {
13        Self { certs: LinkedHashMap::new() }
14    }
15
16    pub fn add(&mut self, cert: &Certificate) -> Result<(), JsError> {
17        if cert.has_required_script_witness() {
18            return Err(JsError::from_str(
19                "Your certificate has a required script witness.\
20                Please use .add_with_plutus_witness or .add_with_native_script instead.",
21            ));
22        }
23
24        if self.certs.contains_key(cert) {
25            return Err(JsError::from_str("Certificate already exists"));
26        }
27
28        self.certs.insert(cert.clone(), None);
29        Ok(())
30    }
31
32    pub fn add_with_plutus_witness(
33        &mut self,
34        cert: &Certificate,
35        witness: &PlutusWitness,
36    ) -> Result<(), JsError> {
37        if !cert.has_required_script_witness() {
38            return Err(JsError::from_str(
39                "Your certificate does not have a required script witness.\
40                Please use .add instead.",
41            ));
42        }
43
44        if self.certs.contains_key(cert) {
45            return Err(JsError::from_str("Certificate already exists"));
46        }
47
48        self.certs.insert(
49            cert.clone(),
50            Some(ScriptWitnessType::PlutusScriptWitness(witness.clone())),
51        );
52        Ok(())
53    }
54
55    pub fn add_with_native_script(
56        &mut self,
57        cert: &Certificate,
58        native_script_source: &NativeScriptSource,
59    ) -> Result<(), JsError> {
60        if !cert.has_required_script_witness() {
61            return Err(JsError::from_str(
62                "Your certificate does not have a required script witness.\
63                Please use .add instead.",
64            ));
65        }
66
67        if self.certs.contains_key(cert) {
68            return Err(JsError::from_str("Certificate already exists"));
69        }
70
71        self.certs.insert(
72            cert.clone(),
73            Some(ScriptWitnessType::NativeScriptWitness(
74                native_script_source.0.clone(),
75            )),
76        );
77        Ok(())
78    }
79
80    pub(crate) fn get_required_signers(&self) -> Ed25519KeyHashes {
81        let mut set = Ed25519KeyHashes::new();
82        for (cert, script_wit) in &self.certs {
83            let cert_req_signers = witness_keys_for_cert(&cert);
84            set.extend_move(cert_req_signers);
85            if let Some(script_wit) = script_wit {
86                if let Some(signers) = script_wit.get_required_signers() {
87                    set.extend(&signers);
88                }
89            }
90        }
91        set
92    }
93
94    pub fn get_plutus_witnesses(&self) -> PlutusWitnesses {
95        let tag = RedeemerTag::new_cert();
96        let mut scripts = PlutusWitnesses::new();
97        for (i, (_, script_wit)) in self.certs.iter().enumerate() {
98            if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit {
99                let index = BigNum::from(i);
100                scripts.add(&s.clone_with_redeemer_index_and_tag(&index, &tag));
101            }
102        }
103        scripts
104    }
105
106    pub fn get_ref_inputs(&self) -> TransactionInputs {
107        let mut inputs = Vec::new();
108        for (_, script_wit) in self.certs.iter() {
109            match script_wit {
110                Some(script_witness) => {
111                    if let Some(input) = script_witness.get_script_ref_input() {
112                        inputs.push(input);
113                    }
114                    if let Some(input) = script_witness.get_datum_ref_input() {
115                        inputs.push(input);
116                    }
117                }
118                None => {}
119            }
120        }
121        TransactionInputs::from_vec(inputs)
122    }
123
124    pub fn get_native_scripts(&self) -> NativeScripts {
125        let mut scripts = NativeScripts::new();
126        for (_, script_wit) in self.certs.iter() {
127            if let Some(ScriptWitnessType::NativeScriptWitness(
128                NativeScriptSourceEnum::NativeScript(script, _),
129            )) = script_wit
130            {
131                scripts.add(script);
132            }
133        }
134        scripts
135    }
136
137    pub(crate) fn get_used_plutus_lang_versions(&self) -> BTreeSet<Language> {
138        let mut used_langs = BTreeSet::new();
139        for (_, script_wit) in &self.certs {
140            if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = script_wit {
141                used_langs.insert(s.script.language().clone());
142            }
143        }
144        used_langs
145    }
146
147    #[allow(unused_variables)]
148    pub fn get_certificates_refund(
149        &self,
150        pool_deposit: &BigNum,
151        key_deposit: &BigNum,
152    ) -> Result<Value, JsError> {
153        let mut refund = Coin::zero();
154        for (cert, _) in &self.certs {
155            match &cert.0 {
156                CertificateEnum::StakeDeregistration(cert) => {
157                    if let Some(coin) = cert.coin {
158                        refund = refund.checked_add(&coin)?;
159                    } else {
160                        refund = refund.checked_add(&key_deposit)?;
161                    }
162                }
163                CertificateEnum::DRepDeregistration(cert) => {
164                    refund = refund.checked_add(&cert.coin)?;
165                }
166                _ => {}
167            }
168        }
169        Ok(Value::new(&refund))
170    }
171
172    pub fn get_certificates_deposit(
173        &self,
174        pool_deposit: &BigNum,
175        key_deposit: &BigNum,
176    ) -> Result<Coin, JsError> {
177        let mut deposit = Coin::zero();
178        for (cert, _) in &self.certs {
179            match &cert.0 {
180                CertificateEnum::PoolRegistration(_) => {
181                    deposit = deposit.checked_add(&pool_deposit)?;
182                }
183                CertificateEnum::StakeRegistration(cert) => {
184                    if let Some(coin) = cert.coin {
185                        deposit = deposit.checked_add(&coin)?;
186                    } else {
187                        deposit = deposit.checked_add(&key_deposit)?;
188                    }
189                }
190                CertificateEnum::DRepRegistration(cert) => {
191                    deposit = deposit.checked_add(&cert.coin)?;
192                }
193                CertificateEnum::StakeRegistrationAndDelegation(cert) => {
194                    deposit = deposit.checked_add(&cert.coin)?;
195                }
196                CertificateEnum::VoteRegistrationAndDelegation(cert) => {
197                    deposit = deposit.checked_add(&cert.coin)?;
198                }
199                CertificateEnum::StakeVoteRegistrationAndDelegation(cert) => {
200                    deposit = deposit.checked_add(&cert.coin)?;
201                }
202                _ => {}
203            }
204        }
205        Ok(deposit)
206    }
207
208    pub fn has_plutus_scripts(&self) -> bool {
209        for (_, script_wit) in &self.certs {
210            if let Some(ScriptWitnessType::PlutusScriptWitness(_)) = script_wit {
211                return true;
212            }
213        }
214        false
215    }
216
217    pub fn build(&self) -> Certificates {
218        let certs = self.certs.iter().map(|(c, _)| c.clone()).collect();
219        Certificates::from_vec(certs)
220    }
221
222    //return only ref inputs that are script refs with added size
223    //used for calculating the fee for the transaction
224    //another ref input and also script ref input without size are filtered out
225    pub(crate) fn get_script_ref_inputs_with_size(
226        &self,
227    ) -> impl Iterator<Item = (&TransactionInput, usize)> {
228        self.certs.iter()
229            .filter_map(|(_, opt_wit)| opt_wit.as_ref())
230            .filter_map(|script_wit| {
231                script_wit.get_script_ref_input_with_size()
232            })
233    }
234}
235
236// comes from witsVKeyNeeded in the Ledger spec
237fn witness_keys_for_cert(cert_enum: &Certificate) -> RequiredSigners {
238    let mut set = RequiredSigners::new();
239    match &cert_enum.0 {
240        // stake key registrations do not require a witness
241        CertificateEnum::StakeRegistration(cert) => {
242            if cert.coin.is_some() {
243                if let Some(key) = cert.stake_credential().to_keyhash() {
244                    set.add(&key);
245                }
246            }
247        }
248        CertificateEnum::StakeDeregistration(cert) => {
249            if let Some(key) = cert.stake_credential().to_keyhash() {
250                set.add(&key);
251            }
252        }
253        CertificateEnum::StakeDelegation(cert) => {
254            if let Some(key) = cert.stake_credential().to_keyhash() {
255                set.add(&key);
256            }
257        }
258        CertificateEnum::PoolRegistration(cert) => {
259            set.extend(&cert.pool_params().pool_owners());
260            set.add(&cert.pool_params().operator());
261        }
262        CertificateEnum::PoolRetirement(cert) => {
263            set.add(&cert.pool_keyhash());
264        }
265        CertificateEnum::GenesisKeyDelegation(cert) => {
266            set.add(&Ed25519KeyHash::from_bytes(cert.genesis_delegate_hash().to_bytes()).unwrap());
267        }
268        // not witness as there is no single core node or genesis key that posts the certificate
269        CertificateEnum::MoveInstantaneousRewardsCert(_cert) => {}
270        CertificateEnum::CommitteeHotAuth(cert) => {
271            if let CredType::Key(key_hash) = &cert.committee_cold_credential.0 {
272                set.add(key_hash);
273            }
274        }
275        CertificateEnum::CommitteeColdResign(cert) => {
276            if let CredType::Key(key_hash) = &cert.committee_cold_credential.0 {
277                set.add(key_hash);
278            }
279        }
280        CertificateEnum::DRepUpdate(cert) => {
281            if let CredType::Key(key_hash) = &cert.voting_credential.0 {
282                set.add(key_hash);
283            }
284        }
285        CertificateEnum::DRepRegistration(cert) => {
286            if let CredType::Key(key_hash) = &cert.voting_credential.0 {
287                set.add(key_hash);
288            }
289        }
290        CertificateEnum::DRepDeregistration(cert) => {
291            if let CredType::Key(key_hash) = &cert.voting_credential.0 {
292                set.add(key_hash);
293            }
294        }
295        CertificateEnum::StakeAndVoteDelegation(cert) => {
296            if let CredType::Key(key_hash) = &cert.stake_credential.0 {
297                set.add(key_hash);
298            }
299        }
300        CertificateEnum::VoteDelegation(cert) => {
301            if let CredType::Key(key_hash) = &cert.stake_credential.0 {
302                set.add(key_hash);
303            }
304        }
305        CertificateEnum::StakeRegistrationAndDelegation(cert) => {
306            if let CredType::Key(key_hash) = &cert.stake_credential.0 {
307                set.add(key_hash);
308            }
309        }
310        CertificateEnum::VoteRegistrationAndDelegation(cert) => {
311            if let CredType::Key(key_hash) = &cert.stake_credential.0 {
312                set.add(key_hash);
313            }
314        }
315        CertificateEnum::StakeVoteRegistrationAndDelegation(cert) => {
316            if let CredType::Key(key_hash) = &cert.stake_credential.0 {
317                set.add(key_hash);
318            }
319        }
320    }
321    set
322}