use super::input::UnsignedInput;
use super::DataInput;
use super::Transaction;
use super::TxIoVec;
use super::{distinct_token_ids, TransactionError};
use bounded_vec::BoundedVec;
use ergo_chain_types::blake2b256_hash;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
use ergotree_ir::chain::token::TokenId;
use ergotree_ir::chain::tx_id::TxId;
use ergotree_ir::serialization::SigmaSerializationError;
use indexmap::IndexSet;
use std::convert::TryInto;
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "json",
serde(
try_from = "crate::chain::json::transaction::UnsignedTransactionJson",
into = "crate::chain::json::transaction::UnsignedTransactionJson"
)
)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct UnsignedTransaction {
tx_id: TxId,
pub inputs: TxIoVec<UnsignedInput>,
pub data_inputs: Option<TxIoVec<DataInput>>,
pub output_candidates: TxIoVec<ErgoBoxCandidate>,
pub(crate) outputs: TxIoVec<ErgoBox>,
}
impl UnsignedTransaction {
pub fn new_from_vec(
inputs: Vec<UnsignedInput>,
data_inputs: Vec<DataInput>,
output_candidates: Vec<ErgoBoxCandidate>,
) -> Result<UnsignedTransaction, TransactionError> {
Ok(UnsignedTransaction::new(
inputs
.try_into()
.map_err(TransactionError::InvalidInputsCount)?,
BoundedVec::opt_empty_vec(data_inputs)
.map_err(TransactionError::InvalidDataInputsCount)?,
output_candidates
.try_into()
.map_err(TransactionError::InvalidOutputCandidatesCount)?,
)?)
}
pub fn new(
inputs: TxIoVec<UnsignedInput>,
data_inputs: Option<TxIoVec<DataInput>>,
output_candidates: TxIoVec<ErgoBoxCandidate>,
) -> Result<UnsignedTransaction, SigmaSerializationError> {
#[allow(clippy::unwrap_used)] let outputs = output_candidates
.iter()
.enumerate()
.map(|(idx, b)| ErgoBox::from_box_candidate(b, TxId::zero(), idx as u16).unwrap())
.collect::<Vec<_>>()
.try_into()
.unwrap();
let tx_to_sign = UnsignedTransaction {
tx_id: TxId::zero(),
inputs,
data_inputs,
output_candidates,
outputs,
};
let tx_id = tx_to_sign.calc_tx_id()?;
let outputs = tx_to_sign
.output_candidates
.clone()
.enumerated()
.try_mapped_ref(|(idx, bc)| ErgoBox::from_box_candidate(bc, tx_id, *idx as u16))?;
Ok(UnsignedTransaction {
tx_id,
outputs,
..tx_to_sign
})
}
fn calc_tx_id(&self) -> Result<TxId, SigmaSerializationError> {
let bytes = self.bytes_to_sign()?;
Ok(TxId(blake2b256_hash(&bytes)))
}
fn to_tx_without_proofs(&self) -> Result<Transaction, SigmaSerializationError> {
let empty_proofs_input = self.inputs.mapped_ref(|ui| ui.input_to_sign());
Transaction::new(
empty_proofs_input,
self.data_inputs.clone(),
self.output_candidates.clone(),
)
}
pub fn id(&self) -> TxId {
self.tx_id
}
pub fn bytes_to_sign(&self) -> Result<Vec<u8>, SigmaSerializationError> {
let tx = self.to_tx_without_proofs()?;
tx.bytes_to_sign()
}
pub fn distinct_token_ids(&self) -> IndexSet<TokenId> {
distinct_token_ids(self.output_candidates.clone())
}
}
#[cfg(feature = "arbitrary")]
#[allow(clippy::unwrap_used)]
pub mod arbitrary {
use super::*;
use proptest::prelude::*;
use proptest::{arbitrary::Arbitrary, collection::vec};
impl Arbitrary for UnsignedTransaction {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
vec(any::<UnsignedInput>(), 1..10),
vec(any::<DataInput>(), 0..10),
vec(any::<ErgoBoxCandidate>(), 1..10),
)
.prop_map(|(inputs, data_inputs, outputs)| {
Self::new_from_vec(inputs, data_inputs, outputs).unwrap()
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
pub mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn test_unsigned_tx_bytes_to_sign(v in any::<UnsignedTransaction>()) {
prop_assert!(!v.bytes_to_sign().unwrap().is_empty());
}
}
}