#[cfg(feature = "serde")]
use serde_with::{serde_as, DisplayFromStr};
use alloc::vec::Vec;
use bytecheck::CheckBytes;
use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
use rkyv::{Archive, Deserialize, Serialize};
use crate::signatures::bls::{
PublicKey as AccountPublicKey, SecretKey as AccountSecretKey,
Signature as AccountSignature,
};
use crate::transfer::data::{
BlobData, ContractBytecode, ContractCall, ContractDeploy, TransactionData,
MAX_MEMO_SIZE,
};
use crate::{BlsScalar, Error};
#[derive(Debug, Clone, PartialEq, Eq, Archive, 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 AccountData {
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub nonce: u64,
#[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))]
pub balance: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
pub struct Transaction {
payload: Payload,
signature: AccountSignature,
}
impl Transaction {
#[allow(clippy::too_many_arguments)]
pub fn new(
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> {
let refund_address = AccountPublicKey::from(sender_sk);
Self::new_with_refund(
sender_sk,
&refund_address,
receiver,
value,
deposit,
gas_limit,
gas_price,
nonce,
chain_id,
data,
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_refund(
sender_sk: &AccountSecretKey,
refund_pk: &AccountPublicKey,
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> {
let data = data.map(Into::into);
let sender = AccountPublicKey::from(sender_sk);
let receiver = receiver.unwrap_or(sender);
let fee = Fee {
gas_limit,
gas_price,
refund_address: *refund_pk,
};
let payload = Payload {
chain_id,
sender,
receiver,
value,
deposit,
fee,
nonce,
data,
};
Self::sign_payload(sender_sk, payload)
}
pub fn sign_payload(
sender_sk: &AccountSecretKey,
payload: Payload,
) -> Result<Self, Error> {
if let Some(TransactionData::Memo(memo)) = payload.data.as_ref() {
if memo.len() > MAX_MEMO_SIZE {
return Err(Error::MemoTooLarge(memo.len()));
}
}
let digest = payload.signature_message();
let signature = sender_sk.sign(&digest);
Ok(Self { payload, signature })
}
#[must_use]
pub fn signature(&self) -> &AccountSignature {
&self.signature
}
#[must_use]
pub fn sender(&self) -> &AccountPublicKey {
&self.payload.sender
}
#[must_use]
pub fn refund_address(&self) -> &AccountPublicKey {
&self.payload.fee.refund_address
}
#[must_use]
pub fn receiver(&self) -> Option<&AccountPublicKey> {
if self.payload.sender == self.payload.receiver {
None
} else {
Some(&self.payload.receiver)
}
}
#[must_use]
pub fn value(&self) -> u64 {
self.payload.value
}
#[must_use]
pub fn deposit(&self) -> u64 {
self.payload.deposit
}
#[must_use]
pub fn gas_limit(&self) -> u64 {
self.payload.fee.gas_limit
}
#[must_use]
pub fn gas_price(&self) -> u64 {
self.payload.fee.gas_price
}
#[must_use]
pub fn nonce(&self) -> u64 {
self.payload.nonce
}
#[must_use]
pub fn chain_id(&self) -> u8 {
self.payload.chain_id
}
#[must_use]
pub fn call(&self) -> Option<&ContractCall> {
#[allow(clippy::match_wildcard_for_single_variants)]
match self.data()? {
TransactionData::Call(ref c) => Some(c),
_ => None,
}
}
#[must_use]
pub fn deploy(&self) -> Option<&ContractDeploy> {
#[allow(clippy::match_wildcard_for_single_variants)]
match self.data()? {
TransactionData::Deploy(ref d) => Some(d),
_ => None,
}
}
#[must_use]
pub fn memo(&self) -> Option<&[u8]> {
match self.data()? {
TransactionData::Memo(memo) => Some(memo),
_ => None,
}
}
#[must_use]
pub fn blob(&self) -> Option<&Vec<BlobData>> {
#[allow(clippy::match_wildcard_for_single_variants)]
match self.data()? {
TransactionData::Blob(ref d) => Some(d),
_ => None,
}
}
#[must_use]
pub fn blob_mut(&mut self) -> Option<&mut Vec<BlobData>> {
#[allow(clippy::match_wildcard_for_single_variants)]
match self.data_mut()? {
TransactionData::Blob(d) => Some(d),
_ => None,
}
}
#[must_use]
pub fn data(&self) -> Option<&TransactionData> {
self.payload.data.as_ref()
}
#[must_use]
pub(crate) fn data_mut(&mut self) -> Option<&mut TransactionData> {
self.payload.data.as_mut()
}
#[must_use]
pub fn strip_off_bytecode(&self) -> Option<Self> {
let deploy = self.deploy()?;
let stripped_deploy = TransactionData::Deploy(ContractDeploy {
owner: deploy.owner.clone(),
init_args: deploy.init_args.clone(),
bytecode: ContractBytecode {
hash: deploy.bytecode.hash,
bytes: Vec::new(),
},
nonce: deploy.nonce,
});
let mut stripped_transaction = self.clone();
stripped_transaction.payload.data = Some(stripped_deploy);
Some(stripped_transaction)
}
#[must_use]
pub fn blob_to_memo(&self) -> Option<Self> {
let data = self.data()?;
if let TransactionData::Blob(_) = data {
let hash = data.signature_message();
let memo = TransactionData::Memo(hash);
let mut converted_tx = self.clone();
converted_tx.payload.data = Some(memo);
Some(converted_tx)
} else {
None
}
}
#[must_use]
pub fn to_var_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let payload_bytes = self.payload.to_var_bytes();
bytes.extend((payload_bytes.len() as u64).to_bytes());
bytes.extend(payload_bytes);
bytes.extend(self.signature.to_bytes());
bytes
}
pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
let mut buf = buf;
let payload_len = usize::try_from(u64::from_reader(&mut buf)?)
.map_err(|_| BytesError::InvalidData)?;
if buf.len() < payload_len {
return Err(BytesError::InvalidData);
}
let (payload_buf, new_buf) = buf.split_at(payload_len);
let payload = Payload::from_slice(payload_buf)?;
buf = new_buf;
let signature = AccountSignature::from_bytes(
buf.try_into().map_err(|_| BytesError::InvalidData)?,
)
.map_err(|_| BytesError::InvalidData)?;
Ok(Self { payload, signature })
}
#[must_use]
pub fn to_hash_input_bytes(&self) -> Vec<u8> {
let mut bytes = self.payload.signature_message();
bytes.extend(self.signature.to_bytes());
bytes
}
#[must_use]
pub fn signature_message(&self) -> Vec<u8> {
self.payload.signature_message()
}
#[must_use]
pub fn hash(&self) -> BlsScalar {
BlsScalar::hash_to_scalar(&self.to_hash_input_bytes())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
pub struct Payload {
pub chain_id: u8,
pub sender: AccountPublicKey,
pub receiver: AccountPublicKey,
pub value: u64,
pub deposit: u64,
pub fee: Fee,
pub nonce: u64,
pub data: Option<TransactionData>,
}
impl Payload {
#[must_use]
pub fn to_var_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::from([self.chain_id]);
bytes.extend(self.sender.to_bytes());
if self.sender == self.receiver {
bytes.push(0);
} else {
bytes.push(1);
bytes.extend(self.receiver.to_bytes());
}
bytes.extend(self.value.to_bytes());
bytes.extend(self.deposit.to_bytes());
bytes.extend(self.fee.gas_limit.to_bytes());
bytes.extend(self.fee.gas_price.to_bytes());
if self.sender == self.fee.refund_address {
bytes.push(0);
} else {
bytes.push(1);
bytes.extend(self.fee.refund_address.to_bytes());
}
bytes.extend(self.nonce.to_bytes());
bytes.extend(TransactionData::option_to_var_bytes(self.data.as_ref()));
bytes
}
pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
let mut buf = buf;
let chain_id = u8::from_reader(&mut buf)?;
let sender = AccountPublicKey::from_reader(&mut buf)?;
let receiver = match u8::from_reader(&mut buf)? {
0 => sender,
1 => AccountPublicKey::from_reader(&mut buf)?,
_ => {
return Err(BytesError::InvalidData);
}
};
let value = u64::from_reader(&mut buf)?;
let deposit = u64::from_reader(&mut buf)?;
let gas_limit = u64::from_reader(&mut buf)?;
let gas_price = u64::from_reader(&mut buf)?;
let refund_address = match u8::from_reader(&mut buf)? {
0 => sender,
1 => AccountPublicKey::from_reader(&mut buf)?,
_ => {
return Err(BytesError::InvalidData);
}
};
let fee = Fee {
gas_limit,
gas_price,
refund_address,
};
let nonce = u64::from_reader(&mut buf)?;
let data = TransactionData::from_slice(buf)?;
Ok(Self {
chain_id,
sender,
receiver,
value,
deposit,
fee,
nonce,
data,
})
}
#[must_use]
pub fn signature_message(&self) -> Vec<u8> {
let mut bytes = Vec::from([self.chain_id]);
bytes.extend(self.sender.to_bytes());
if self.receiver != self.sender {
bytes.extend(self.receiver.to_bytes());
}
bytes.extend(self.value.to_bytes());
bytes.extend(self.deposit.to_bytes());
bytes.extend(self.fee.gas_limit.to_bytes());
bytes.extend(self.fee.gas_price.to_bytes());
if self.fee.refund_address != self.sender {
bytes.extend(self.fee.refund_address.to_bytes());
}
bytes.extend(self.nonce.to_bytes());
if let Some(data) = &self.data {
bytes.extend(data.signature_message());
}
bytes
}
}
#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
pub struct Fee {
pub gas_limit: u64,
pub gas_price: u64,
pub refund_address: AccountPublicKey,
}