1use alloc::string::String;
11use alloc::vec::Vec;
12use core::cmp::max;
13use core::fmt::Debug;
14
15use bytecheck::CheckBytes;
16use dusk_bytes::{DeserializableSlice, Error as BytesError};
17use poseidon_merkle::Opening;
18use rand::{CryptoRng, RngCore};
19use rkyv::{Archive, Deserialize, Serialize};
20#[cfg(feature = "serde")]
21use serde_with::hex::Hex;
22#[cfg(feature = "serde")]
23use serde_with::{As, DisplayFromStr, Same};
24
25use crate::abi::ContractId;
26use crate::error::TxPreconditionError;
27use crate::signatures::bls::{
28 PublicKey as AccountPublicKey, SecretKey as AccountSecretKey,
29};
30use crate::{BlsScalar, Error};
31
32use self::data::{
33 BlobData, BlobSidecar, ContractCall, ContractDeploy, TransactionData,
34};
35use self::moonlight::Transaction as MoonlightTransaction;
36use self::phoenix::{
37 NOTES_TREE_DEPTH, Note, Prove, PublicKey as PhoenixPublicKey,
38 SecretKey as PhoenixSecretKey, Sender, StealthAddress,
39 Transaction as PhoenixTransaction,
40};
41use self::withdraw::{Withdraw, WithdrawReceiver};
42
43pub mod data;
44pub mod moonlight;
45pub mod phoenix;
46pub mod withdraw;
47
48pub const TRANSFER_CONTRACT: ContractId = crate::reserved(0x1);
50
51pub const PANIC_NONCE_NOT_READY: &str = "Nonce not ready to be used yet";
53
54pub const MOONLIGHT_TOPIC: &str = "moonlight";
56pub const PHOENIX_TOPIC: &str = "phoenix";
58pub const CONTRACT_TO_CONTRACT_TOPIC: &str = "contract_to_contract";
60pub const CONTRACT_TO_ACCOUNT_TOPIC: &str = "contract_to_account";
62pub const WITHDRAW_TOPIC: &str = "withdraw";
64pub const DEPOSIT_TOPIC: &str = "deposit";
66pub const CONVERT_TOPIC: &str = "convert";
68pub const MINT_TOPIC: &str = "mint";
70pub const MINT_CONTRACT_TOPIC: &str = "mint_c";
72
73#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
75#[archive_attr(derive(CheckBytes))]
76#[allow(clippy::large_enum_variant)]
77pub enum Transaction {
78 Phoenix(PhoenixTransaction),
80 Moonlight(MoonlightTransaction),
82}
83
84impl Transaction {
85 #[allow(clippy::too_many_arguments)]
95 pub fn phoenix<R: RngCore + CryptoRng, P: Prove>(
96 rng: &mut R,
97 sender_sk: &PhoenixSecretKey,
98 refund_pk: &PhoenixPublicKey,
99 receiver_pk: &PhoenixPublicKey,
100 inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>)>,
101 root: BlsScalar,
102 transfer_value: u64,
103 obfuscated_transaction: bool,
104 deposit: u64,
105 gas_limit: u64,
106 gas_price: u64,
107 chain_id: u8,
108 data: Option<impl Into<TransactionData>>,
109 prover: &P,
110 ) -> Result<Self, Error> {
111 Ok(Self::Phoenix(PhoenixTransaction::new::<R, P>(
112 rng,
113 sender_sk,
114 refund_pk,
115 receiver_pk,
116 inputs,
117 root,
118 transfer_value,
119 obfuscated_transaction,
120 deposit,
121 gas_limit,
122 gas_price,
123 chain_id,
124 data,
125 prover,
126 )?))
127 }
128
129 #[allow(clippy::too_many_arguments)]
135 pub fn moonlight(
136 sender_sk: &AccountSecretKey,
137 receiver: Option<AccountPublicKey>,
138 value: u64,
139 deposit: u64,
140 gas_limit: u64,
141 gas_price: u64,
142 nonce: u64,
143 chain_id: u8,
144 data: Option<impl Into<TransactionData>>,
145 ) -> Result<Self, Error> {
146 Ok(Self::Moonlight(MoonlightTransaction::new(
147 sender_sk, receiver, value, deposit, gas_limit, gas_price, nonce,
148 chain_id, data,
149 )?))
150 }
151
152 #[must_use]
154 pub fn moonlight_sender(&self) -> Option<&AccountPublicKey> {
155 match self {
156 Self::Phoenix(_) => None,
157 Self::Moonlight(tx) => Some(tx.sender()),
158 }
159 }
160
161 #[must_use]
172 pub fn moonlight_receiver(&self) -> Option<&AccountPublicKey> {
173 match self {
174 Self::Phoenix(_) => None,
175 Self::Moonlight(tx) => tx.receiver(),
176 }
177 }
178
179 #[must_use]
181 pub fn value(&self) -> Option<u64> {
182 match self {
183 Self::Phoenix(_) => None,
184 Self::Moonlight(tx) => Some(tx.value()),
185 }
186 }
187
188 #[must_use]
191 pub fn nullifiers(&self) -> &[BlsScalar] {
192 match self {
193 Self::Phoenix(tx) => tx.nullifiers(),
194 Self::Moonlight(_) => &[],
195 }
196 }
197
198 #[must_use]
200 pub fn root(&self) -> Option<&BlsScalar> {
201 match self {
202 Self::Phoenix(tx) => Some(tx.root()),
203 Self::Moonlight(_) => None,
204 }
205 }
206
207 #[must_use]
209 pub fn outputs(&self) -> &[Note] {
210 match self {
211 Self::Phoenix(tx) => &tx.outputs()[..],
212 Self::Moonlight(_) => &[],
213 }
214 }
215
216 #[must_use]
218 pub fn phoenix_sender(&self) -> Option<&Sender> {
219 match self {
220 Self::Phoenix(tx) => Some(tx.sender()),
221 Self::Moonlight(_) => None,
222 }
223 }
224
225 #[must_use]
227 pub fn deposit(&self) -> u64 {
228 match self {
229 Self::Phoenix(tx) => tx.deposit(),
230 Self::Moonlight(tx) => tx.deposit(),
231 }
232 }
233
234 #[must_use]
236 pub fn gas_limit(&self) -> u64 {
237 match self {
238 Self::Phoenix(tx) => tx.gas_limit(),
239 Self::Moonlight(tx) => tx.gas_limit(),
240 }
241 }
242
243 #[must_use]
245 pub fn gas_price(&self) -> u64 {
246 match self {
247 Self::Phoenix(tx) => tx.gas_price(),
248 Self::Moonlight(tx) => tx.gas_price(),
249 }
250 }
251
252 #[must_use]
254 pub fn refund_address(&self) -> RefundAddress<'_> {
255 match self {
256 Self::Phoenix(tx) => RefundAddress::Phoenix(tx.stealth_address()),
257 Self::Moonlight(tx) => {
258 RefundAddress::Moonlight(tx.refund_address())
259 }
260 }
261 }
262
263 #[must_use]
274 pub fn call(&self) -> Option<&ContractCall> {
275 match self {
276 Self::Phoenix(tx) => tx.call(),
277 Self::Moonlight(tx) => tx.call(),
278 }
279 }
280
281 #[must_use]
283 pub fn deploy(&self) -> Option<&ContractDeploy> {
284 match self {
285 Self::Phoenix(tx) => tx.deploy(),
286 Self::Moonlight(tx) => tx.deploy(),
287 }
288 }
289
290 #[must_use]
292 pub fn memo(&self) -> Option<&[u8]> {
293 match self {
294 Self::Phoenix(tx) => tx.memo(),
295 Self::Moonlight(tx) => tx.memo(),
296 }
297 }
298
299 #[must_use]
301 pub fn blob(&self) -> Option<&Vec<BlobData>> {
302 match self {
303 Self::Phoenix(tx) => tx.blob(),
304 Self::Moonlight(tx) => tx.blob(),
305 }
306 }
307
308 #[must_use]
310 pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
311 match self {
312 Self::Phoenix(tx) => tx.blob_mut(),
313 Self::Moonlight(tx) => tx.blob_mut(),
314 }
315 }
316
317 #[must_use]
327 pub fn strip_blobs(&mut self) -> Option<Vec<([u8; 32], BlobSidecar)>> {
328 let blob = match self {
329 Self::Phoenix(tx) => tx.blob_mut(),
330 Self::Moonlight(tx) => tx.blob_mut(),
331 }?;
332
333 let ret = blob
334 .iter_mut()
335 .filter_map(|b| b.take_sidecar().map(|d| (b.hash, d)))
336 .collect::<Vec<_>>();
337
338 Some(ret)
339 }
340
341 #[must_use]
347 pub fn blob_to_memo(&self) -> Option<Self> {
348 Some(match self {
349 Transaction::Phoenix(tx) => {
350 Transaction::Phoenix(tx.blob_to_memo()?)
351 }
352 Transaction::Moonlight(tx) => {
353 Transaction::Moonlight(tx.blob_to_memo()?)
354 }
355 })
356 }
357
358 #[must_use]
362 pub fn strip_off_bytecode(&self) -> Option<Self> {
363 Some(match self {
364 Transaction::Phoenix(tx) => {
365 Transaction::Phoenix(tx.strip_off_bytecode()?)
366 }
367 Transaction::Moonlight(tx) => {
368 Transaction::Moonlight(tx.strip_off_bytecode()?)
369 }
370 })
371 }
372
373 #[must_use]
375 pub fn to_var_bytes(&self) -> Vec<u8> {
376 let mut bytes = Vec::new();
377
378 match self {
379 Self::Phoenix(tx) => {
380 bytes.push(0);
381 bytes.extend(tx.to_var_bytes());
382 }
383 Self::Moonlight(tx) => {
384 bytes.push(1);
385 bytes.extend(tx.to_var_bytes());
386 }
387 }
388
389 bytes
390 }
391
392 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
397 let mut buf = buf;
398
399 Ok(match u8::from_reader(&mut buf)? {
400 0 => Self::Phoenix(PhoenixTransaction::from_slice(buf)?),
401 1 => Self::Moonlight(MoonlightTransaction::from_slice(buf)?),
402 _ => return Err(BytesError::InvalidData),
403 })
404 }
405
406 #[must_use]
411 pub fn to_hash_input_bytes(&self) -> Vec<u8> {
412 match self {
413 Self::Phoenix(tx) => tx.to_hash_input_bytes(),
414 Self::Moonlight(tx) => tx.to_hash_input_bytes(),
415 }
416 }
417
418 #[must_use]
420 pub fn hash(&self) -> BlsScalar {
421 match self {
422 Self::Phoenix(tx) => tx.hash(),
423 Self::Moonlight(tx) => tx.hash(),
424 }
425 }
426
427 #[must_use]
432 pub fn deploy_charge(
433 &self,
434 gas_per_deploy_byte: u64,
435 min_deploy_points: u64,
436 ) -> u64 {
437 if let Some(deploy) = self.deploy() {
438 let bytecode_len = deploy.bytecode.bytes.len() as u64;
439 max(bytecode_len * gas_per_deploy_byte, min_deploy_points)
440 } else {
441 0
442 }
443 }
444
445 #[must_use]
448 pub fn blob_charge(&self, gas_per_blob: u64) -> Option<u64> {
449 self.blob().map(|blobs| blobs.len() as u64 * gas_per_blob)
450 }
451
452 pub fn phoenix_fee_check(&self) -> Result<(), TxPreconditionError> {
460 if let Transaction::Phoenix(tx) = self {
461 let max_fee = tx
462 .fee()
463 .gas_limit
464 .checked_mul(tx.fee().gas_price)
465 .ok_or(TxPreconditionError::PhoenixFeeOverflow)?;
466
467 if max_fee != tx.max_fee() {
468 return Err(TxPreconditionError::PhoenixFeeTampered);
469 }
470 }
471 Ok(())
472 }
473
474 pub fn phoenix_refund_check(&self) -> Result<(), TxPreconditionError> {
484 if let Transaction::Phoenix(tx) = self {
485 if tx.fee().stealth_address != *tx.outputs()[1].stealth_address() {
486 return Err(TxPreconditionError::PhoenixFeeRefundMismatch);
487 }
488 }
489 Ok(())
490 }
491
492 pub fn deploy_check(
500 &self,
501 gas_per_deploy_byte: u64,
502 min_deploy_gas_price: u64,
503 min_deploy_points: u64,
504 ) -> Result<(), TxPreconditionError> {
505 if self.deploy().is_some() {
506 let deploy_charge =
507 self.deploy_charge(gas_per_deploy_byte, min_deploy_points);
508
509 if self.gas_price() < min_deploy_gas_price {
510 return Err(TxPreconditionError::DeployLowPrice(
511 min_deploy_gas_price,
512 ));
513 }
514 if self.gas_limit() < deploy_charge {
515 return Err(TxPreconditionError::DeployLowLimit(deploy_charge));
516 }
517 }
518
519 Ok(())
520 }
521
522 pub fn blob_check(
536 &self,
537 gas_per_blob: u64,
538 ) -> Result<Option<u64>, TxPreconditionError> {
539 if let Some(blobs) = self.blob() {
540 match blobs.len() {
541 0 => Err(TxPreconditionError::BlobEmpty),
542 n if n > 6 => Err(TxPreconditionError::BlobTooMany(n)),
543 _ => Ok(()),
544 }?;
545 } else {
546 return Ok(None);
547 }
548
549 let min_charge = self.blob_charge(gas_per_blob);
550 if let Some(min_charge) = min_charge {
551 if self.gas_limit() < min_charge {
552 return Err(TxPreconditionError::BlobLowLimit(min_charge));
553 }
554 }
555 Ok(min_charge)
556 }
557}
558
559impl From<PhoenixTransaction> for Transaction {
560 fn from(tx: PhoenixTransaction) -> Self {
561 Self::Phoenix(tx)
562 }
563}
564
565impl From<MoonlightTransaction> for Transaction {
566 fn from(tx: MoonlightTransaction) -> Self {
567 Self::Moonlight(tx)
568 }
569}
570
571pub enum RefundAddress<'a> {
574 Phoenix(&'a StealthAddress),
576 Moonlight(&'a AccountPublicKey),
578}
579
580#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
583#[archive_attr(derive(CheckBytes))]
584#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
585pub struct ContractToContract {
586 pub contract: ContractId,
588 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
590 pub value: u64,
591 pub fn_name: String,
593 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
595 pub data: Vec<u8>,
596}
597
598#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
601#[archive_attr(derive(CheckBytes))]
602#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
603pub struct ReceiveFromContract {
604 pub contract: ContractId,
606 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
608 pub value: u64,
609 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
611 pub data: Vec<u8>,
612}
613
614#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
617#[archive_attr(derive(CheckBytes))]
618#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
619pub struct ContractToAccount {
620 pub account: AccountPublicKey,
622 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
624 pub value: u64,
625}
626
627#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
629#[archive_attr(derive(CheckBytes))]
630#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
631pub struct WithdrawEvent {
632 pub sender: ContractId,
634 pub receiver: WithdrawReceiver,
636 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
638 pub value: u64,
639}
640
641impl From<Withdraw> for WithdrawEvent {
642 fn from(w: Withdraw) -> Self {
643 Self {
644 sender: w.contract,
645 receiver: w.receiver,
646 value: w.value,
647 }
648 }
649}
650
651#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
654#[archive_attr(derive(CheckBytes))]
655#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
656pub struct ConvertEvent {
657 pub sender: Option<AccountPublicKey>,
661 pub receiver: WithdrawReceiver,
663 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
665 pub value: u64,
666}
667
668impl ConvertEvent {
669 #[must_use]
671 pub fn from_withdraw_and_sender(
672 sender: Option<AccountPublicKey>,
673 withdraw: &Withdraw,
674 ) -> Self {
675 Self {
676 sender,
677 receiver: withdraw.receiver,
678 value: withdraw.value,
679 }
680 }
681}
682
683#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
685#[archive_attr(derive(CheckBytes))]
686#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
687pub struct DepositEvent {
688 pub sender: Option<AccountPublicKey>,
692 pub receiver: ContractId,
694 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
696 pub value: u64,
697}
698
699#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
701#[archive_attr(derive(CheckBytes))]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub struct ContractToContractEvent {
704 pub sender: ContractId,
706 pub receiver: ContractId,
708 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
710 pub value: u64,
711}
712
713#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
715#[archive_attr(derive(CheckBytes))]
716#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
717pub struct ContractToAccountEvent {
718 pub sender: ContractId,
720 pub receiver: AccountPublicKey,
722 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
724 pub value: u64,
725}
726
727#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
729#[archive_attr(derive(CheckBytes))]
730#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
731pub struct PhoenixTransactionEvent {
732 pub nullifiers: Vec<BlsScalar>,
734 pub notes: Vec<Note>,
736 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
738 pub memo: Vec<u8>,
739 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
741 pub gas_spent: u64,
742
743 pub refund_note: Option<Note>,
745}
746
747#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
749#[archive_attr(derive(CheckBytes))]
750#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
751pub struct MoonlightTransactionEvent {
752 pub sender: AccountPublicKey,
754 pub receiver: Option<AccountPublicKey>,
756 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
758 pub value: u64,
759 #[cfg_attr(feature = "serde", serde(with = "As::<Hex>"))]
761 pub memo: Vec<u8>,
762 #[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
764 pub gas_spent: u64,
765 #[cfg_attr(
768 feature = "serde",
769 serde(with = "As::<Option<(Same, DisplayFromStr)>>")
770 )]
771 pub refund_info: Option<(AccountPublicKey, u64)>,
772}