1use crate::annotation::NightAnn;
15use crate::dust::DUST_GENERATION_INFO_SIZE;
16use crate::dust::DUST_SPEND_PIS;
17use crate::dust::DUST_SPEND_PROOF_SIZE;
18use crate::dust::{DustActions, DustParameters, DustState, INITIAL_DUST_PARAMETERS};
19use crate::error::FeeCalculationError;
20use crate::error::InvariantViolation;
21use crate::error::MalformedTransaction;
22use crate::verify::ProofVerificationMode;
23use base_crypto::BinaryHashRepr;
24use base_crypto::cost_model::RunningCost;
25use base_crypto::cost_model::price_adjustment_function;
26use base_crypto::cost_model::{CostDuration, FeePrices, FixedPoint, SyntheticCost};
27use base_crypto::hash::HashOutput;
28use base_crypto::hash::PERSISTENT_HASH_BYTES;
29use base_crypto::hash::persistent_hash;
30use base_crypto::repr::MemWrite;
31use base_crypto::signatures::Signature;
32use base_crypto::signatures::{SigningKey, VerifyingKey};
33use base_crypto::time::{Duration, Timestamp};
34use coin_structure::coin::NIGHT;
35use coin_structure::coin::PublicAddress;
36use coin_structure::coin::UserAddress;
37use coin_structure::coin::{Commitment, Nonce, ShieldedTokenType, TokenType, UnshieldedTokenType};
38use coin_structure::contract::ContractAddress;
39use derive_where::derive_where;
40use fake::Dummy;
41use introspection_derive::Introspection;
42use onchain_runtime::context::{BlockContext, CallContext, ClaimedContractCallsValue};
43use onchain_runtime::state::ChargedState;
44use onchain_runtime::state::ContractOperation;
45use onchain_runtime::state::{ContractMaintenanceAuthority, ContractState, EntryPointBuf};
46use onchain_runtime::transcript::Transcript;
47use rand::{CryptoRng, Rng};
48use serde::{Deserialize, Serialize};
49use serialize::{
50 self, Deserializable, Serializable, Tagged, tag_enforcement_test, tagged_serialize,
51};
52use sha2::{Digest, Sha256};
53use std::collections::BTreeSet;
54use std::fmt::Debug;
55use std::fmt::Display;
56use std::fmt::{self, Formatter};
57use std::hash::Hash;
58use std::io::{Read, Write};
59use std::iter::once;
60use std::marker::PhantomData;
61use std::ops::Deref;
62use storage::Storable;
63use storage::arena::{ArenaHash, ArenaKey, Sp};
64use storage::db::DB;
65use storage::db::InMemoryDB;
66use storage::merkle_patricia_trie::Annotation;
67use storage::storable::Loader;
68use storage::storage::Map;
69use storage::storage::{HashMap, HashSet, TimeFilterMap};
70use transient_crypto::commitment::{Pedersen, PedersenRandomness, PureGeneratorPedersen};
71use transient_crypto::curve::FR_BYTES;
72use transient_crypto::curve::Fr;
73use transient_crypto::proofs::KeyLocation;
74use transient_crypto::proofs::VerifierKey;
75use transient_crypto::proofs::{Proof, ProofPreimage};
76use transient_crypto::repr::{FieldRepr, FromFieldRepr};
77use zswap::ZSWAP_TREE_HEIGHT;
78use zswap::error::MalformedOffer;
79use zswap::{Input, Offer as ZswapOffer, Output, Transient};
80
81pub trait SignatureKind<D: DB>: Ord + Storable<D> + Debug + Tagged + 'static {
83 type Signature<T>: Ord + Serializable + Deserializable + Storable<D> + Debug + Tagged;
85
86 fn signature_verify<T>(msg: &[u8], key: VerifyingKey, signature: &Self::Signature<T>) -> bool;
88
89 fn sign<R: Rng + CryptoRng, T>(
90 sk: &SigningKey,
91 rng: &mut R,
92 msg: &[u8],
93 ) -> <Self as SignatureKind<D>>::Signature<T>;
94}
95
96impl<D: DB> SignatureKind<D> for () {
97 type Signature<T> = ();
98
99 fn signature_verify<T>(_msg: &[u8], _key: VerifyingKey, _signature: &()) -> bool {
100 true
101 }
102
103 fn sign<R: Rng + CryptoRng, T>(_: &SigningKey, _: &mut R, _: &[u8]) {}
104}
105
106impl<D: DB> SignatureKind<D> for Signature {
107 type Signature<T> = Signature;
108
109 fn signature_verify<T>(msg: &[u8], key: VerifyingKey, signature: &Signature) -> bool {
110 key.verify(msg, signature)
111 }
112
113 fn sign<R: Rng + CryptoRng, T>(sk: &SigningKey, rng: &mut R, msg: &[u8]) -> Signature {
114 sk.sign(rng, msg)
115 }
116}
117
118pub trait BindingKind<S: SignatureKind<D>, P: ProofKind<D>, D: DB>: Sync + Send {
119 fn when_sealed(
120 f: Result<impl Fn() -> Result<(), MalformedTransaction<D>>, MalformedTransaction<D>>,
121 ) -> Result<(), MalformedTransaction<D>>;
122}
123
124impl<S: SignatureKind<D>, P: ProofKind<D>, D: DB> BindingKind<S, P, D> for Pedersen {
125 fn when_sealed(
126 f: Result<impl Fn() -> Result<(), MalformedTransaction<D>>, MalformedTransaction<D>>,
127 ) -> Result<(), MalformedTransaction<D>> {
128 f?;
129 Ok(())
130 }
131}
132
133impl<S: SignatureKind<D>, P: ProofKind<D>, D: DB> BindingKind<S, P, D> for PedersenRandomness {
134 fn when_sealed(
135 f: Result<impl Fn() -> Result<(), MalformedTransaction<D>>, MalformedTransaction<D>>,
136 ) -> Result<(), MalformedTransaction<D>> {
137 f?;
138 Ok(())
139 }
140}
141
142impl<S: SignatureKind<D>, P: ProofKind<D>, D: DB> BindingKind<S, P, D> for PureGeneratorPedersen {
143 fn when_sealed(
144 f: Result<impl Fn() -> Result<(), MalformedTransaction<D>>, MalformedTransaction<D>>,
145 ) -> Result<(), MalformedTransaction<D>> {
146 f?()
147 }
148}
149
150pub trait PedersenDowngradeable<D: DB>:
151 Into<Pedersen> + Clone + PartialEq + Eq + PartialOrd + Ord + Serialize
152{
153 fn downgrade(&self) -> Pedersen;
154
155 #[allow(clippy::result_large_err)]
156 fn valid(&self, _challenge_pre: &[u8]) -> Result<(), MalformedTransaction<D>>;
157}
158
159pub trait PedersenUpgradeable<D: DB>:
160 Into<Pedersen> + Clone + PartialEq + Eq + PartialOrd + Ord + Serialize + PedersenDowngradeable<D>
161{
162 fn upgrade<R: Rng + CryptoRng>(
163 &self,
164 rng: &mut R,
165 challenge_pre: &[u8],
166 ) -> PureGeneratorPedersen;
167}
168
169impl<D: DB> PedersenDowngradeable<D> for PureGeneratorPedersen {
170 fn downgrade(&self) -> Pedersen {
171 Pedersen::from(self.clone())
172 }
173
174 fn valid(&self, challenge_pre: &[u8]) -> Result<(), MalformedTransaction<D>> {
175 if PureGeneratorPedersen::valid(self, challenge_pre) {
176 Ok(())
177 } else {
178 Err(MalformedTransaction::<D>::InvalidSchnorrProof)
179 }
180 }
181}
182
183impl<D: DB> PedersenUpgradeable<D> for PureGeneratorPedersen {
184 fn upgrade<R: Rng + CryptoRng>(
185 &self,
186 _rng: &mut R,
187 _challenge_pre: &[u8],
188 ) -> PureGeneratorPedersen {
189 self.clone()
190 }
191}
192
193impl<D: DB> PedersenDowngradeable<D> for PedersenRandomness {
194 fn downgrade(&self) -> Pedersen {
195 Pedersen::from(*self)
196 }
197
198 fn valid(&self, _challenge_pre: &[u8]) -> Result<(), MalformedTransaction<D>> {
199 Ok(())
200 }
201}
202
203impl<D: DB> PedersenUpgradeable<D> for PedersenRandomness {
204 fn upgrade<R: Rng + CryptoRng>(
205 &self,
206 rng: &mut R,
207 challenge_pre: &[u8],
208 ) -> PureGeneratorPedersen {
209 PureGeneratorPedersen::new_from(rng, self, challenge_pre)
210 }
211}
212
213impl<D: DB> PedersenDowngradeable<D> for Pedersen {
214 fn downgrade(&self) -> Pedersen {
215 *self
216 }
217
218 fn valid(&self, _challenge_pre: &[u8]) -> Result<(), MalformedTransaction<D>> {
219 Ok(())
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Storable)]
224#[storable(base)]
225#[tag = "proof-preimage-versioned"]
226#[non_exhaustive]
227pub enum ProofPreimageVersioned {
228 V2(std::sync::Arc<ProofPreimage>),
229}
230
231impl Serializable for ProofPreimageVersioned {
232 fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
233 match self {
234 ProofPreimageVersioned::V2(preimage) => {
235 Serializable::serialize(&1u8, writer)?;
236 preimage.serialize(writer)
237 }
238 }
239 }
240 fn serialized_size(&self) -> usize {
241 match self {
242 ProofPreimageVersioned::V2(preimage) => preimage.serialized_size() + 1,
243 }
244 }
245}
246
247impl Deserializable for ProofPreimageVersioned {
248 fn deserialize(reader: &mut impl Read, recursion_depth: u32) -> std::io::Result<Self> {
249 let discrim = Deserializable::deserialize(reader, recursion_depth)?;
250 match discrim {
251 0u8 => Err(std::io::Error::new(
252 std::io::ErrorKind::InvalidData,
253 format!("invalid old discriminant for ProofPreimageVersioned: {discrim}"),
254 )),
255 1 => Ok(ProofPreimageVersioned::V2(std::sync::Arc::new(
256 ProofPreimage::deserialize(reader, recursion_depth)?,
257 ))),
258 _ => Err(std::io::Error::new(
259 std::io::ErrorKind::InvalidData,
260 format!("unknown discriminant for ProofPreimageVersioned: {discrim}"),
261 )),
262 }
263 }
264}
265
266impl Tagged for ProofPreimageVersioned {
267 fn tag() -> std::borrow::Cow<'static, str> {
268 "proof-preimage-versioned".into()
269 }
270 fn tag_unique_factor() -> String {
271 format!("[[],{}]", ProofPreimage::tag())
272 }
273}
274
275tag_enforcement_test!(ProofPreimageVersioned);
276
277impl ProofPreimageVersioned {
278 pub fn key_location(&self) -> &KeyLocation {
279 match self {
280 ProofPreimageVersioned::V2(ppi) => &ppi.key_location,
281 }
282 }
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Storable, Introspection)]
286#[storable(base)]
287#[tag = "proof-versioned"]
288#[non_exhaustive]
289pub enum ProofVersioned {
290 V2(Proof),
291}
292
293impl Serializable for ProofVersioned {
294 fn serialize(&self, writer: &mut impl Write) -> std::io::Result<()> {
295 match self {
296 ProofVersioned::V2(proof) => {
297 Serializable::serialize(&1u8, writer)?;
298 proof.serialize(writer)
299 }
300 }
301 }
302 fn serialized_size(&self) -> usize {
303 match self {
304 ProofVersioned::V2(proof) => proof.serialized_size() + 1,
305 }
306 }
307}
308
309impl Deserializable for ProofVersioned {
310 fn deserialize(reader: &mut impl Read, recursion_depth: u32) -> std::io::Result<Self> {
311 let discrim = Deserializable::deserialize(reader, recursion_depth)?;
312 match discrim {
313 0u8 => Err(std::io::Error::new(
314 std::io::ErrorKind::InvalidData,
315 format!("invalid old discriminant for ProofVersioned: {discrim}"),
316 )),
317 1 => Ok(ProofVersioned::V2(Proof::deserialize(
318 reader,
319 recursion_depth,
320 )?)),
321 _ => Err(std::io::Error::new(
322 std::io::ErrorKind::InvalidData,
323 format!("unknown discriminant for ProofVersioned: {discrim}"),
324 )),
325 }
326 }
327}
328
329impl Tagged for ProofVersioned {
330 fn tag() -> std::borrow::Cow<'static, str> {
331 "proof-versioned".into()
332 }
333 fn tag_unique_factor() -> String {
334 format!("[[],{}]", Proof::tag())
335 }
336}
337
338tag_enforcement_test!(ProofVersioned);
339
340#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serializable, Storable)]
341#[tag = "proof"]
342#[storable(base)]
343pub struct ProofMarker;
344
345#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serializable, Storable)]
346#[tag = "proof-preimage"]
347#[storable(base)]
348pub struct ProofPreimageMarker;
349
350pub trait ProofKind<D: DB>: Ord + Storable<D> + Serializable + Deserializable + Tagged {
351 type Pedersen: PedersenDowngradeable<D> + Serializable + Deserializable + Tagged;
352 type Proof: Clone
353 + PartialEq
354 + Eq
355 + PartialOrd
356 + Ord
357 + Serializable
358 + Deserializable
359 + Tagged
360 + Storable<D>;
361 type LatestProof: Clone
362 + PartialEq
363 + Eq
364 + Ord
365 + Serializable
366 + Deserializable
367 + Hash
368 + Storable<D>
369 + Tagged
370 + Into<Self::Proof>;
371 fn zswap_well_formed(
372 offer: &zswap::Offer<Self::LatestProof, D>,
373 segment: u16,
374 ) -> Result<Pedersen, MalformedOffer>;
375 fn zswap_claim_well_formed(
376 claim: &zswap::AuthorizedClaim<Self::LatestProof>,
377 ) -> Result<(), MalformedOffer>;
378 #[allow(clippy::result_large_err)]
379 fn proof_verify(
380 op: &ContractOperation,
381 proof: &Self::Proof,
382 pis: Vec<Fr>,
383 call: &ContractCall<Self, D>,
384 mode: ProofVerificationMode,
385 ) -> Result<(), MalformedTransaction<D>>;
386 #[allow(clippy::result_large_err)]
387 fn latest_proof_verify(
388 op: &VerifierKey,
389 proof: &Self::LatestProof,
390 pis: Vec<Fr>,
391 mode: ProofVerificationMode,
392 ) -> Result<(), MalformedTransaction<D>>;
393 fn estimated_tx_size<
396 S: SignatureKind<D>,
397 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
398 >(
399 tx: &Transaction<S, Self, B, D>,
400 ) -> usize;
401}
402
403impl From<Proof> for ProofVersioned {
404 fn from(proof: Proof) -> Self {
405 Self::V2(proof)
406 }
407}
408
409impl<D: DB> ProofKind<D> for ProofMarker {
410 type Pedersen = PureGeneratorPedersen;
411 type Proof = ProofVersioned;
412 type LatestProof = Proof;
413 fn zswap_well_formed(
414 offer: &zswap::Offer<Self::LatestProof, D>,
415 segment: u16,
416 ) -> Result<Pedersen, MalformedOffer> {
417 offer.well_formed(segment)
418 }
419 fn zswap_claim_well_formed(
420 claim: &zswap::AuthorizedClaim<Self::LatestProof>,
421 ) -> Result<(), MalformedOffer> {
422 claim.well_formed()
423 }
424 #[cfg(not(feature = "proof-verifying"))]
425 fn proof_verify(
426 _op: &ContractOperation,
427 _proof: &Self::Proof,
428 _pis: Vec<Fr>,
429 _call: &ContractCall<Self, D>,
430 _mode: ProofVerificationMode,
431 ) -> Result<(), MalformedTransaction<D>> {
432 Ok(())
433 }
434 #[cfg(feature = "proof-verifying")]
435 fn proof_verify(
436 op: &ContractOperation,
437 proof: &Self::Proof,
438 pis: Vec<Fr>,
439 call: &ContractCall<Self, D>,
440 mode: ProofVerificationMode,
441 ) -> Result<(), MalformedTransaction<D>> {
442 use transient_crypto::proofs::PARAMS_VERIFIER;
443
444 let vk = match &op.v2 {
445 Some(vk) => vk,
446 None => {
447 warn!("missing verifier key");
448 return Err(MalformedTransaction::<D>::VerifierKeyNotPresent {
449 address: call.address,
450 operation: call.entry_point.clone(),
451 });
452 }
453 };
454
455 if op.v2.is_some() && !matches!(proof, ProofVersioned::V2(_)) {
456 return Err(MalformedTransaction::<D>::UnsupportedProofVersion {
457 op_version: "V2".to_string(),
458 });
459 }
460
461 match proof {
462 ProofVersioned::V2(proof) => match mode {
463 #[cfg(feature = "mock-verify")]
464 ProofVerificationMode::CalibratedMock => vk
465 .mock_verify(pis.into_iter())
466 .map_err(MalformedTransaction::<D>::InvalidProof),
467 _ => vk
468 .verify(&PARAMS_VERIFIER, proof, pis.into_iter())
469 .map_err(MalformedTransaction::<D>::InvalidProof),
470 },
471 }
472 }
473 #[allow(clippy::result_large_err)]
474 fn latest_proof_verify(
475 vk: &VerifierKey,
476 proof: &Self::LatestProof,
477 pis: Vec<Fr>,
478 mode: ProofVerificationMode,
479 ) -> Result<(), MalformedTransaction<D>> {
480 use transient_crypto::proofs::PARAMS_VERIFIER;
481
482 match mode {
483 #[cfg(feature = "mock-verify")]
484 ProofVerificationMode::CalibratedMock => vk
485 .mock_verify(pis.into_iter())
486 .map_err(MalformedTransaction::<D>::InvalidProof),
487 _ => vk
488 .verify(&PARAMS_VERIFIER, proof, pis.into_iter())
489 .map_err(MalformedTransaction::<D>::InvalidProof),
490 }
491 }
492 fn estimated_tx_size<
493 S: SignatureKind<D>,
494 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
495 >(
496 tx: &Transaction<S, Self, B, D>,
497 ) -> usize {
498 tx.serialized_size()
499 }
500}
501
502impl From<ProofPreimage> for ProofPreimageVersioned {
503 fn from(proof: ProofPreimage) -> Self {
504 Self::V2(std::sync::Arc::new(proof))
505 }
506}
507
508impl<D: DB> ProofKind<D> for ProofPreimageMarker {
509 type Pedersen = PedersenRandomness;
510 type Proof = ProofPreimageVersioned;
511 type LatestProof = ProofPreimage;
512 fn zswap_well_formed(
513 offer: &zswap::Offer<Self::LatestProof, D>,
514 segment: u16,
515 ) -> Result<Pedersen, MalformedOffer> {
516 offer.well_formed(segment)
517 }
518 fn zswap_claim_well_formed(
519 _: &zswap::AuthorizedClaim<Self::LatestProof>,
520 ) -> Result<(), MalformedOffer> {
521 Ok(())
522 }
523 fn proof_verify(
524 _: &ContractOperation,
525 _: &Self::Proof,
526 _: Vec<Fr>,
527 _: &ContractCall<Self, D>,
528 _: ProofVerificationMode,
529 ) -> Result<(), MalformedTransaction<D>> {
530 Ok(())
531 }
532 #[allow(clippy::result_large_err)]
533 fn latest_proof_verify(
534 _: &VerifierKey,
535 _: &Self::LatestProof,
536 _: Vec<Fr>,
537 _: ProofVerificationMode,
538 ) -> Result<(), MalformedTransaction<D>> {
539 Ok(())
540 }
541 fn estimated_tx_size<
542 S: SignatureKind<D>,
543 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
544 >(
545 tx: &Transaction<S, Self, B, D>,
546 ) -> usize {
547 <()>::estimated_tx_size(&tx.erase_proofs())
548 }
549}
550
551impl<D: DB> ProofKind<D> for () {
552 type Pedersen = Pedersen;
553 type Proof = ();
554 type LatestProof = ();
555 fn zswap_well_formed(
556 offer: &zswap::Offer<Self::LatestProof, D>,
557 segment: u16,
558 ) -> Result<Pedersen, MalformedOffer> {
559 offer.well_formed(segment)
560 }
561 fn zswap_claim_well_formed(
562 _: &zswap::AuthorizedClaim<Self::LatestProof>,
563 ) -> Result<(), MalformedOffer> {
564 Ok(())
565 }
566 fn proof_verify(
567 _: &ContractOperation,
568 _: &Self::Proof,
569 _: Vec<Fr>,
570 _: &ContractCall<Self, D>,
571 _: ProofVerificationMode,
572 ) -> Result<(), MalformedTransaction<D>> {
573 Ok(())
574 }
575 #[allow(clippy::result_large_err)]
576 fn latest_proof_verify(
577 _: &VerifierKey,
578 _: &Self::LatestProof,
579 _: Vec<Fr>,
580 _: ProofVerificationMode,
581 ) -> Result<(), MalformedTransaction<D>> {
582 Ok(())
583 }
584 fn estimated_tx_size<
585 S: SignatureKind<D>,
586 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
587 >(
588 tx: &Transaction<S, Self, B, D>,
589 ) -> usize {
590 let size = tx.serialized_size();
591 let calls = tx.calls().count();
592 let dust_spends = tx
593 .intents()
594 .map(|(_, intent)| {
595 intent
596 .dust_actions
597 .as_ref()
598 .map(|da| da.spends.len())
599 .unwrap_or(0)
600 })
601 .sum::<usize>();
602 let (zswap_inputs, zswap_outputs) = if let Transaction::Standard(stx) = tx {
603 let transients = stx.transients().count();
604 (
605 stx.inputs().count() + transients,
606 stx.outputs().count() + transients,
607 )
608 } else {
609 (0, 0)
610 };
611 size + calls * PROOF_SIZE
612 + zswap_inputs * zswap::INPUT_PROOF_SIZE
613 + zswap_outputs * zswap::OUTPUT_PROOF_SIZE
614 + dust_spends * DUST_SPEND_PROOF_SIZE
615 }
616}
617
618#[derive(Clone, Debug, PartialEq, Serializable, Storable)]
619#[storable(base)]
620#[tag = "output-instruction-shielded[v2]"]
621pub struct OutputInstructionShielded {
622 pub amount: u128,
623 pub target_key: coin_structure::coin::PublicKey,
624}
625tag_enforcement_test!(OutputInstructionShielded);
626
627#[derive(Copy, Clone, Debug, PartialEq, Eq, Serializable, Storable)]
628#[storable(base)]
629#[tag = "claim-kind[v1]"]
630pub enum ClaimKind {
631 Reward,
632 CardanoBridge,
633}
634tag_enforcement_test!(ClaimKind);
635
636impl Display for ClaimKind {
637 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
638 match self {
639 ClaimKind::Reward => write!(f, "rewards claim"),
640 ClaimKind::CardanoBridge => write!(f, "cardano bridge claim"),
641 }
642 }
643}
644
645impl rand::distributions::Distribution<ClaimKind> for rand::distributions::Standard {
646 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ClaimKind {
647 let b: bool = rng.r#gen();
648 if b {
649 ClaimKind::Reward
650 } else {
651 ClaimKind::CardanoBridge
652 }
653 }
654}
655
656#[derive(Clone, Debug, PartialEq, Serializable, Storable)]
657#[storable(base)]
658#[tag = "output-instruction-unshielded[v1]"]
659pub struct OutputInstructionUnshielded {
660 pub amount: u128,
661 pub target_address: UserAddress,
662 pub nonce: Nonce,
663}
664tag_enforcement_test!(OutputInstructionUnshielded);
665
666impl OutputInstructionUnshielded {
667 pub fn to_hash_data(self, tt: UnshieldedTokenType) -> Vec<u8> {
668 let mut data = Vec::new();
669 data.extend(b"midnight:hash-output-instruction-unshielded:");
670 Serializable::serialize(&tt, &mut data).expect("In-memory serialization should succeed");
671 Serializable::serialize(&self.amount, &mut data)
672 .expect("In-memory serialization should succeed");
673 Serializable::serialize(&self.target_address, &mut data)
674 .expect("In-memory serialization should succeed");
675 Serializable::serialize(&self.nonce, &mut data)
676 .expect("In-memory serialization should succeed");
677 data
678 }
679
680 pub fn mk_intent_hash(self, tt: UnshieldedTokenType) -> IntentHash {
681 IntentHash(persistent_hash(&self.to_hash_data(tt)))
682 }
683}
684
685#[derive(Clone, Debug, PartialEq, Serializable)]
686#[tag = "cnight-generates-dust-action-type"]
687pub enum CNightGeneratesDustActionType {
688 Create,
689 Destroy,
690}
691tag_enforcement_test!(CNightGeneratesDustActionType);
692
693#[derive(Clone, Debug, PartialEq, Serializable)]
694#[tag = "cnight-generates-dust-event[v1]"]
695pub struct CNightGeneratesDustEvent {
696 pub value: u128,
697 pub owner: crate::dust::DustPublicKey,
698 pub time: Timestamp,
699 pub action: CNightGeneratesDustActionType,
700 pub nonce: crate::dust::InitialNonce,
701}
702tag_enforcement_test!(CNightGeneratesDustEvent);
703
704#[derive(Clone, Debug, PartialEq, Serializable, Storable)]
705#[storable(base)]
706#[tag = "cardano-bridge[v1]"]
707pub struct CardanoBridge {
708 pub amount: u128,
709 pub target_address: UserAddress,
710 pub nonce: Nonce,
711}
712tag_enforcement_test!(CardanoBridge);
713
714#[derive(Clone, Debug, PartialEq, Serializable, Storable)]
715#[tag = "system-transaction[v6]"]
716#[storable(base)]
717#[non_exhaustive]
718#[allow(clippy::large_enum_variant)]
720pub enum SystemTransaction {
721 OverwriteParameters(LedgerParameters),
722 DistributeNight(ClaimKind, Vec<OutputInstructionUnshielded>),
723 PayBlockRewardsToTreasury {
724 amount: u128,
725 },
726 PayFromTreasuryShielded {
727 outputs: Vec<OutputInstructionShielded>,
728 nonce: HashOutput,
729 token_type: ShieldedTokenType,
730 },
731 PayFromTreasuryUnshielded {
732 outputs: Vec<OutputInstructionUnshielded>,
733 token_type: UnshieldedTokenType,
734 },
735 DistributeReserve(u128),
736 CNightGeneratesDustUpdate {
737 events: Vec<CNightGeneratesDustEvent>,
738 },
739}
740tag_enforcement_test!(SystemTransaction);
741
742#[derive(Storable)]
743#[derive_where(Clone, PartialEq, Eq)]
744#[storable(db = D)]
745pub struct SegIntent<D: DB>(u16, ErasedIntent<D>);
746
747impl<D: DB> SegIntent<D> {
748 pub fn into_inner(&self) -> (u16, ErasedIntent<D>) {
749 (self.0, self.1.clone())
750 }
751}
752
753#[derive(Storable)]
754#[tag = "unshielded-offer[v1]"]
755#[derive_where(Clone, PartialEq, Eq, PartialOrd, Ord; S)]
756#[storable(db = D)]
757pub struct UnshieldedOffer<S: SignatureKind<D>, D: DB> {
758 pub inputs: storage::storage::Array<UtxoSpend, D>,
759 pub outputs: storage::storage::Array<UtxoOutput, D>,
763 pub signatures: storage::storage::Array<S::Signature<SegIntent<D>>, D>,
766}
767tag_enforcement_test!(UnshieldedOffer<(), InMemoryDB>);
768
769impl<S: SignatureKind<D>, D: DB> fmt::Debug for UnshieldedOffer<S, D> {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 f.debug_struct("UnshieldedOffer")
772 .field("inputs", &self.inputs)
773 .field("outputs", &self.outputs)
774 .field(
775 "signatures",
776 &format_args!("[{} signatures]", self.signatures.len()),
777 )
778 .finish()
779 }
780}
781
782impl<S: SignatureKind<D>, D: DB> UnshieldedOffer<S, D> {
783 pub fn add_signatures(
784 &mut self,
785 signatures: Vec<<S as SignatureKind<D>>::Signature<SegIntent<D>>>,
786 ) {
787 for signature in signatures {
788 self.signatures = self.signatures.push(signature);
789 }
790 }
791
792 pub fn erase_signatures(&self) -> UnshieldedOffer<(), D>
793 where
794 UnshieldedOffer<S, D>: Clone,
795 {
796 UnshieldedOffer {
797 inputs: self.inputs.clone(),
798 outputs: self.outputs.clone(),
799 signatures: vec![].into(),
800 }
801 }
802}
803
804#[derive(
805 Debug,
806 Default,
807 Copy,
808 Clone,
809 Hash,
810 PartialEq,
811 Eq,
812 PartialOrd,
813 Ord,
814 FieldRepr,
815 FromFieldRepr,
816 BinaryHashRepr,
817 Serializable,
818 Storable,
819 Dummy,
820)]
821#[storable(base)]
822#[tag = "intent-hash"]
823pub struct IntentHash(pub HashOutput);
824tag_enforcement_test!(IntentHash);
825
826impl<D: DB> ErasedIntent<D> {
827 pub fn intent_hash(&self, segment_id: u16) -> IntentHash {
828 IntentHash(persistent_hash(&self.data_to_sign(segment_id)))
829 }
830}
831
832impl rand::distributions::Distribution<IntentHash> for rand::distributions::Standard {
833 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> IntentHash {
834 IntentHash(rng.r#gen())
835 }
836}
837
838pub type ErasedIntent<D> = Intent<(), (), Pedersen, D>;
839
840#[derive(Storable)]
841#[tag = "intent[v6]"]
842#[derive_where(Clone, PartialEq, Eq; S, B, P)]
843#[storable(db = D)]
844pub struct Intent<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> {
845 pub guaranteed_unshielded_offer: Option<Sp<UnshieldedOffer<S, D>, D>>,
846 pub fallible_unshielded_offer: Option<Sp<UnshieldedOffer<S, D>, D>>,
847 pub actions: storage::storage::Array<ContractAction<P, D>, D>,
848 pub dust_actions: Option<Sp<DustActions<S, P, D>, D>>,
849 pub ttl: Timestamp,
850 pub binding_commitment: B,
851}
852tag_enforcement_test!(Intent<(), (), Pedersen, InMemoryDB>);
853
854impl<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> Intent<S, P, B, D> {
855 pub fn challenge_pre_for(&self, segment_id: u16) -> Vec<u8> {
856 let mut data = ContractAction::challenge_pre_for(Vec::from(&self.actions).as_slice());
857 let _ = Serializable::serialize(&segment_id, &mut data);
858
859 data
860 }
861}
862
863impl<S: SignatureKind<D> + Debug, P: ProofKind<D>, B: Storable<D>, D: DB> fmt::Debug
864 for Intent<S, P, B, D>
865{
866 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
867 let mut debug_struct = f.debug_struct("Intent");
868 debug_struct
869 .field(
870 "guaranteed_unshielded_offer",
871 &self.guaranteed_unshielded_offer,
872 )
873 .field("fallible_unshielded_offer", &self.fallible_unshielded_offer)
874 .field("actions", &self.actions)
875 .field("ttl", &self.ttl);
876 debug_struct.field("dust_actions", &self.dust_actions);
877 debug_struct
878 .field("binding_commitment", &Symbol("<binding commitment>"))
879 .finish()
880 }
881}
882
883fn to_hash_data<D: DB>(intent: Intent<(), (), Pedersen, D>, mut data: Vec<u8>) -> Vec<u8> {
884 Serializable::serialize(&intent.guaranteed_unshielded_offer, &mut data)
885 .expect("In-memory serialization should succeed");
886 Serializable::serialize(&intent.fallible_unshielded_offer, &mut data)
887 .expect("In-memory serialization should succeed");
888 Serializable::serialize(&intent.actions, &mut data)
889 .expect("In-memory serialization should succeed");
890 Serializable::serialize(&intent.ttl, &mut data)
891 .expect("In-memory serialization should succeed");
892 Serializable::serialize(&intent.binding_commitment, &mut data)
893 .expect("In-memory serialization should succeed");
894 data
895}
896
897impl<
898 S: SignatureKind<D>,
899 P: ProofKind<D>,
900 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
901 D: DB,
902> Intent<S, P, B, D>
903{
904 pub fn erase_proofs(&self) -> Intent<S, (), Pedersen, D>
905 where
906 UnshieldedOffer<S, D>: Clone,
907 {
908 Intent {
909 guaranteed_unshielded_offer: self.guaranteed_unshielded_offer.clone(),
910 fallible_unshielded_offer: self.fallible_unshielded_offer.clone(),
911 actions: self
912 .actions
913 .clone()
914 .iter()
915 .map(|x| x.erase_proof())
916 .collect(),
917 dust_actions: self
918 .dust_actions
919 .as_ref()
920 .map(|act| Sp::new(act.erase_proofs())),
921 ttl: self.ttl,
922 binding_commitment: self.binding_commitment.downgrade(),
923 }
924 }
925
926 pub fn erase_signatures(&self) -> Intent<(), P, B, D>
927 where
928 UnshieldedOffer<S, D>: Clone,
929 {
930 Intent {
931 guaranteed_unshielded_offer: self
932 .guaranteed_unshielded_offer
933 .clone()
934 .map(|x| Sp::new(x.erase_signatures())),
935 fallible_unshielded_offer: self
936 .fallible_unshielded_offer
937 .clone()
938 .map(|x| Sp::new(x.erase_signatures())),
939 actions: self.actions.clone(),
940 dust_actions: self
941 .dust_actions
942 .as_ref()
943 .map(|act| Sp::new(act.erase_signatures())),
944 ttl: self.ttl,
945 binding_commitment: self.binding_commitment.clone(),
946 }
947 }
948
949 pub fn guaranteed_inputs(&self) -> Vec<UtxoSpend> {
950 self.guaranteed_unshielded_offer
951 .clone()
952 .map(|guo| guo.inputs.clone())
953 .unwrap_or_default()
954 .into()
955 }
956
957 pub fn fallible_inputs(&self) -> Vec<UtxoSpend> {
958 self.fallible_unshielded_offer
959 .clone()
960 .map(|fuo| fuo.inputs.clone())
961 .unwrap_or_default()
962 .into()
963 }
964
965 pub fn guaranteed_outputs(&self) -> Vec<UtxoOutput> {
966 self.guaranteed_unshielded_offer
967 .clone()
968 .map(|guo| guo.outputs.clone())
969 .unwrap_or_default()
970 .into()
971 }
972
973 pub fn fallible_outputs(&self) -> Vec<UtxoOutput> {
974 self.fallible_unshielded_offer
975 .clone()
976 .map(|fuo| fuo.outputs.clone())
977 .unwrap_or_default()
978 .into()
979 }
980}
981
982impl<D: DB> Intent<(), (), Pedersen, D> {
983 pub fn data_to_sign(&self, segment_id: u16) -> Vec<u8> {
984 let mut data = Vec::new();
985 data.extend(b"midnight:hash-intent:");
986 Serializable::serialize(&segment_id, &mut data)
987 .expect("In-memory serialization should succeed");
988 to_hash_data::<D>(self.clone(), data)
989 }
990}
991
992#[derive(Storable)]
993#[derive_where(Debug, Clone, PartialEq, Eq)]
994#[storable(db = D)]
995#[tag = "replay-protection-state[v1]"]
996#[must_use]
997pub struct ReplayProtectionState<D: DB> {
998 pub time_filter_map: TimeFilterMap<HashSet<IntentHash, D>, D>,
999}
1000tag_enforcement_test!(ReplayProtectionState<InMemoryDB>);
1001
1002impl<D: DB> ReplayProtectionState<D> {
1003 pub fn new() -> ReplayProtectionState<D> {
1004 ReplayProtectionState {
1005 time_filter_map: TimeFilterMap::new(),
1006 }
1007 }
1008}
1009
1010impl<D: DB> Default for ReplayProtectionState<D> {
1011 fn default() -> Self {
1012 Self::new()
1013 }
1014}
1015
1016#[derive(Clone, Debug, PartialEq, Eq, Serializable, Serialize, Deserialize)]
1017#[tag = "transaction-cost-model[v4]"]
1018pub struct TransactionCostModel {
1019 pub runtime_cost_model: onchain_runtime::cost_model::CostModel,
1020 pub parallelism_factor: u64,
1021 pub baseline_cost: RunningCost,
1022}
1023
1024impl TransactionCostModel {
1025 pub(crate) fn cell_read(&self, size: u64) -> RunningCost {
1026 self.runtime_cost_model.read_cell(size, true)
1027 }
1028 fn cell_write(&self, size: u64, overwrite: bool) -> RunningCost {
1029 RunningCost {
1030 bytes_written: size,
1031 bytes_deleted: if overwrite { size } else { 0 },
1032 ..RunningCost::ZERO
1033 }
1034 }
1035 fn cell_delete(&self, size: u64) -> RunningCost {
1036 RunningCost {
1037 bytes_deleted: size,
1038 ..RunningCost::ZERO
1039 }
1040 }
1041 pub(crate) fn map_index(&self, log_size: usize) -> RunningCost {
1042 self.runtime_cost_model.read_map(log_size, true)
1043 }
1044 pub(crate) fn proof_verify(&self, size: usize) -> RunningCost {
1045 let time = self.runtime_cost_model.proof_verify_constant
1046 + self.runtime_cost_model.proof_verify_coeff_size * size;
1047 RunningCost::compute(time)
1048 }
1049 fn map_insert(&self, log_size: usize, overwrite: bool) -> RunningCost {
1050 let layers = log_size.div_ceil(4);
1051 self.cell_write(PERSISTENT_HASH_BYTES as u64 * 16, true) * layers as u64
1052 + self.cell_write(PERSISTENT_HASH_BYTES as u64, overwrite)
1053 }
1054 fn map_remove(&self, log_size: usize, guaranteed_present: bool) -> RunningCost {
1055 if guaranteed_present {
1056 self.map_insert(log_size, true)
1057 } else {
1058 self.map_insert(log_size, true) + self.map_index(log_size)
1059 }
1060 }
1061 fn time_filter_map_lookup(&self) -> RunningCost {
1062 self.map_index(8) * 2u64
1064 }
1065 fn time_filter_map_insert(&self, overwrite: bool) -> RunningCost {
1066 self.map_insert(8, overwrite) * 2u64
1068 }
1069 fn merkle_tree_index(&self, log_size: usize) -> RunningCost {
1070 self.runtime_cost_model.read_bmt(log_size, true)
1071 }
1072 fn merkle_tree_insert_no_rehash(&self, log_size: usize, overwrite: bool) -> RunningCost {
1073 let raw_writes = self.cell_write(FR_BYTES as u64, overwrite);
1079 let raw_overwrites = self.cell_write((FR_BYTES * 3 + 2) as u64, true) * log_size as u64;
1080 raw_writes + raw_overwrites
1081 }
1082 fn merkle_tree_insert_unamortized(&self, log_size: usize, overwrite: bool) -> RunningCost {
1083 let rehashes = RunningCost::compute(self.runtime_cost_model.transient_hash * log_size);
1084 rehashes + self.merkle_tree_insert_no_rehash(log_size, overwrite)
1085 }
1086 fn merkle_tree_insert_amortized(&self, log_size: usize, overwrite: bool) -> RunningCost {
1087 const EXPECTED_AMORTIZE_HASHES: usize = 6;
1094 let rehashes =
1095 RunningCost::compute(self.runtime_cost_model.transient_hash * EXPECTED_AMORTIZE_HASHES);
1096 rehashes + self.merkle_tree_insert_no_rehash(log_size, overwrite)
1097 }
1098 fn tree_copy<T: Storable<D>, D: DB>(&self, value: Sp<T, D>) -> RunningCost {
1099 self.runtime_cost_model.tree_copy(value)
1100 }
1101 fn stack_setup_cost<D: DB>(&self, transcript: &Transcript<D>) -> RunningCost {
1102 const EXPECTED_COM_INDICES: usize = 16;
1109 let eff = &transcript.effects;
1110 let maps = [
1112 (EXPECTED_COM_INDICES, PERSISTENT_HASH_BYTES),
1113 (eff.claimed_nullifiers.size(), PERSISTENT_HASH_BYTES),
1114 (eff.claimed_shielded_receives.size(), PERSISTENT_HASH_BYTES),
1115 (eff.claimed_shielded_spends.size(), PERSISTENT_HASH_BYTES),
1116 (
1117 eff.claimed_contract_calls.size(),
1118 PERSISTENT_HASH_BYTES * 2 + FR_BYTES + 8,
1119 ),
1120 (eff.shielded_mints.size(), PERSISTENT_HASH_BYTES),
1121 (eff.unshielded_mints.size(), PERSISTENT_HASH_BYTES),
1122 (eff.unshielded_inputs.size(), PERSISTENT_HASH_BYTES + 1),
1123 (eff.unshielded_outputs.size(), PERSISTENT_HASH_BYTES + 1),
1124 (
1125 eff.claimed_unshielded_spends.size(),
1126 PERSISTENT_HASH_BYTES * 2 + 1,
1127 ),
1128 ];
1129 let time = maps
1130 .iter()
1131 .map(|(n, key_len)| {
1132 self.runtime_cost_model.ins_map_constant
1133 + self.runtime_cost_model.ins_map_coeff_container_log_size
1134 * n.next_power_of_two().ilog2() as u64
1135 + self.runtime_cost_model.ins_map_coeff_key_size * *key_len
1136 })
1137 .sum();
1138 RunningCost::compute(time)
1139 }
1140}
1141tag_enforcement_test!(TransactionCostModel);
1142
1143pub const INITIAL_TRANSACTION_COST_MODEL: TransactionCostModel = TransactionCostModel {
1144 runtime_cost_model: onchain_runtime::cost_model::INITIAL_COST_MODEL,
1145 parallelism_factor: 4,
1146 baseline_cost: RunningCost {
1147 compute_time: CostDuration::from_picoseconds(100_000_000),
1148 read_time: CostDuration::ZERO,
1149 bytes_written: 0,
1150 bytes_deleted: 0,
1151 },
1152};
1153
1154#[derive(Clone, Debug, PartialEq, Eq, Serializable)]
1155#[cfg_attr(feature = "fixed-point-custom-serde", derive(Serialize, Deserialize))]
1156#[tag = "transaction-limits[v2]"]
1157pub struct TransactionLimits {
1158 pub transaction_byte_limit: u64,
1159 pub time_to_dismiss_per_byte: CostDuration,
1160 pub min_time_to_dismiss: CostDuration,
1161 pub block_limits: SyntheticCost,
1162 #[cfg_attr(
1166 feature = "fixed-point-custom-serde",
1167 serde(with = "base_crypto::cost_model::fixed_point_custom_serde")
1168 )]
1169 pub block_withdrawal_minimum_multiple: FixedPoint,
1170}
1171tag_enforcement_test!(TransactionLimits);
1172
1173pub const INITIAL_LIMITS: TransactionLimits = TransactionLimits {
1174 transaction_byte_limit: 1 << 20, time_to_dismiss_per_byte: CostDuration::from_picoseconds(2_000_000),
1176 min_time_to_dismiss: CostDuration::from_picoseconds(15_000_000_000),
1177 block_limits: SyntheticCost {
1178 read_time: CostDuration::SECOND,
1179 compute_time: CostDuration::SECOND,
1180 block_usage: 200_000,
1181 bytes_written: 50_000,
1182 bytes_churned: 1_000_000,
1183 },
1184 block_withdrawal_minimum_multiple: FixedPoint::from_u64_div(1, 2),
1185};
1186
1187#[derive(Clone, Debug, PartialEq, Eq, Serializable, Storable)]
1188#[cfg_attr(
1189 feature = "fixed-point-custom-serde",
1190 derive(serde::Serialize, serde::Deserialize)
1191)]
1192#[tag = "ledger-parameters[v5]"]
1193#[storable(base)]
1194pub struct LedgerParameters {
1195 pub cost_model: TransactionCostModel,
1196 pub limits: TransactionLimits,
1197 pub dust: DustParameters,
1198 pub fee_prices: FeePrices,
1199 pub global_ttl: Duration,
1200 #[cfg_attr(
1202 feature = "fixed-point-custom-serde",
1203 serde(with = "base_crypto::cost_model::fixed_point_custom_serde")
1204 )]
1205 pub cost_dimension_min_ratio: FixedPoint,
1206 #[cfg_attr(
1207 feature = "fixed-point-custom-serde",
1208 serde(with = "base_crypto::cost_model::fixed_point_custom_serde")
1209 )]
1210 pub price_adjustment_a_parameter: FixedPoint,
1211 pub cardano_to_midnight_bridge_fee_basis_points: u32,
1214 pub c_to_m_bridge_min_amount: u128,
1216}
1217tag_enforcement_test!(LedgerParameters);
1218
1219impl LedgerParameters {
1220 pub fn max_price_adjustment(&self) -> FixedPoint {
1226 price_adjustment_function(FixedPoint::ONE, self.price_adjustment_a_parameter)
1227 + FixedPoint::ONE
1228 }
1229
1230 pub fn min_claimable_rewards(&self) -> u128 {
1231 let Ok(synthetic_fee) =
1232 Transaction::ClaimRewards::<Signature, ProofMarker, PureGeneratorPedersen, InMemoryDB>(
1233 ClaimRewardsTransaction {
1234 network_id: "phantom-value".into(),
1235 value: u128::MAX,
1236 owner: Default::default(),
1237 nonce: Default::default(),
1238 signature: Default::default(),
1239 kind: ClaimKind::Reward,
1240 },
1241 )
1242 .cost(self, true)
1243 else {
1244 return u128::MAX;
1245 };
1246 let real_dust_fee = synthetic_fee
1247 .normalize(self.limits.block_limits)
1248 .map(|norm| self.fee_prices.overall_cost(&norm))
1249 .unwrap_or(FixedPoint::MAX);
1250 let night_dust_fp = FixedPoint::from_u64_div(
1251 self.dust.night_dust_ratio,
1252 (SPECKS_PER_DUST / STARS_PER_NIGHT) as u64,
1253 );
1254 let night_to_pay_at_cap = real_dust_fee / night_dust_fp;
1255 let min_night_fp = night_to_pay_at_cap * self.limits.block_withdrawal_minimum_multiple;
1256 min_night_fp.into_atomic_units(STARS_PER_NIGHT)
1257 }
1258}
1259
1260pub const INITIAL_PARAMETERS: LedgerParameters = LedgerParameters {
1261 cost_model: INITIAL_TRANSACTION_COST_MODEL,
1262 limits: INITIAL_LIMITS,
1263 dust: INITIAL_DUST_PARAMETERS,
1264 fee_prices: FeePrices {
1265 overall_price: FixedPoint::from_u64_div(10, 1),
1266 read_factor: FixedPoint::ONE,
1267 compute_factor: FixedPoint::ONE,
1268 block_usage_factor: FixedPoint::ONE,
1269 write_factor: FixedPoint::ONE,
1270 },
1271 global_ttl: Duration::from_secs(3600),
1272 cardano_to_midnight_bridge_fee_basis_points: 500,
1273 cost_dimension_min_ratio: FixedPoint::from_u64_div(1, 4),
1274 price_adjustment_a_parameter: FixedPoint::from_u64_div(100, 1),
1275 c_to_m_bridge_min_amount: 1000,
1276};
1277
1278#[derive(Storable)]
1279#[storable(db = D)]
1280#[derive_where(Clone; S, B, P)]
1281#[tag = "transaction[v9]"]
1282#[allow(clippy::large_enum_variant)]
1284pub enum Transaction<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> {
1285 Standard(StandardTransaction<S, P, B, D>),
1286 ClaimRewards(ClaimRewardsTransaction<S, D>),
1287}
1288tag_enforcement_test!(Transaction<(), (), Pedersen, InMemoryDB>);
1289
1290#[derive_where(Debug, Clone)]
1291pub struct VerifiedTransaction<D: DB> {
1292 pub(crate) inner: Transaction<(), (), Pedersen, D>,
1293 pub(crate) hash: TransactionHash,
1294}
1295
1296impl<D: DB> Deref for VerifiedTransaction<D> {
1297 type Target = Transaction<(), (), Pedersen, D>;
1298 fn deref(&self) -> &Self::Target {
1299 &self.inner
1300 }
1301}
1302
1303impl<S: SignatureKind<D> + Debug, P: ProofKind<D> + Debug, B: Storable<D> + Debug, D: DB> Debug
1304 for Transaction<S, P, B, D>
1305{
1306 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1307 match self {
1308 Transaction::Standard(stx) => stx.fmt(f),
1309 Transaction::ClaimRewards(cm) => cm.fmt(f),
1310 }
1311 }
1312}
1313
1314impl<
1315 S: SignatureKind<D>,
1316 P: ProofKind<D>,
1317 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
1318 D: DB,
1319> Transaction<S, P, B, D>
1320{
1321 pub fn erase_proofs(&self) -> Transaction<S, (), Pedersen, D> {
1322 match self {
1323 Transaction::Standard(StandardTransaction {
1324 network_id,
1325 intents,
1326 guaranteed_coins,
1327 fallible_coins,
1328 binding_randomness,
1329 ..
1330 }) => Transaction::Standard(StandardTransaction {
1331 network_id: network_id.clone(),
1332 intents: intents
1333 .iter()
1334 .map(|seg_intent| {
1335 (
1336 *seg_intent.deref().0.deref(),
1337 seg_intent.deref().1.deref().erase_proofs(),
1338 )
1339 })
1340 .collect(),
1341 guaranteed_coins: guaranteed_coins
1342 .as_ref()
1343 .map(|x| Sp::new(ZswapOffer::erase_proofs(x.deref()))),
1344 fallible_coins: fallible_coins
1345 .iter()
1346 .map(|sp| (*sp.deref().0.deref(), sp.deref().1.deref().erase_proofs()))
1347 .collect(),
1348 binding_randomness: *binding_randomness,
1349 }),
1350 Transaction::ClaimRewards(claim) => Transaction::ClaimRewards(claim.clone()),
1351 }
1352 }
1353
1354 pub fn erase_signatures(&self) -> Transaction<(), P, B, D> {
1355 match self {
1356 Transaction::Standard(StandardTransaction {
1357 network_id,
1358 intents,
1359 guaranteed_coins,
1360 fallible_coins,
1361 binding_randomness,
1362 ..
1363 }) => Transaction::Standard(StandardTransaction {
1364 network_id: network_id.clone(),
1365 intents: intents
1366 .iter()
1367 .map(|sp| {
1368 (
1369 *sp.deref().0.deref(),
1370 sp.deref().1.deref().erase_signatures(),
1371 )
1372 })
1373 .collect(),
1374 guaranteed_coins: guaranteed_coins.clone(),
1375 fallible_coins: fallible_coins.clone(),
1376 binding_randomness: *binding_randomness,
1377 }),
1378 Transaction::ClaimRewards(claim) => Transaction::ClaimRewards(claim.erase_signatures()),
1379 }
1380 }
1381
1382 #[instrument(skip(self, other))]
1383 pub fn merge(&self, other: &Self) -> Result<Self, MalformedTransaction<D>> {
1384 use Transaction as T;
1385 match (self, other) {
1386 (T::Standard(stx1), T::Standard(stx2)) => {
1387 if stx1.network_id != stx2.network_id {
1388 return Err(MalformedTransaction::InvalidNetworkId {
1389 expected: stx1.network_id.clone(),
1390 found: stx2.network_id.clone(),
1391 });
1392 }
1393 let res = Transaction::Standard(StandardTransaction {
1394 network_id: stx1.network_id.clone(),
1395 intents: stx1
1396 .intents
1397 .clone()
1398 .into_iter()
1399 .chain(stx2.intents.clone())
1400 .try_fold(HashMap::new(), |mut acc, (k, v)| {
1402 if acc.contains_key(&k) {
1403 Err(MalformedTransaction::<D>::IntentSegmentIdCollision(k))
1404 } else {
1405 acc = acc.insert(k, v);
1406 Ok(acc)
1407 }
1408 })?,
1409 guaranteed_coins: match (
1410 stx1.guaranteed_coins.as_ref(),
1411 stx2.guaranteed_coins.as_ref(),
1412 ) {
1413 (Some(gc1), Some(gc2)) => Some(Sp::new(
1414 gc1.merge(gc2).map_err(MalformedTransaction::<D>::Zswap)?,
1415 )),
1416 (Some(gc1), None) => Some(gc1.clone()),
1417 (None, Some(gc2)) => Some(gc2.clone()),
1418 (None, None) => None,
1419 },
1420 fallible_coins: {
1421 let mut result: std::collections::BTreeMap<
1422 u16,
1423 ZswapOffer<P::LatestProof, D>,
1424 > = stx1.fallible_coins.clone().into_iter().collect();
1425 for (key, offer2) in stx2.fallible_coins.clone() {
1426 match result.get(&key) {
1427 Some(offer1) => {
1428 result.insert(key, offer1.merge(&offer2)?);
1429 }
1430 None => {
1431 result.insert(key, offer2.clone());
1432 }
1433 }
1434 }
1435
1436 result.into_iter().collect()
1437 },
1438 binding_randomness: stx1.binding_randomness + stx2.binding_randomness,
1439 });
1440 debug!("transaction merged");
1441 Ok(res)
1442 }
1443 _ => Err(MalformedTransaction::<D>::CantMergeTypes),
1444 }
1445 }
1446
1447 pub fn identifiers(&'_ self) -> impl Iterator<Item = TransactionIdentifier> + '_ {
1448 let mut res = Vec::new();
1449 match self {
1450 Transaction::Standard(stx) => res.extend(
1451 stx.inputs()
1452 .map(|i| i.value_commitment)
1453 .chain(stx.outputs().map(|o| o.value_commitment))
1454 .chain(stx.transients().map(|io| io.value_commitment_input))
1455 .chain(stx.transients().map(|io| io.value_commitment_output))
1456 .chain(
1457 stx.intents()
1458 .map(|(_, intent)| intent.binding_commitment.downgrade()),
1459 )
1460 .map(TransactionIdentifier::Merged),
1461 ),
1462 Transaction::ClaimRewards(claim) => res.push(TransactionIdentifier::Unique(
1463 OutputInstructionUnshielded {
1464 amount: claim.value,
1465 target_address: claim.owner.clone().into(),
1466 nonce: claim.nonce,
1467 }
1468 .clone()
1469 .mk_intent_hash(NIGHT)
1470 .0,
1471 )),
1472 }
1473 res.into_iter()
1474 }
1475
1476 pub fn actions(&self) -> impl Iterator<Item = (u16, ContractAction<P, D>)> {
1477 match self {
1478 Transaction::Standard(stx) => stx.actions().collect(),
1479 _ => Vec::new(),
1480 }
1481 .into_iter()
1482 }
1483
1484 pub fn deploys(&self) -> impl Iterator<Item = (u16, ContractDeploy<D>)> {
1485 match self {
1486 Transaction::Standard(stx) => stx.deploys().collect(),
1487 _ => Vec::new(),
1488 }
1489 .into_iter()
1490 }
1491
1492 pub fn updates(&'_ self) -> impl Iterator<Item = (u16, MaintenanceUpdate<D>)> {
1493 match self {
1494 Transaction::Standard(stx) => stx.updates().collect(),
1495 _ => Vec::new(),
1496 }
1497 .into_iter()
1498 }
1499
1500 pub fn calls(&'_ self) -> impl Iterator<Item = (u16, ContractCall<P, D>)> {
1501 match self {
1502 Transaction::Standard(stx) => stx.calls().collect(),
1503 _ => Vec::new(),
1504 }
1505 .into_iter()
1506 }
1507
1508 pub fn intents(&'_ self) -> impl Iterator<Item = (u16, Intent<S, P, B, D>)> {
1509 match self {
1510 Transaction::Standard(stx) => stx.intents().collect(),
1511 _ => Vec::new(),
1512 }
1513 .into_iter()
1514 }
1515
1516 pub fn has_identifier(&self, ident: &TransactionIdentifier) -> bool {
1517 self.identifiers().any(|ident2| ident == &ident2)
1518 }
1519}
1520
1521impl<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> Transaction<S, P, B, D>
1522where
1523 Transaction<S, P, B, D>: Serializable,
1524{
1525 pub fn segments(&self) -> Vec<u16> {
1526 match self {
1527 Self::Standard(stx) => stx.segments(),
1528 Self::ClaimRewards(_) => Vec::new(),
1529 }
1530 }
1531}
1532
1533impl<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> Intent<S, P, B, D> {
1534 pub fn calls(&self) -> impl Iterator<Item = &ContractCall<P, D>> {
1535 self.actions.iter_deref().filter_map(|cd| match cd {
1536 ContractAction::Call(upd) => Some(&**upd),
1537 _ => None,
1538 })
1539 }
1540
1541 pub fn calls_owned(&self) -> Vec<ContractCall<P, D>>
1542 where
1543 ContractCall<P, D>: Clone,
1544 {
1545 self.actions
1546 .iter_deref()
1547 .filter_map(|cd| match cd {
1548 ContractAction::Call(upd) => Some((**upd).clone()),
1549 _ => None,
1550 })
1551 .collect()
1552 }
1553}
1554
1555#[derive(Storable)]
1556#[storable(db = D)]
1557#[derive_where(Clone, Debug; S, P, B)]
1558#[tag = "standard-transaction[v9]"]
1559pub struct StandardTransaction<S: SignatureKind<D>, P: ProofKind<D>, B: Storable<D>, D: DB> {
1560 pub network_id: String,
1561 pub intents: HashMap<u16, Intent<S, P, B, D>, D>,
1562 pub guaranteed_coins: Option<Sp<ZswapOffer<P::LatestProof, D>, D>>,
1563 pub fallible_coins: HashMap<u16, ZswapOffer<P::LatestProof, D>, D>,
1564 pub binding_randomness: PedersenRandomness,
1565}
1566tag_enforcement_test!(StandardTransaction<(), (), Pedersen, InMemoryDB>);
1567
1568impl<S: SignatureKind<D>, P: ProofKind<D> + Serializable + Deserializable, B: Storable<D>, D: DB>
1569 StandardTransaction<S, P, B, D>
1570{
1571 pub fn actions(&self) -> impl Iterator<Item = (u16, ContractAction<P, D>)> {
1572 self.intents
1573 .clone()
1574 .into_iter()
1575 .flat_map(|(segment_id, intent)| {
1576 Vec::from(&intent.actions)
1577 .into_iter()
1578 .map(move |act| (segment_id, act))
1579 })
1580 }
1581
1582 pub fn deploys(&self) -> impl Iterator<Item = (u16, ContractDeploy<D>)> {
1583 self.intents
1584 .clone()
1585 .into_iter()
1586 .flat_map(|(segment_id, intent)| {
1587 Vec::from(&intent.actions)
1588 .into_iter()
1589 .map(move |act| (segment_id, act))
1590 })
1591 .filter_map(|(segment_id, action)| match action {
1592 ContractAction::Deploy(d) => Some((segment_id, (*d).clone())),
1593 _ => None,
1594 })
1595 }
1596
1597 pub fn updates(&self) -> impl Iterator<Item = (u16, MaintenanceUpdate<D>)> {
1598 self.intents
1599 .clone()
1600 .into_iter()
1601 .flat_map(|(segment_id, intent)| {
1602 Vec::from(&intent.actions)
1603 .into_iter()
1604 .map(move |act| (segment_id, act))
1605 })
1606 .filter_map(|(segment_id, action)| match action {
1607 ContractAction::Maintain(upd) => Some((segment_id, upd)),
1608 _ => None,
1609 })
1610 }
1611
1612 pub fn calls(&self) -> impl Iterator<Item = (u16, ContractCall<P, D>)> {
1613 self.intents
1614 .clone()
1615 .into_iter()
1616 .flat_map(|(segment_id, intent)| {
1617 Vec::from(&intent.actions)
1618 .into_iter()
1619 .filter_map(move |action| {
1620 if let ContractAction::Call(upd) = action {
1621 Some((segment_id, (*upd).clone()))
1622 } else {
1623 None
1624 }
1625 })
1626 })
1627 }
1628
1629 pub fn intents(&'_ self) -> impl Iterator<Item = (u16, Intent<S, P, B, D>)> {
1630 self.intents.clone().into_iter()
1631 }
1632
1633 pub fn inputs(
1634 &self,
1635 ) -> impl Iterator<Item = Input<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1636 {
1637 self.guaranteed_inputs().chain(self.fallible_inputs())
1638 }
1639
1640 pub fn guaranteed_inputs(
1641 &self,
1642 ) -> impl Iterator<Item = Input<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1643 {
1644 self.guaranteed_coins
1645 .iter()
1646 .flat_map(|gc| Vec::from(&gc.inputs).into_iter())
1647 }
1648
1649 pub fn fallible_inputs(
1650 &self,
1651 ) -> impl Iterator<Item = Input<<P as ProofKind<D>>::LatestProof, D>> {
1652 self.fallible_coins
1653 .iter()
1654 .flat_map(|sp| Vec::from(&sp.1.inputs).into_iter())
1655 }
1656
1657 pub fn outputs(
1658 &self,
1659 ) -> impl Iterator<Item = Output<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1660 {
1661 self.guaranteed_outputs().chain(self.fallible_outputs())
1662 }
1663
1664 pub fn guaranteed_outputs(
1665 &self,
1666 ) -> impl Iterator<Item = Output<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1667 {
1668 self.guaranteed_coins
1669 .iter()
1670 .flat_map(|gc| Vec::from(&gc.outputs).into_iter())
1671 }
1672
1673 pub fn fallible_outputs(
1674 &'_ self,
1675 ) -> impl Iterator<Item = Output<<P as ProofKind<D>>::LatestProof, D>> {
1676 self.fallible_coins
1677 .iter()
1678 .flat_map(|sp| Vec::from(&sp.1.outputs).into_iter())
1679 }
1680
1681 pub fn transients(
1682 &self,
1683 ) -> impl Iterator<Item = Transient<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1684 {
1685 self.guaranteed_transients()
1686 .chain(self.fallible_transients())
1687 }
1688
1689 pub fn guaranteed_transients(
1690 &self,
1691 ) -> impl Iterator<Item = Transient<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1692 {
1693 self.guaranteed_coins
1694 .iter()
1695 .flat_map(|offer| Vec::from(&offer.transient).into_iter())
1696 }
1697
1698 pub fn fallible_transients(
1699 &self,
1700 ) -> impl Iterator<Item = Transient<<P as ProofKind<D>>::LatestProof, D>> + use<'_, S, P, B, D>
1701 {
1702 self.fallible_coins
1703 .iter()
1704 .flat_map(|offer| Vec::from(&offer.1.transient).into_iter())
1705 }
1706
1707 pub fn segments(&self) -> Vec<u16> {
1708 let mut segments = once(0)
1709 .chain(self.intents.iter().map(|seg_intent| *seg_intent.0))
1710 .chain(self.fallible_coins.iter().map(|seg_offer| *seg_offer.0))
1711 .collect::<Vec<_>>();
1712 segments.sort();
1713 segments.dedup();
1714 segments
1715 }
1716}
1717
1718type ErasedClaimRewardsTransaction<D> = ClaimRewardsTransaction<(), D>;
1719
1720#[derive(Storable)]
1721#[derive_where(Clone, PartialEq, Eq; S)]
1722#[storable(db = D)]
1723#[tag = "claim-rewards-transaction[v1]"]
1724pub struct ClaimRewardsTransaction<S: SignatureKind<D>, D: DB> {
1725 pub network_id: String,
1726 pub value: u128,
1727 pub owner: VerifyingKey,
1728 pub nonce: Nonce,
1729 pub signature: S::Signature<ErasedClaimRewardsTransaction<D>>,
1730 pub kind: ClaimKind,
1731}
1732tag_enforcement_test!(ClaimRewardsTransaction<(), InMemoryDB>);
1733
1734impl<S: SignatureKind<D>, D: DB> ClaimRewardsTransaction<S, D> {
1735 pub fn add_signature(&self, signature: Signature) -> ClaimRewardsTransaction<Signature, D> {
1736 ClaimRewardsTransaction {
1737 network_id: self.network_id.clone(),
1738 value: self.value,
1739 owner: self.owner.clone(),
1740 nonce: self.nonce,
1741 signature,
1742 kind: self.kind,
1743 }
1744 }
1745
1746 pub fn erase_signatures(&self) -> ErasedClaimRewardsTransaction<D> {
1747 ClaimRewardsTransaction {
1748 network_id: self.network_id.clone(),
1749 value: self.value,
1750 owner: self.owner.clone(),
1751 nonce: self.nonce,
1752 signature: (),
1753 kind: self.kind,
1754 }
1755 }
1756}
1757
1758impl<D: DB> ClaimRewardsTransaction<(), D> {
1759 pub fn data_to_sign(&self) -> Vec<u8> {
1760 let mut data = Vec::new();
1761 data.extend(b"midnight:sig-claim_rewards_transaction:");
1762 Self::to_hash_data(self.clone(), data)
1763 }
1764
1765 pub fn to_hash_data(rewards: ClaimRewardsTransaction<(), D>, mut data: Vec<u8>) -> Vec<u8> {
1766 Serializable::serialize(&rewards.value, &mut data)
1767 .expect("In-memory serialization should succeed");
1768 Serializable::serialize(&rewards.owner, &mut data)
1769 .expect("In-memory serialization should succeed");
1770 Serializable::serialize(&rewards.nonce, &mut data)
1771 .expect("In-memory serialization should succeed");
1772 data
1773 }
1774}
1775
1776impl<S: SignatureKind<D>, D: DB> Debug for ClaimRewardsTransaction<S, D> {
1777 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
1778 write!(
1779 formatter,
1780 "<rewards of {} of Night for recipient {:?}>",
1781 self.value, self.owner
1782 )
1783 }
1784}
1785
1786pub(crate) const MIN_PROOF_SIZE: usize = DUST_SPEND_PROOF_SIZE;
1787pub(crate) const PROOF_SIZE: usize = zswap::INPUT_PROOF_SIZE;
1788pub(crate) const VERIFIER_KEY_SIZE: usize = 2875;
1791pub(crate) const EXPECTED_CONTRACT_DEPTH: usize = 32;
1792pub(crate) const EXPECTED_UTXO_DEPTH: usize = 32;
1793pub(crate) const EXPECTED_GENERATION_DEPTH: usize = 32;
1794pub(crate) const EXPECTED_OPERATIONS_DEPTH: usize = 8;
1795pub(crate) const EXPECTED_TOKEN_TYPE_DEPTH: usize = 8;
1796pub(crate) const EXPECTED_TIME_FILTER_MAP_DEPTH: usize = 8;
1797pub(crate) const FRESH_DUST_COMMITMENT_HASHES: usize = 6;
1798
1799impl<
1800 S: SignatureKind<D>,
1801 P: ProofKind<D>,
1802 B: Storable<D> + PedersenDowngradeable<D> + Serializable,
1803 D: DB,
1804> Transaction<S, P, B, D>
1805where
1806 Transaction<S, P, B, D>: Serializable,
1807{
1808 pub fn fees_with_margin(
1809 &self,
1810 params: &LedgerParameters,
1811 margin: usize,
1812 ) -> Result<u128, FeeCalculationError> {
1813 let synthetic = self.cost(params, false)?;
1814 let normalized = synthetic
1815 .normalize(params.limits.block_limits)
1816 .ok_or(FeeCalculationError::BlockLimitExceeded)?;
1817 let fees_fixed_point = params.fee_prices.overall_cost(&normalized);
1818 let margin_fees = fees_fixed_point * params.max_price_adjustment().powi(margin as i32);
1819 Ok(margin_fees.into_atomic_units(SPECKS_PER_DUST))
1820 }
1821
1822 pub fn fees(
1823 &self,
1824 params: &LedgerParameters,
1825 enforce_time_to_dismiss: bool,
1826 ) -> Result<u128, FeeCalculationError> {
1827 let synthetic = self.cost(params, enforce_time_to_dismiss)?;
1828 let normalized = synthetic
1829 .normalize(params.limits.block_limits)
1830 .ok_or(FeeCalculationError::BlockLimitExceeded)?;
1831 let fees_fixed_point = params.fee_prices.overall_cost(&normalized);
1832 Ok(fees_fixed_point.into_atomic_units(SPECKS_PER_DUST))
1833 }
1834
1835 pub fn validation_cost(&self, model: &TransactionCostModel) -> SyntheticCost {
1836 match self {
1837 Transaction::Standard(stx) => {
1838 let vk_reads = self
1839 .calls()
1840 .map(|(_, call)| (call.address, call.entry_point))
1841 .collect::<BTreeSet<_>>()
1842 .len();
1843 let mut cost = model.baseline_cost;
1844 cost += (model.cell_read(VERIFIER_KEY_SIZE as u64)
1845 + model.map_index(EXPECTED_CONTRACT_DEPTH)
1846 + model.map_index(EXPECTED_OPERATIONS_DEPTH))
1847 * vk_reads;
1848 let offers = stx
1849 .guaranteed_coins
1850 .iter()
1851 .map(|o| (**o).clone())
1852 .chain(stx.fallible_coins.values())
1853 .collect::<Vec<_>>();
1854 let zswap_inputs = offers
1855 .iter()
1856 .map(|offer| offer.inputs.len() + offer.transient.len())
1857 .sum::<usize>();
1858 let zswap_outputs = offers
1859 .iter()
1860 .map(|offer| offer.outputs.len() + offer.transient.len())
1861 .sum::<usize>();
1862 cost += model.proof_verify(zswap::INPUT_PIS) * zswap_inputs;
1863 cost += model.proof_verify(zswap::OUTPUT_PIS) * zswap_outputs;
1864 for intent in stx.intents.values() {
1865 cost.compute_time += model.runtime_cost_model.pedersen_valid;
1867 cost.compute_time += intent
1869 .guaranteed_unshielded_offer
1870 .iter()
1871 .chain(intent.fallible_unshielded_offer.iter())
1872 .map(|o| o.signatures.len())
1873 .sum::<usize>()
1874 * model.runtime_cost_model.signature_verify_constant;
1875 for action in intent.actions.iter() {
1876 match &*action {
1877 ContractAction::Call(call) => {
1878 cost.compute_time += model.runtime_cost_model.verifier_key_load;
1879 cost += model
1880 .proof_verify(call.public_inputs(Default::default()).len());
1881 }
1882 ContractAction::Maintain(upd) => {
1883 cost.compute_time +=
1884 model.runtime_cost_model.signature_verify_constant
1885 * upd.signatures.len();
1886 }
1887 _ => {}
1888 }
1889 }
1890 if let Some(dust_actions) = intent.dust_actions {
1891 cost += model.proof_verify(DUST_SPEND_PIS) * dust_actions.spends.len();
1892 cost.compute_time += model.runtime_cost_model.signature_verify_constant
1893 * dust_actions.registrations.len();
1894 }
1895 }
1896 cost.compute_time += offers.iter().map(|o| o.deltas.len()).sum::<usize>()
1898 * (model.runtime_cost_model.hash_to_curve + model.runtime_cost_model.ec_mul);
1899 cost.compute_time += model.runtime_cost_model.ec_mul;
1900 let mut res = SyntheticCost::from(cost);
1901 res.block_usage = self.est_size() as u64;
1902 res
1903 }
1904 Transaction::ClaimRewards(_) => (RunningCost {
1905 compute_time: model.runtime_cost_model.signature_verify_constant,
1906 ..RunningCost::ZERO
1907 } + model.baseline_cost)
1908 .into(),
1909 }
1910 }
1911
1912 fn est_size(&self) -> usize {
1913 P::estimated_tx_size(self)
1914 }
1915
1916 pub fn application_cost(&self, model: &TransactionCostModel) -> (SyntheticCost, SyntheticCost) {
1917 let mut g_cost = model.baseline_cost;
1918 let mut f_cost = RunningCost::ZERO;
1919 for (_, intent) in self.intents() {
1920 g_cost +=
1922 model.time_filter_map_lookup() + model.cell_read(PERSISTENT_HASH_BYTES as u64);
1923 g_cost += model.time_filter_map_insert(false)
1924 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false);
1925 for (cost, offer) in [
1927 (&mut g_cost, intent.guaranteed_unshielded_offer.as_ref()),
1928 (&mut f_cost, intent.fallible_unshielded_offer.as_ref()),
1929 ]
1930 .into_iter()
1931 .filter_map(|(c, o)| o.map(|o| (c, o)))
1932 {
1933 let inputs = offer.inputs.len();
1934 let outputs = offer.outputs.len();
1935 *cost += model.map_index(EXPECTED_UTXO_DEPTH) * inputs;
1937 *cost += (model.map_remove(EXPECTED_UTXO_DEPTH, true)
1939 + model.cell_delete(UTXO_SIZE as u64))
1940 * inputs;
1941 *cost += (model.map_insert(EXPECTED_UTXO_DEPTH, false)
1943 + model.cell_write(UTXO_SIZE as u64, false))
1944 * outputs;
1945 let night_inputs = offer.inputs.iter().filter(|i| i.type_ == NIGHT).count();
1946 let night_outputs = offer.outputs.iter().filter(|o| o.type_ == NIGHT).count();
1947 *cost += (model.merkle_tree_insert_unamortized(32, false)
1949 + model.cell_write(DUST_GENERATION_INFO_SIZE as u64, false))
1950 * night_inputs;
1951 *cost += (model.map_index(EXPECTED_UTXO_DEPTH) + model.cell_read(FR_BYTES as u64))
1953 * night_outputs;
1954 *cost += (model.cell_read(8) + model.cell_write(8, true)) * night_outputs;
1956 *cost += (model.merkle_tree_insert_amortized(32, false)
1957 + model.cell_write(DUST_GENERATION_INFO_SIZE as u64, false))
1958 * night_outputs;
1959 *cost += (model.map_insert(EXPECTED_UTXO_DEPTH, false)
1961 + model.cell_write(8, false))
1962 * night_outputs;
1963 *cost += (model.cell_read(8) + model.cell_write(8, true)) * night_outputs;
1965 *cost += (model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false)
1966 + model.cell_write(FR_BYTES as u64, false))
1967 * night_outputs;
1968 *cost += RunningCost::compute(
1970 model.runtime_cost_model.transient_hash
1971 * FRESH_DUST_COMMITMENT_HASHES
1972 * night_outputs,
1973 );
1974 }
1975 let dust_spends = intent
1976 .dust_actions
1977 .as_ref()
1978 .map(|a| a.spends.len())
1979 .unwrap_or(0);
1980 g_cost += model.map_index(32) * dust_spends;
1982 g_cost += (model.map_insert(EXPECTED_UTXO_DEPTH, false)
1984 + model.cell_write(FR_BYTES as u64, false))
1985 * dust_spends;
1986 g_cost += (model.cell_read(8) + model.cell_write(8, true)) * dust_spends;
1988 g_cost += (model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false)
1989 + model.cell_write(FR_BYTES as u64, false))
1990 * dust_spends;
1991 g_cost += model.time_filter_map_lookup() * 2u64;
1993 for reg in intent
1994 .dust_actions
1995 .iter()
1996 .flat_map(|a| a.registrations.iter())
1997 {
1998 if reg.dust_address.is_some() {
2000 g_cost += model.map_insert(EXPECTED_GENERATION_DEPTH, false)
2001 + model.cell_write(FR_BYTES as u64, false);
2002 } else {
2003 g_cost += model.map_remove(EXPECTED_GENERATION_DEPTH, true)
2004 + model.cell_delete(FR_BYTES as u64);
2005 }
2006 if reg.dust_address.is_some() {
2010 let night_inputs = intent
2011 .guaranteed_unshielded_offer
2012 .iter()
2013 .flat_map(|o| o.inputs.iter())
2014 .filter(|i| i.owner == reg.night_key && i.type_ == NIGHT)
2015 .count();
2016 g_cost +=
2018 (model.map_index(EXPECTED_UTXO_DEPTH) + model.cell_read(8)) * night_inputs;
2019 g_cost += (model.merkle_tree_index(EXPECTED_UTXO_DEPTH)
2021 + model.cell_read(DUST_GENERATION_INFO_SIZE as u64))
2022 * night_inputs;
2023 }
2024 }
2025 for action in intent.actions.iter() {
2027 match &*action {
2028 ContractAction::Call(call) => {
2029 let base_cost = if call.guaranteed_transcript.is_some() {
2030 &mut g_cost
2031 } else {
2032 &mut f_cost
2033 };
2034 *base_cost += model.map_index(EXPECTED_CONTRACT_DEPTH)
2036 + model.map_insert(EXPECTED_CONTRACT_DEPTH, true)
2037 + model.map_index(1)
2038 + model.map_insert(1, true);
2039 for (cost, transcript) in [
2048 (&mut g_cost, call.guaranteed_transcript.as_ref()),
2049 (&mut f_cost, call.fallible_transcript.as_ref()),
2050 ]
2051 .into_iter()
2052 .filter_map(|(c, t)| t.map(|t| (c, t)))
2053 {
2054 *cost += transcript.gas;
2055 *cost += model.stack_setup_cost(transcript);
2059 }
2060 }
2061 ContractAction::Deploy(deploy) => {
2062 f_cost += model.map_index(EXPECTED_CONTRACT_DEPTH);
2064 f_cost += model.map_insert(EXPECTED_CONTRACT_DEPTH, false)
2066 + model.tree_copy(Sp::new(deploy.initial_state.clone()));
2067 }
2068 ContractAction::Maintain(upd) => {
2069 f_cost += model.map_index(EXPECTED_CONTRACT_DEPTH);
2071 f_cost += model.map_index(1) * 2u64
2073 + model.map_insert(1, true) * 2u64
2074 + model.cell_read(8)
2075 + model.cell_write(8, true);
2076 for part in upd.updates.iter() {
2078 match &*part {
2079 SingleUpdate::ReplaceAuthority(auth) => {
2080 f_cost += model.tree_copy::<_, D>(Sp::new(auth.clone()))
2081 + model.map_insert(1, true)
2082 }
2083 SingleUpdate::VerifierKeyRemove(..) => {
2084 f_cost += model.map_remove(EXPECTED_OPERATIONS_DEPTH, true)
2085 + model.cell_delete(VERIFIER_KEY_SIZE as u64)
2086 + model.map_insert(1, true)
2087 }
2088 SingleUpdate::VerifierKeyInsert(..) => {
2089 f_cost += model.map_insert(EXPECTED_OPERATIONS_DEPTH, false)
2090 + model.cell_write(VERIFIER_KEY_SIZE as u64, false)
2091 + model.map_insert(1, true)
2092 }
2093 }
2094 }
2095 f_cost += model.map_insert(EXPECTED_CONTRACT_DEPTH, true);
2097 }
2098 }
2099 }
2100 }
2101 match self {
2102 Transaction::Standard(stx) => {
2103 let offers = stx
2104 .guaranteed_coins
2105 .iter()
2106 .map(|o| (0, (**o).clone()))
2107 .chain(
2108 stx.fallible_coins
2109 .iter()
2110 .map(|pair| (*pair.0, (*pair.1).clone())),
2111 );
2112 for (segment, offer) in offers {
2113 let mut offer_cost = RunningCost::ZERO;
2114 let inputs = offer.inputs.len() + offer.transient.len();
2115 let outputs = offer.outputs.len() + offer.transient.len();
2116 offer_cost += model.map_index(EXPECTED_TIME_FILTER_MAP_DEPTH) * inputs;
2118 offer_cost += model.map_index(EXPECTED_UTXO_DEPTH) * inputs;
2120 offer_cost += (model.map_insert(EXPECTED_UTXO_DEPTH, false)
2122 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false))
2123 * inputs;
2124 offer_cost += model.map_index(EXPECTED_UTXO_DEPTH) * outputs;
2126 offer_cost += model.map_insert(EXPECTED_UTXO_DEPTH, false) * outputs;
2128 offer_cost += (model.cell_read(8) + model.cell_write(8, true)) * outputs;
2130 offer_cost +=
2132 model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false) * outputs;
2133 if segment == 0 {
2134 g_cost += offer_cost;
2135 } else {
2136 f_cost += offer_cost;
2137 g_cost.compute_time += offer_cost.compute_time;
2139 }
2140 }
2141 }
2142 Transaction::ClaimRewards(_) => {
2143 g_cost += model.map_index(EXPECTED_UTXO_DEPTH);
2145 g_cost += model.map_insert(EXPECTED_UTXO_DEPTH, true);
2147 g_cost +=
2149 model.time_filter_map_lookup() + model.cell_read(PERSISTENT_HASH_BYTES as u64);
2150 g_cost += model.time_filter_map_insert(false)
2151 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false);
2152 g_cost += model.map_insert(EXPECTED_UTXO_DEPTH, false);
2154 }
2155 }
2156 (g_cost.into(), (g_cost + f_cost).into())
2157 }
2158
2159 pub fn time_to_dismiss(&self, model: &TransactionCostModel) -> CostDuration {
2160 let mut validation_cost = self.validation_cost(model);
2161 validation_cost.compute_time = validation_cost.compute_time / model.parallelism_factor;
2162 let guaranteed_cost = self.application_cost(model).0;
2163 let cost_to_dismiss = guaranteed_cost + validation_cost;
2164 CostDuration::max(cost_to_dismiss.compute_time, cost_to_dismiss.read_time)
2165 }
2166
2167 pub fn cost(
2168 &self,
2169 params: &LedgerParameters,
2170 enforce_time_to_dismiss: bool,
2171 ) -> Result<SyntheticCost, FeeCalculationError> {
2172 let mut validation_cost = self.validation_cost(¶ms.cost_model);
2173 validation_cost.compute_time =
2174 validation_cost.compute_time / params.cost_model.parallelism_factor;
2175 let (guaranteed_cost, application_cost) = self.application_cost(¶ms.cost_model);
2176 let cost_to_dismiss = guaranteed_cost + validation_cost;
2177 let time_to_dismiss = CostDuration::max(
2178 params.limits.time_to_dismiss_per_byte * self.est_size() as u64,
2179 params.limits.min_time_to_dismiss,
2180 );
2181 if enforce_time_to_dismiss && cost_to_dismiss.max_time() > time_to_dismiss {
2182 return Err(FeeCalculationError::OutsideTimeToDismiss {
2183 time_to_dismiss: cost_to_dismiss.max_time(),
2184 allowed_time_to_dismiss: time_to_dismiss,
2185 size: self.est_size() as u64,
2186 });
2187 }
2188 Ok(validation_cost + application_cost)
2189 }
2190}
2191
2192impl<S: SignatureKind<D>, P: ProofKind<D>, B: Serializable + Tagged + Storable<D>, D: DB>
2193 Transaction<S, P, B, D>
2194{
2195 pub fn transaction_hash(&self) -> TransactionHash {
2196 let mut hasher = Sha256::new();
2197 tagged_serialize(self, &mut hasher).expect("In-memory serialization must succeed");
2198 TransactionHash(HashOutput(hasher.finalize().into()))
2199 }
2200}
2201
2202impl SystemTransaction {
2203 pub fn transaction_hash(&self) -> TransactionHash {
2204 let mut hasher = Sha256::new();
2205 tagged_serialize(self, &mut hasher).expect("In-memory serialization must succeed");
2206 TransactionHash(HashOutput(hasher.finalize().into()))
2207 }
2208
2209 pub fn cost(&self, params: &LedgerParameters) -> SyntheticCost {
2210 use SystemTransaction::*;
2211 let model = ¶ms.cost_model;
2212 match self {
2213 OverwriteParameters(new_params) => params
2214 .cost_model
2215 .cell_write(new_params.serialized_size() as u64, true),
2216 DistributeNight(_, outputs) => {
2217 let mut cost = RunningCost::ZERO;
2218 cost +=
2220 model.time_filter_map_lookup() + model.cell_read(PERSISTENT_HASH_BYTES as u64);
2221 cost += model.time_filter_map_insert(false)
2222 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false);
2223 cost += model.map_insert(EXPECTED_GENERATION_DEPTH, false);
2225 cost += model.cell_write(16, false);
2226 cost += model.cell_read(8) + model.cell_write(8, true);
2228 cost += model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false)
2229 + model.cell_write(FR_BYTES as u64, false);
2230 cost += RunningCost::compute(
2232 model.runtime_cost_model.transient_hash * FRESH_DUST_COMMITMENT_HASHES,
2233 );
2234 cost * outputs.len()
2236 }
2237 PayBlockRewardsToTreasury { .. } => {
2238 let mut cost = RunningCost::ZERO;
2239 cost += model.cell_read(16) + model.cell_write(16, true);
2240 cost += model.map_index(EXPECTED_TOKEN_TYPE_DEPTH);
2241 cost += model.map_insert(EXPECTED_TOKEN_TYPE_DEPTH, true);
2242 cost
2243 }
2244 PayFromTreasuryShielded { outputs, .. } => {
2245 let mut cost = RunningCost::ZERO;
2246 cost += RunningCost::compute(model.runtime_cost_model.transient_hash);
2249 cost += model.merkle_tree_insert_amortized(ZSWAP_TREE_HEIGHT as usize, false);
2251 cost += model.map_insert(EXPECTED_UTXO_DEPTH, false)
2253 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false);
2254 cost = cost * outputs.len();
2255 cost += model.cell_read(16) + model.cell_write(16, true);
2257 cost += model.map_index(EXPECTED_TOKEN_TYPE_DEPTH);
2258 cost += model.map_insert(EXPECTED_TOKEN_TYPE_DEPTH, true);
2259 cost
2260 }
2261 PayFromTreasuryUnshielded { outputs, .. } => {
2262 let mut cost = RunningCost::ZERO;
2263 cost +=
2265 model.time_filter_map_lookup() + model.cell_read(PERSISTENT_HASH_BYTES as u64);
2266 cost += model.time_filter_map_insert(false)
2267 + model.cell_write(PERSISTENT_HASH_BYTES as u64, false);
2268 cost += model.map_insert(EXPECTED_UTXO_DEPTH, false);
2270 cost += model.cell_write(UTXO_SIZE as u64, false);
2271 cost = cost * outputs.len();
2272 cost += model.cell_read(16) + model.cell_write(16, true);
2274 cost += model.map_index(EXPECTED_TOKEN_TYPE_DEPTH);
2275 cost += model.map_insert(EXPECTED_TOKEN_TYPE_DEPTH, true);
2276 cost += model.cell_read(8) + model.cell_write(8, true);
2278 cost += model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false)
2279 + model.cell_write(FR_BYTES as u64, false);
2280 cost += RunningCost::compute(
2282 model.runtime_cost_model.transient_hash * FRESH_DUST_COMMITMENT_HASHES,
2283 );
2284 cost
2285 }
2286 DistributeReserve(..) => {
2287 let cost = model.cell_read(16) + model.cell_write(16, true);
2289 cost * 2u64
2290 }
2291 CNightGeneratesDustUpdate { events } => {
2292 let creates = events
2293 .iter()
2294 .filter(|e| e.action == CNightGeneratesDustActionType::Create)
2295 .count();
2296 let destroys = events.len() - creates;
2297 let mut ccost = RunningCost::ZERO;
2298 ccost += model.map_index(EXPECTED_UTXO_DEPTH) + model.cell_read(FR_BYTES as u64);
2300 ccost += model.cell_read(8) + model.cell_write(8, true);
2302 ccost += model.merkle_tree_insert_amortized(32, false)
2303 + model.cell_write(DUST_GENERATION_INFO_SIZE as u64, false);
2304 ccost += model.map_insert(EXPECTED_UTXO_DEPTH, false) + model.cell_write(8, false);
2306 ccost += model.cell_read(8) + model.cell_write(8, true);
2308 ccost += model.merkle_tree_insert_amortized(EXPECTED_UTXO_DEPTH, false)
2309 + model.cell_write(FR_BYTES as u64, false);
2310 ccost += RunningCost::compute(
2312 model.runtime_cost_model.transient_hash * FRESH_DUST_COMMITMENT_HASHES,
2313 );
2314 let mut dcost = RunningCost::ZERO;
2315 dcost += model.map_index(EXPECTED_GENERATION_DEPTH);
2317 dcost += model.merkle_tree_index(EXPECTED_GENERATION_DEPTH);
2318 dcost += model.merkle_tree_insert_unamortized(32, true);
2319 ccost * creates + dcost * destroys
2320 }
2321 }
2322 .into()
2323 }
2324}
2325
2326#[derive(
2327 Debug,
2328 Default,
2329 Copy,
2330 Clone,
2331 PartialEq,
2332 Eq,
2333 PartialOrd,
2334 Ord,
2335 Hash,
2336 FieldRepr,
2337 FromFieldRepr,
2338 Serializable,
2339 Dummy,
2340 Storable,
2341)]
2342#[storable(base)]
2343#[tag = "transaction-hash"]
2344pub struct TransactionHash(pub HashOutput);
2345tag_enforcement_test!(TransactionHash);
2346
2347impl Serialize for TransactionHash {
2348 fn serialize<S: serde::ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
2349 let mut bytes = Vec::new();
2350 <Self as Serializable>::serialize(self, &mut bytes).map_err(serde::ser::Error::custom)?;
2351 ser.serialize_bytes(&bytes)
2352 }
2353}
2354
2355impl<'de> Deserialize<'de> for TransactionHash {
2356 fn deserialize<D: serde::de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
2357 de.deserialize_bytes(BorshVisitor(PhantomData))
2358 }
2359}
2360
2361#[cfg(feature = "proptest")]
2362impl rand::distributions::Distribution<ContractCall<(), InMemoryDB>>
2363 for rand::distributions::Standard
2364{
2365 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ContractCall<(), InMemoryDB> {
2366 ContractCall {
2367 address: rng.r#gen(),
2368 entry_point: rng.r#gen(),
2369 guaranteed_transcript: None, fallible_transcript: None, communication_commitment: rng.r#gen(),
2372 proof: (),
2373 }
2374 }
2375}
2376
2377#[derive(Storable)]
2378#[derive_where(Clone, PartialEq, Eq; P)]
2379#[storable(db = D)]
2380#[tag = "contract-call[v3]"]
2381pub struct ContractCall<P: ProofKind<D>, D: DB> {
2382 pub address: ContractAddress,
2383 pub entry_point: EntryPointBuf,
2384 pub guaranteed_transcript: Option<Sp<Transcript<D>, D>>,
2386 pub fallible_transcript: Option<Sp<Transcript<D>, D>>,
2387
2388 pub communication_commitment: Fr,
2389 pub proof: P::Proof,
2390}
2391tag_enforcement_test!(ContractCall<(), InMemoryDB>);
2392
2393impl<P: ProofKind<D>, D: DB> Debug for ContractCall<P, D> {
2394 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
2395 formatter
2396 .debug_map()
2397 .entry(&Symbol("contract"), &self.address)
2398 .entry(&Symbol("entry_point"), &self.entry_point)
2399 .entry(
2400 &Symbol("guaranteed_transcript"),
2401 &self.guaranteed_transcript,
2402 )
2403 .entry(&Symbol("fallible_transcript"), &self.fallible_transcript)
2404 .entry(&Symbol("communication_commitment"), &Symbol("<commitment>"))
2405 .entry(&Symbol("proof"), &Symbol("<proof>"))
2406 .finish()
2407 }
2408}
2409
2410impl<P: ProofKind<D>, D: DB> ContractCall<P, D> {
2411 pub fn context(
2412 self,
2413 block: &BlockContext,
2414 intent: &Intent<(), (), Pedersen, D>,
2415 state: ContractState<D>,
2416 com_indices: &Map<Commitment, u64>,
2417 ) -> CallContext<D> {
2418 let caller = intent
2419 .actions
2420 .iter_deref()
2421 .find_map(|action| match action {
2422 ContractAction::Call(caller) if caller.clone().calls(&self.erase_proof()) => {
2423 Some(PublicAddress::Contract(caller.address))
2424 }
2425 _ => None,
2426 })
2427 .or_else(|| {
2428 let mut owners = intent
2429 .guaranteed_unshielded_offer
2430 .iter()
2431 .chain(intent.fallible_unshielded_offer.iter())
2432 .flat_map(|o| o.inputs.iter())
2433 .map(|i| i.owner.clone());
2434 let owner = owners.next()?;
2435 if owners.all(|owner2| owner == owner2) {
2436 Some(PublicAddress::User(UserAddress::from(owner)))
2437 } else {
2438 None
2439 }
2440 });
2441 CallContext {
2442 own_address: self.address,
2443 tblock: block.tblock,
2444 tblock_err: block.tblock_err,
2445 parent_block_hash: block.parent_block_hash,
2446 caller,
2447 balance: state.balance,
2448 com_indices: com_indices.clone(),
2449 last_block_time: block.last_block_time,
2450 }
2451 }
2452
2453 pub fn calls(&self, callee: &ContractCall<P, D>) -> bool {
2454 self.calls_with_seq(callee).is_some()
2455 }
2456
2457 pub fn calls_with_seq(&self, callee: &ContractCall<P, D>) -> Option<(bool, u64)> {
2458 let calls: Vec<((u64, ContractAddress, HashOutput, Fr), bool)> = self
2459 .guaranteed_transcript
2460 .iter()
2461 .map(|x| (x, true))
2462 .chain(self.fallible_transcript.iter().map(|x| (x, false)))
2463 .flat_map(|(t, guaranteed)| {
2464 let ccs: HashSet<ClaimedContractCallsValue, D> =
2465 t.deref().effects.claimed_contract_calls.clone();
2466 ccs.iter()
2467 .map(|x| ((*x).deref().into_inner(), guaranteed))
2468 .collect::<Vec<_>>()
2469 .clone()
2470 })
2471 .collect();
2472 calls
2473 .into_iter()
2474 .find_map(|((seq, addr, ep, cc), guaranteed)| {
2475 (addr == callee.address
2476 && ep == callee.entry_point.ep_hash()
2477 && cc == callee.communication_commitment)
2478 .then_some((guaranteed, seq))
2479 })
2480 }
2481
2482 pub fn erase_proof(&self) -> ContractCall<(), D> {
2483 ContractCall {
2484 address: self.address,
2485 entry_point: self.entry_point.clone(),
2486 guaranteed_transcript: self.guaranteed_transcript.clone(),
2487 fallible_transcript: self.fallible_transcript.clone(),
2488 communication_commitment: self.communication_commitment,
2489 proof: (),
2490 }
2491 }
2492}
2493
2494#[derive(Storable)]
2495#[derive_where(Clone, PartialEq, Eq)]
2496#[storable(db = D)]
2497#[tag = "contract-deploy[v4]"]
2498pub struct ContractDeploy<D: DB> {
2499 pub initial_state: ContractState<D>,
2500 pub nonce: HashOutput,
2501}
2502tag_enforcement_test!(ContractDeploy<InMemoryDB>);
2503
2504impl<D: DB> Debug for ContractDeploy<D> {
2505 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
2506 formatter.write_str("Deploy ")?;
2507 self.initial_state.fmt(formatter)
2508 }
2509}
2510
2511impl<D: DB> ContractDeploy<D> {
2512 pub fn address(&self) -> ContractAddress {
2513 let mut writer = Sha256::new();
2514 tagged_serialize(self, &mut writer).expect("In-memory serialization should succeed");
2515 ContractAddress(HashOutput(writer.finalize().into()))
2516 }
2517}
2518
2519#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2520#[non_exhaustive]
2521pub enum ContractOperationVersion {
2522 V3,
2523}
2524
2525impl Serializable for ContractOperationVersion {
2526 fn serialize(&self, writer: &mut impl Write) -> Result<(), std::io::Error> {
2527 use ContractOperationVersion as V;
2528 match self {
2529 V::V3 => Serializable::serialize(&2u8, writer),
2530 }
2531 }
2532
2533 fn serialized_size(&self) -> usize {
2534 use ContractOperationVersion as V;
2535 match self {
2536 V::V3 => 1,
2537 }
2538 }
2539}
2540
2541impl Tagged for ContractOperationVersion {
2542 fn tag() -> std::borrow::Cow<'static, str> {
2543 "contract-operation-version".into()
2544 }
2545 fn tag_unique_factor() -> String {
2546 "u8".into()
2547 }
2548}
2549tag_enforcement_test!(ContractOperationVersion);
2550
2551impl Deserializable for ContractOperationVersion {
2552 fn deserialize(reader: &mut impl Read, _recursion_depth: u32) -> Result<Self, std::io::Error> {
2553 use ContractOperationVersion as V;
2554 let mut disc = vec![0u8; 1];
2555 reader.read_exact(&mut disc)?;
2556 match disc[0] {
2557 0u8..=1u8 => Err(std::io::Error::new(
2558 std::io::ErrorKind::InvalidData,
2559 format!("Invalid old discriminant {}", disc[0]),
2560 )),
2561 2u8 => Ok(V::V3),
2562 _ => Err(std::io::Error::new(
2563 std::io::ErrorKind::InvalidData,
2564 format!("Unknown discriminant {}", disc[0]),
2565 )),
2566 }
2567 }
2568}
2569
2570impl ContractOperationVersion {
2571 pub(crate) fn has(&self, co: &ContractOperation) -> bool {
2572 use ContractOperationVersion as V;
2573 match self {
2574 V::V3 => co.v2.is_some(),
2575 }
2576 }
2577 pub(crate) fn rm_from(&self, co: &mut ContractOperation) {
2578 use ContractOperationVersion as V;
2579 match self {
2580 V::V3 => co.v2 = None,
2581 }
2582 }
2583}
2584
2585#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2586#[non_exhaustive]
2587pub enum ContractOperationVersionedVerifierKey {
2588 V3(transient_crypto::proofs::VerifierKey),
2589}
2590
2591impl ContractOperationVersionedVerifierKey {
2592 pub(crate) fn as_version(&self) -> ContractOperationVersion {
2593 use ContractOperationVersion as V;
2594 use ContractOperationVersionedVerifierKey as VK;
2595 match self {
2596 VK::V3(_) => V::V3,
2597 }
2598 }
2599
2600 pub(crate) fn insert_into(&self, co: &mut ContractOperation) {
2601 use ContractOperationVersionedVerifierKey as VK;
2602 match self {
2603 VK::V3(vk) => co.v2 = Some(vk.clone()),
2604 }
2605 }
2606}
2607
2608impl Serializable for ContractOperationVersionedVerifierKey {
2609 fn serialize(&self, writer: &mut impl Write) -> Result<(), std::io::Error> {
2610 use ContractOperationVersionedVerifierKey as VK;
2611 match self {
2612 VK::V3(vk) => {
2613 Serializable::serialize(&2u8, writer)?;
2614 Serializable::serialize(vk, writer)
2615 }
2616 }
2617 }
2618
2619 fn serialized_size(&self) -> usize {
2620 use ContractOperationVersionedVerifierKey as VK;
2621 match self {
2622 VK::V3(vk) => 1 + Serializable::serialized_size(vk),
2623 }
2624 }
2625}
2626
2627impl Tagged for ContractOperationVersionedVerifierKey {
2628 fn tag() -> std::borrow::Cow<'static, str> {
2629 "contract-operation-versioned-verifier-key".into()
2630 }
2631 fn tag_unique_factor() -> String {
2632 format!("[[],[],{}]", transient_crypto::proofs::VerifierKey::tag())
2633 }
2634}
2635tag_enforcement_test!(ContractOperationVersionedVerifierKey);
2636
2637impl Deserializable for ContractOperationVersionedVerifierKey {
2638 fn deserialize(reader: &mut impl Read, recursion_depth: u32) -> Result<Self, std::io::Error> {
2639 use ContractOperationVersionedVerifierKey as VK;
2640 let mut disc = vec![0u8; 1];
2641 reader.read_exact(&mut disc)?;
2642 match disc[0] {
2643 0u8..=1u8 => Err(std::io::Error::new(
2644 std::io::ErrorKind::InvalidData,
2645 format!("Invalid old discriminant {}", disc[0]),
2646 )),
2647 2u8 => Ok(VK::V3(Deserializable::deserialize(
2648 reader,
2649 recursion_depth,
2650 )?)),
2651 _ => Err(std::io::Error::new(
2652 std::io::ErrorKind::InvalidData,
2653 format!("Unknown discriminant {}", disc[0]),
2654 )),
2655 }
2656 }
2657}
2658
2659#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serializable, Storable)]
2660#[storable(base)]
2661#[tag = "maintenance-update-single-update[v1]"]
2662pub enum SingleUpdate {
2663 ReplaceAuthority(ContractMaintenanceAuthority),
2666 VerifierKeyRemove(EntryPointBuf, ContractOperationVersion),
2668 VerifierKeyInsert(EntryPointBuf, ContractOperationVersionedVerifierKey),
2672}
2673tag_enforcement_test!(SingleUpdate);
2674
2675#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serializable, Storable, Debug)]
2676#[storable(base)]
2677#[tag = "maintenance-update-signatures-value[v1]"]
2678pub struct SignaturesValue(pub u32, pub Signature);
2680tag_enforcement_test!(SignaturesValue);
2681
2682impl SignaturesValue {
2683 pub fn into_inner(&self) -> (u32, Signature) {
2684 (self.0, self.1.clone())
2685 }
2686}
2687
2688#[derive(Storable)]
2689#[derive_where(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2690#[tag = "contract-maintenance-update[v1]"]
2691#[storable(db = D)]
2692pub struct MaintenanceUpdate<D: DB> {
2693 pub address: ContractAddress,
2694 pub updates: storage::storage::Array<SingleUpdate, D>,
2695 pub counter: u32,
2696 pub signatures: storage::storage::Array<SignaturesValue, D>,
2697}
2698tag_enforcement_test!(MaintenanceUpdate<InMemoryDB>);
2699
2700impl<D: DB> Debug for MaintenanceUpdate<D> {
2701 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2702 f.debug_struct("MaintenanceUpdate")
2703 .field("address", &self.address)
2704 .field("updates", &self.updates)
2705 .field("counter", &self.counter)
2706 .field("signatures", &self.signatures)
2707 .finish()
2708 }
2709}
2710
2711impl<D: DB> MaintenanceUpdate<D> {
2712 pub fn data_to_sign(&self) -> Vec<u8> {
2713 let mut data = Vec::new();
2714 data.extend(b"midnight:contract-update:");
2715 Serializable::serialize(&self.address, &mut data)
2716 .expect("In-memory serialization should succeed");
2717 Serializable::serialize(&self.updates, &mut data)
2718 .expect("In-memory serialization should succeed");
2719 Serializable::serialize(&self.counter, &mut data)
2720 .expect("In-memory serialization should succeed");
2721 data
2722 }
2723}
2724
2725#[derive(Storable)]
2726#[storable(db = D)]
2727#[tag = "contract-action[v6]"]
2728#[derive_where(Clone, PartialEq, Eq; P)]
2729pub enum ContractAction<P: ProofKind<D>, D: DB> {
2730 Call(#[storable(child)] Sp<ContractCall<P, D>, D>),
2731 Deploy(Sp<ContractDeploy<D>, D>),
2732 Maintain(MaintenanceUpdate<D>),
2733}
2734tag_enforcement_test!(ContractAction<(), InMemoryDB>);
2735
2736impl<P: ProofKind<D>, D: DB> From<ContractCall<P, D>> for ContractAction<P, D> {
2737 fn from(call: ContractCall<P, D>) -> Self {
2738 ContractAction::Call(Sp::new(call))
2739 }
2740}
2741
2742impl<P: ProofKind<D>, D: DB> From<ContractDeploy<D>> for ContractAction<P, D> {
2743 fn from(deploy: ContractDeploy<D>) -> Self {
2744 ContractAction::Deploy(Sp::new(deploy))
2745 }
2746}
2747
2748impl<P: ProofKind<D>, D: DB> From<MaintenanceUpdate<D>> for ContractAction<P, D> {
2749 fn from(upd: MaintenanceUpdate<D>) -> Self {
2750 ContractAction::Maintain(upd)
2751 }
2752}
2753
2754impl<P: ProofKind<D>, D: DB> Debug for ContractAction<P, D> {
2755 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
2756 match self {
2757 ContractAction::Call(call) => call.fmt(formatter),
2758 ContractAction::Deploy(deploy) => deploy.fmt(formatter),
2759 ContractAction::Maintain(upd) => upd.fmt(formatter),
2760 }
2761 }
2762}
2763
2764impl<P: ProofKind<D>, D: DB> ContractAction<P, D> {
2765 pub fn erase_proof(&self) -> ContractAction<(), D> {
2766 match self {
2767 ContractAction::Call(call) => ContractAction::Call(Sp::new(call.erase_proof())),
2768 ContractAction::Deploy(deploy) => ContractAction::Deploy(deploy.clone()),
2769 ContractAction::Maintain(upd) => ContractAction::Maintain(upd.clone()),
2770 }
2771 }
2772
2773 pub fn erase_proofs(actions: Vec<&ContractAction<P, D>>) -> Vec<ContractAction<(), D>> {
2774 actions
2775 .into_iter()
2776 .map(ContractAction::erase_proof)
2777 .collect()
2778 }
2779
2780 pub fn challenge_pre_for(calls: &[ContractAction<P, D>]) -> Vec<u8> {
2781 let mut data = Vec::new();
2782 for cd in calls.iter() {
2783 match cd {
2784 ContractAction::Call(call) => {
2785 data.push(0u8);
2786 let _ = Serializable::serialize(&call.address, &mut data);
2787 let _ = Serializable::serialize(&call.entry_point, &mut data);
2788
2789 let _ = Serializable::serialize(&call.guaranteed_transcript, &mut data);
2790
2791 let _ = Serializable::serialize(&call.fallible_transcript, &mut data);
2792 }
2793 ContractAction::Deploy(deploy) => {
2794 data.push(1u8);
2795 let _ = Serializable::serialize(&deploy, &mut data);
2796 }
2797 ContractAction::Maintain(upd) => {
2798 data.push(2u8);
2799 let _ = Serializable::serialize(&upd, &mut data);
2800 }
2801 }
2802 }
2803 data
2804 }
2805}
2806
2807#[derive(Copy, Clone, Debug, PartialEq, Eq, Serializable)]
2808#[tag = "transcation-id[v1]"]
2809pub enum TransactionIdentifier {
2810 Merged(Pedersen),
2811 Unique(HashOutput),
2812}
2813tag_enforcement_test!(TransactionIdentifier);
2814
2815impl Serialize for TransactionIdentifier {
2816 fn serialize<S: serde::ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
2817 let mut bytes = Vec::new();
2818 <Self as Serializable>::serialize(self, &mut bytes).map_err(serde::ser::Error::custom)?;
2819 ser.serialize_bytes(&bytes)
2820 }
2821}
2822
2823impl<'de> Deserialize<'de> for TransactionIdentifier {
2824 fn deserialize<D: serde::de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
2825 de.deserialize_bytes(BorshVisitor(PhantomData))
2826 }
2827}
2828
2829#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serializable, Storable)]
2830#[tag = "unshielded-utxo[v1]"]
2831#[storable(base)]
2832pub struct Utxo {
2833 pub value: u128,
2834 pub owner: UserAddress,
2835 pub type_: UnshieldedTokenType,
2836 pub intent_hash: IntentHash,
2837 pub output_no: u32,
2838}
2839tag_enforcement_test!(Utxo);
2840
2841pub(crate) const UTXO_SIZE: usize = 16 + PERSISTENT_HASH_BYTES * 3 + 4;
2842
2843impl From<UtxoSpend> for Utxo {
2844 fn from(x: UtxoSpend) -> Self {
2845 Utxo {
2846 value: x.value,
2847 owner: UserAddress::from(x.owner),
2848 type_: x.type_,
2849 intent_hash: x.intent_hash,
2850 output_no: x.output_no,
2851 }
2852 }
2853}
2854
2855#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serializable, Storable)]
2856#[storable(base)]
2857#[tag = "unshielded-utxo-output[v1]"]
2858pub struct UtxoOutput {
2859 pub value: u128,
2860 pub owner: UserAddress,
2861 pub type_: UnshieldedTokenType,
2862}
2863tag_enforcement_test!(UtxoOutput);
2864
2865impl From<Utxo> for UtxoOutput {
2866 fn from(value: Utxo) -> Self {
2867 UtxoOutput {
2868 value: value.value,
2869 owner: value.owner,
2870 type_: value.type_,
2871 }
2872 }
2873}
2874
2875#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serializable, Storable)]
2876#[storable(base)]
2877#[tag = "unshielded-utxo-spend"]
2878pub struct UtxoSpend {
2879 pub value: u128,
2880 pub owner: VerifyingKey,
2881 pub type_: UnshieldedTokenType,
2882 pub intent_hash: IntentHash,
2883 pub output_no: u32,
2884}
2885tag_enforcement_test!(UtxoSpend);
2886
2887#[derive(PartialOrd, Ord, Debug, Clone, PartialEq, Eq, Hash, Serializable, Storable)]
2888#[storable(base)]
2889#[tag = "utxo-metadata[v1]"]
2890pub struct UtxoMeta {
2891 pub ctime: Timestamp,
2892}
2893tag_enforcement_test!(UtxoMeta);
2894
2895#[derive(Storable, PartialOrd, Ord)]
2896#[derive_where(Debug, Clone, PartialEq, Eq, Hash)]
2897#[storable(db = D)]
2898#[tag = "unshielded-utxo-state[v2]"]
2899#[must_use]
2900pub struct UtxoState<D: DB> {
2901 pub utxos: HashMap<Utxo, UtxoMeta, D, NightAnn>,
2902}
2903tag_enforcement_test!(UtxoState<InMemoryDB>);
2904
2905impl Annotation<Utxo> for NightAnn {
2906 fn from_value(utxo: &Utxo) -> Self {
2907 NightAnn {
2908 size: 1,
2909 value: if utxo.type_ == NIGHT { utxo.value } else { 0 },
2910 }
2911 }
2912}
2913
2914impl<D: DB> Annotation<(Sp<Utxo, D>, Sp<UtxoMeta, D>)> for NightAnn {
2915 fn from_value(tuple: &(Sp<Utxo, D>, Sp<UtxoMeta, D>)) -> Self {
2916 Self::from_value(tuple.0.deref())
2917 }
2918}
2919
2920impl<D: DB> Annotation<(Sp<Utxo, D>, Sp<(), D>)> for NightAnn {
2921 fn from_value(tuple: &(Sp<Utxo, D>, Sp<(), D>)) -> Self {
2922 Self::from_value(tuple.0.deref())
2923 }
2924}
2925
2926impl Annotation<u128> for NightAnn {
2927 fn from_value(value: &u128) -> Self {
2928 NightAnn {
2929 size: 1,
2930 value: *value,
2931 }
2932 }
2933}
2934
2935impl<D: DB> Annotation<ContractState<D>> for NightAnn {
2936 fn from_value(state: &ContractState<D>) -> Self {
2937 NightAnn {
2938 size: 1,
2939 value: state
2940 .balance
2941 .get(&TokenType::Unshielded(NIGHT))
2942 .map(|sp_value| *sp_value)
2943 .unwrap_or(0),
2944 }
2945 }
2946}
2947
2948impl<D: DB> UtxoState<D> {
2949 pub fn insert(&self, utxo: Utxo, meta: UtxoMeta) -> Self {
2950 UtxoState {
2951 utxos: self.utxos.insert(utxo, meta),
2952 }
2953 }
2954
2955 pub fn remove(&self, utxo: &Utxo) -> Self {
2956 UtxoState {
2957 utxos: self.utxos.remove(utxo),
2958 }
2959 }
2960}
2961
2962impl<D: DB> Default for UtxoState<D> {
2963 fn default() -> Self {
2964 Self {
2965 utxos: HashMap::new(),
2966 }
2967 }
2968}
2969
2970#[derive(Storable)]
2971#[derive_where(Clone, Debug, PartialEq, Eq)]
2972#[storable(db = D)]
2973#[tag = "ledger-state[v13]"]
2974#[must_use]
2975pub struct LedgerState<D: DB> {
2976 pub network_id: String,
2977 #[storable(child)]
2978 pub parameters: Sp<LedgerParameters, D>,
2979 pub locked_pool: u128,
2980 pub bridge_receiving: Map<UserAddress, u128, D>,
2981 pub reserve_pool: u128,
2982 pub block_reward_pool: u128,
2983 pub unclaimed_block_rewards: Map<UserAddress, u128, D, NightAnn>,
2984 pub treasury: Map<TokenType, u128, D>,
2985 #[storable(child)]
2986 pub zswap: Sp<zswap::ledger::State<D>, D>,
2987 pub contract: Map<ContractAddress, ContractState<D>, D, NightAnn>,
2988 #[storable(child)]
2989 pub utxo: Sp<UtxoState<D>, D>,
2990 pub replay_protection: Sp<ReplayProtectionState<D>, D>,
2991 #[storable(child)]
2992 pub dust: Sp<DustState<D>, D>,
2993}
2994tag_enforcement_test!(LedgerState<InMemoryDB>);
2995
2996#[allow(clippy::inconsistent_digit_grouping)]
2999pub const MAX_SUPPLY: u128 = 24_000_000_000 * STARS_PER_NIGHT;
3000pub const STARS_PER_NIGHT: u128 = 1_000_000;
3001pub const SPECKS_PER_DUST: u128 = 1_000_000_000_000_000;
3002
3003impl<D: DB> LedgerState<D> {
3004 pub fn new(network_id: impl Into<String>) -> Self {
3006 Self::with_genesis_settings(network_id, INITIAL_PARAMETERS, 0, MAX_SUPPLY, 0)
3007 .expect("default state should be legal")
3008 }
3009
3010 pub fn with_genesis_settings(
3022 network_id: impl Into<String>,
3023 parameters: LedgerParameters,
3024 locked_pool: u128,
3025 reserve_pool: u128,
3026 treasury: u128,
3027 ) -> Result<Self, InvariantViolation> {
3028 let result = LedgerState {
3029 network_id: network_id.into(),
3030 parameters: Sp::new(parameters),
3031 locked_pool,
3032 bridge_receiving: Map::new(),
3033 reserve_pool,
3034 treasury: [(TokenType::Unshielded(NIGHT), treasury)]
3035 .into_iter()
3036 .collect(),
3037 block_reward_pool: 0,
3038 unclaimed_block_rewards: Map::new(),
3039 zswap: Sp::new(zswap::ledger::State::new()),
3040 contract: Map::new(),
3041 utxo: Sp::new(UtxoState::default()),
3042 replay_protection: Sp::new(ReplayProtectionState::default()),
3043 dust: Sp::new(DustState::default()),
3044 };
3045 result.check_night_balance_invariant()?;
3046 Ok(result)
3047 }
3048
3049 pub fn state_hash(&self) -> ArenaHash<D::Hasher> {
3050 Sp::new(self.clone()).hash()
3051 }
3052
3053 pub fn index(&self, address: ContractAddress) -> Option<ContractState<D>> {
3054 self.contract.get(&address).cloned()
3055 }
3056
3057 pub fn update_index(
3058 &self,
3059 address: ContractAddress,
3060 state: ChargedState<D>,
3061 balance: storage::storage::HashMap<TokenType, u128, D>,
3062 ) -> Self {
3063 let mut new_ledger_state = self.clone();
3064 let contract = new_ledger_state
3065 .contract
3066 .get(&address)
3067 .cloned()
3068 .unwrap_or_default();
3069 let new_contract = ContractState {
3070 data: state,
3071 balance,
3072 ..contract
3073 };
3074 new_ledger_state.contract = new_ledger_state.contract.insert(address, new_contract);
3075 new_ledger_state
3076 }
3077}
3078
3079pub(crate) struct Symbol(pub(crate) &'static str);
3080
3081impl Debug for Symbol {
3082 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
3083 formatter.write_str(self.0)
3084 }
3085}
3086
3087struct BorshVisitor<T>(PhantomData<T>);
3088
3089impl<T: Deserializable> serde::de::Visitor<'_> for BorshVisitor<T> {
3090 type Value = T;
3091 fn expecting(&self, fmt: &mut Formatter) -> fmt::Result {
3092 write!(fmt, "Borsh-serialized {}", std::any::type_name::<T>())
3093 }
3094 fn visit_bytes<E: serde::de::Error>(self, mut v: &[u8]) -> Result<Self::Value, E> {
3095 Deserializable::deserialize(&mut v, 0).map_err(serde::de::Error::custom)
3096 }
3097}
3098
3099pub const FEE_TOKEN: TokenType = TokenType::Dust;
3100
3101#[cfg(test)]
3102mod tests {
3103 use storage::db::InMemoryDB;
3104
3105 use super::*;
3106
3107 #[test]
3108 fn test_max_price_adjustment() {
3109 let adj = f64::from(INITIAL_PARAMETERS.max_price_adjustment());
3110 assert!(1.045 <= adj);
3111 assert!(adj <= 1.047);
3112 }
3113
3114 #[test]
3115 fn test_genesis_state() {
3116 assert_eq!(
3117 LedgerState::<InMemoryDB>::with_genesis_settings(
3118 "local-test",
3119 INITIAL_PARAMETERS,
3120 10_000_000_000_000_000,
3121 10_000_000_000_000_000,
3122 10_000_000_000_000_000,
3123 ),
3124 Err(InvariantViolation::NightBalance(30_000_000_000_000_000))
3125 );
3126 assert_eq!(
3127 LedgerState::<InMemoryDB>::with_genesis_settings(
3128 "local-test",
3129 INITIAL_PARAMETERS,
3130 1_000_000_000_000_000,
3131 1_000_000_000_000_000,
3132 1_000_000_000_000_000,
3133 ),
3134 Err(InvariantViolation::NightBalance(3_000_000_000_000_000))
3135 );
3136 assert!(
3137 LedgerState::<InMemoryDB>::with_genesis_settings(
3138 "local-test",
3139 INITIAL_PARAMETERS,
3140 10_000_000_000_000_000,
3141 10_000_000_000_000_000,
3142 4_000_000_000_000_000,
3143 )
3144 .is_ok()
3145 );
3146 }
3147
3148 #[test]
3149 fn test_state_serialized() {
3150 let state = LedgerState::<InMemoryDB>::new("local-test");
3151 let mut ser = Vec::new();
3152 serialize::tagged_serialize(&state, &mut ser).unwrap();
3153 let _ = serialize::tagged_deserialize::<LedgerState<InMemoryDB>>(&ser[..]).unwrap();
3154 }
3155}