#[cfg(feature = "serde")]
use serde_with::{hex::Hex, serde_as, DisplayFromStr};
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp::max;
use core::fmt::Debug;
use bytecheck::CheckBytes;
use dusk_bytes::{DeserializableSlice, Error as BytesError};
use poseidon_merkle::Opening;
use rand::{CryptoRng, RngCore};
use rkyv::{Archive, Deserialize, Serialize};
use crate::abi::ContractId;
use crate::error::TxPreconditionError;
use crate::signatures::bls::{
PublicKey as AccountPublicKey, SecretKey as AccountSecretKey,
};
use crate::{BlsScalar, Error};
use self::data::{
BlobData, BlobSidecar, ContractCall, ContractDeploy, TransactionData,
};
use self::moonlight::Transaction as MoonlightTransaction;
use self::phoenix::{
Note, Prove, PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey,
Sender, StealthAddress, Transaction as PhoenixTransaction,
NOTES_TREE_DEPTH,
};
use self::withdraw::{Withdraw, WithdrawReceiver};
pub mod data;
pub mod moonlight;
pub mod phoenix;
pub mod withdraw;
pub const TRANSFER_CONTRACT: ContractId = crate::reserved(0x1);
pub const PANIC_NONCE_NOT_READY: &str = "Nonce not ready to be used yet";
pub const MOONLIGHT_TOPIC: &str = "moonlight";
pub const PHOENIX_TOPIC: &str = "phoenix";
pub const CONTRACT_TO_CONTRACT_TOPIC: &str = "contract_to_contract";
pub const CONTRACT_TO_ACCOUNT_TOPIC: &str = "contract_to_account";
pub const WITHDRAW_TOPIC: &str = "withdraw";
pub const DEPOSIT_TOPIC: &str = "deposit";
pub const CONVERT_TOPIC: &str = "convert";
pub const MINT_TOPIC: &str = "mint";
pub const MINT_CONTRACT_TOPIC: &str = "mint_c";
#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[allow(clippy::large_enum_variant)]
pub enum Transaction {
Phoenix(PhoenixTransaction),
Moonlight(MoonlightTransaction),
}
impl Transaction {
#[allow(clippy::too_many_arguments)]
pub fn phoenix<R: RngCore + CryptoRng, P: Prove>(
rng: &mut R,
sender_sk: &PhoenixSecretKey,
refund_pk: &PhoenixPublicKey,
receiver_pk: &PhoenixPublicKey,
inputs: Vec<(Note, Opening<(), NOTES_TREE_DEPTH>)>,
root: BlsScalar,
transfer_value: u64,
obfuscated_transaction: bool,
deposit: u64,
gas_limit: u64,
gas_price: u64,
chain_id: u8,
data: Option<impl Into<TransactionData>>,
prover: &P,
) -> Result<Self, Error> {
Ok(Self::Phoenix(PhoenixTransaction::new::<R, P>(
rng,
sender_sk,
refund_pk,
receiver_pk,
inputs,
root,
transfer_value,
obfuscated_transaction,
deposit,
gas_limit,
gas_price,
chain_id,
data,
prover,
)?))
}
#[allow(clippy::too_many_arguments)]
pub fn moonlight(
sender_sk: &AccountSecretKey,
receiver: Option<AccountPublicKey>,
value: u64,
deposit: u64,
gas_limit: u64,
gas_price: u64,
nonce: u64,
chain_id: u8,
data: Option<impl Into<TransactionData>>,
) -> Result<Self, Error> {
Ok(Self::Moonlight(MoonlightTransaction::new(
sender_sk, receiver, value, deposit, gas_limit, gas_price, nonce,
chain_id, data,
)?))
}
#[must_use]
pub fn moonlight_sender(&self) -> Option<&AccountPublicKey> {
match self {
Self::Phoenix(_) => None,
Self::Moonlight(tx) => Some(tx.sender()),
}
}
#[must_use]
pub fn moonlight_receiver(&self) -> Option<&AccountPublicKey> {
match self {
Self::Phoenix(_) => None,
Self::Moonlight(tx) => tx.receiver(),
}
}
#[must_use]
pub fn value(&self) -> Option<u64> {
match self {
Self::Phoenix(_) => None,
Self::Moonlight(tx) => Some(tx.value()),
}
}
#[must_use]
pub fn nullifiers(&self) -> &[BlsScalar] {
match self {
Self::Phoenix(tx) => tx.nullifiers(),
Self::Moonlight(_) => &[],
}
}
#[must_use]
pub fn root(&self) -> Option<&BlsScalar> {
match self {
Self::Phoenix(tx) => Some(tx.root()),
Self::Moonlight(_) => None,
}
}
#[must_use]
pub fn outputs(&self) -> &[Note] {
match self {
Self::Phoenix(tx) => &tx.outputs()[..],
Self::Moonlight(_) => &[],
}
}
#[must_use]
pub fn phoenix_sender(&self) -> Option<&Sender> {
match self {
Self::Phoenix(tx) => Some(tx.sender()),
Self::Moonlight(_) => None,
}
}
#[must_use]
pub fn deposit(&self) -> u64 {
match self {
Self::Phoenix(tx) => tx.deposit(),
Self::Moonlight(tx) => tx.deposit(),
}
}
#[must_use]
pub fn gas_limit(&self) -> u64 {
match self {
Self::Phoenix(tx) => tx.gas_limit(),
Self::Moonlight(tx) => tx.gas_limit(),
}
}
#[must_use]
pub fn gas_price(&self) -> u64 {
match self {
Self::Phoenix(tx) => tx.gas_price(),
Self::Moonlight(tx) => tx.gas_price(),
}
}
#[must_use]
pub fn refund_address(&self) -> RefundAddress {
match self {
Self::Phoenix(tx) => RefundAddress::Phoenix(tx.stealth_address()),
Self::Moonlight(tx) => {
RefundAddress::Moonlight(tx.refund_address())
}
}
}
#[must_use]
pub fn call(&self) -> Option<&ContractCall> {
match self {
Self::Phoenix(tx) => tx.call(),
Self::Moonlight(tx) => tx.call(),
}
}
#[must_use]
pub fn deploy(&self) -> Option<&ContractDeploy> {
match self {
Self::Phoenix(tx) => tx.deploy(),
Self::Moonlight(tx) => tx.deploy(),
}
}
#[must_use]
pub fn memo(&self) -> Option<&[u8]> {
match self {
Self::Phoenix(tx) => tx.memo(),
Self::Moonlight(tx) => tx.memo(),
}
}
#[must_use]
pub fn blob(&self) -> Option<&Vec<BlobData>> {
match self {
Self::Phoenix(tx) => tx.blob(),
Self::Moonlight(tx) => tx.blob(),
}
}
#[must_use]
pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
match self {
Self::Phoenix(tx) => tx.blob_mut(),
Self::Moonlight(tx) => tx.blob_mut(),
}
}
#[must_use]
pub fn strip_blobs(&mut self) -> Option<Vec<([u8; 32], BlobSidecar)>> {
let blob = match self {
Self::Phoenix(tx) => tx.blob_mut(),
Self::Moonlight(tx) => tx.blob_mut(),
}?;
let ret = blob
.iter_mut()
.filter_map(|b| b.take_sidecar().map(|d| (b.hash, d)))
.collect::<Vec<_>>();
Some(ret)
}
#[must_use]
pub fn blob_to_memo(&self) -> Option<Self> {
Some(match self {
Transaction::Phoenix(tx) => {
Transaction::Phoenix(tx.blob_to_memo()?)
}
Transaction::Moonlight(tx) => {
Transaction::Moonlight(tx.blob_to_memo()?)
}
})
}
#[must_use]
pub fn strip_off_bytecode(&self) -> Option<Self> {
Some(match self {
Transaction::Phoenix(tx) => {
Transaction::Phoenix(tx.strip_off_bytecode()?)
}
Transaction::Moonlight(tx) => {
Transaction::Moonlight(tx.strip_off_bytecode()?)
}
})
}
#[must_use]
pub fn to_var_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
match self {
Self::Phoenix(tx) => {
bytes.push(0);
bytes.extend(tx.to_var_bytes());
}
Self::Moonlight(tx) => {
bytes.push(1);
bytes.extend(tx.to_var_bytes());
}
}
bytes
}
pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
let mut buf = buf;
Ok(match u8::from_reader(&mut buf)? {
0 => Self::Phoenix(PhoenixTransaction::from_slice(buf)?),
1 => Self::Moonlight(MoonlightTransaction::from_slice(buf)?),
_ => return Err(BytesError::InvalidData),
})
}
#[must_use]
pub fn to_hash_input_bytes(&self) -> Vec<u8> {
match self {
Self::Phoenix(tx) => tx.to_hash_input_bytes(),
Self::Moonlight(tx) => tx.to_hash_input_bytes(),
}
}
#[must_use]
pub fn hash(&self) -> BlsScalar {
match self {
Self::Phoenix(tx) => tx.hash(),
Self::Moonlight(tx) => tx.hash(),
}
}
#[must_use]
pub fn deploy_charge(
&self,
gas_per_deploy_byte: u64,
min_deploy_points: u64,
) -> u64 {
if let Some(deploy) = self.deploy() {
let bytecode_len = deploy.bytecode.bytes.len() as u64;
max(bytecode_len * gas_per_deploy_byte, min_deploy_points)
} else {
0
}
}
#[must_use]
pub fn blob_charge(&self, gas_per_blob: u64) -> Option<u64> {
self.blob().map(|blobs| blobs.len() as u64 * gas_per_blob)
}
pub fn deploy_check(
&self,
gas_per_deploy_byte: u64,
min_deploy_gas_price: u64,
min_deploy_points: u64,
) -> Result<(), TxPreconditionError> {
if self.deploy().is_some() {
let deploy_charge =
self.deploy_charge(gas_per_deploy_byte, min_deploy_points);
if self.gas_price() < min_deploy_gas_price {
return Err(TxPreconditionError::DeployLowPrice(
min_deploy_gas_price,
));
}
if self.gas_limit() < deploy_charge {
return Err(TxPreconditionError::DeployLowLimit(deploy_charge));
}
}
Ok(())
}
pub fn blob_check(
&self,
gas_per_blob: u64,
) -> Result<Option<u64>, TxPreconditionError> {
if let Some(blobs) = self.blob() {
match blobs.len() {
0 => Err(TxPreconditionError::BlobEmpty),
n if n > 6 => Err(TxPreconditionError::BlobTooMany(n)),
_ => Ok(()),
}?;
} else {
return Ok(None);
}
let min_charge = self.blob_charge(gas_per_blob);
if let Some(min_charge) = min_charge {
if self.gas_limit() < min_charge {
return Err(TxPreconditionError::BlobLowLimit(min_charge));
}
}
Ok(min_charge)
}
}
impl From<PhoenixTransaction> for Transaction {
fn from(tx: PhoenixTransaction) -> Self {
Self::Phoenix(tx)
}
}
impl From<MoonlightTransaction> for Transaction {
fn from(tx: MoonlightTransaction) -> Self {
Self::Moonlight(tx)
}
}
pub enum RefundAddress<'a> {
Phoenix(&'a StealthAddress),
Moonlight(&'a AccountPublicKey),
}
#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ContractToContract {
pub contract: ContractId,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
pub fn_name: String,
#[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ReceiveFromContract {
pub contract: ContractId,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
#[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ContractToAccount {
pub account: AccountPublicKey,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WithdrawEvent {
pub sender: ContractId,
pub receiver: WithdrawReceiver,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
impl From<Withdraw> for WithdrawEvent {
fn from(w: Withdraw) -> Self {
Self {
sender: w.contract,
receiver: w.receiver,
value: w.value,
}
}
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ConvertEvent {
pub sender: Option<AccountPublicKey>,
pub receiver: WithdrawReceiver,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
impl ConvertEvent {
#[must_use]
pub fn from_withdraw_and_sender(
sender: Option<AccountPublicKey>,
withdraw: &Withdraw,
) -> Self {
Self {
sender,
receiver: withdraw.receiver,
value: withdraw.value,
}
}
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DepositEvent {
pub sender: Option<AccountPublicKey>,
pub receiver: ContractId,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ContractToContractEvent {
pub sender: ContractId,
pub receiver: ContractId,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ContractToAccountEvent {
pub sender: ContractId,
pub receiver: AccountPublicKey,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PhoenixTransactionEvent {
pub nullifiers: Vec<BlsScalar>,
pub notes: Vec<Note>,
#[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
pub memo: Vec<u8>,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub gas_spent: u64,
pub refund_note: Option<Note>,
}
#[derive(Debug, Clone, Archive, PartialEq, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
#[cfg_attr(feature = "serde", cfg_eval, serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MoonlightTransactionEvent {
pub sender: AccountPublicKey,
pub receiver: Option<AccountPublicKey>,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub value: u64,
#[cfg_attr(feature = "serde", serde_as(as = "Hex"))]
pub memo: Vec<u8>,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub gas_spent: u64,
#[cfg_attr(
feature = "serde",
serde_as(as = "Option<(serde_with::Same, DisplayFromStr)>")
)]
pub refund_info: Option<(AccountPublicKey, u64)>,
}