use super::input::Input;
use super::payload::{NoExtra, Payload};
use super::transaction::{
Transaction, TransactionAuthData, TransactionBindingAuthData, TransactionStruct,
};
use super::transfer::Output;
use super::witness::Witness;
use crate::date::BlockDate;
use chain_addr::Address;
use std::marker::PhantomData;
pub struct TxBuilderState<T> {
data: Vec<u8>,
tstruct: TransactionStruct,
phantom: PhantomData<T>,
}
impl<T> Clone for TxBuilderState<T> {
fn clone(&self) -> Self {
TxBuilderState {
data: self.data.clone(),
tstruct: self.tstruct.clone(),
phantom: self.phantom,
}
}
}
pub enum SetPayload {}
pub struct SetTtl<P>(PhantomData<P>);
pub struct SetIOs<P>(PhantomData<P>);
pub struct SetWitnesses<P>(PhantomData<P>);
pub struct SetAuthData<P: Payload>(PhantomData<P>);
pub type TxBuilder = TxBuilderState<SetPayload>;
pub const FRAGMENT_OVERHEAD: usize = 0;
impl Default for TxBuilder {
fn default() -> Self {
Self::new()
}
}
impl TxBuilder {
pub fn new() -> Self {
let data = vec![0u8; FRAGMENT_OVERHEAD];
TxBuilderState {
data,
tstruct: TransactionStruct {
sz: 0,
nb_inputs: 0,
nb_outputs: 0,
valid_until: BlockDate::first(),
inputs: 0,
outputs: 0,
witnesses: 0,
payload_auth: 0,
},
phantom: PhantomData,
}
}
}
impl<State> TxBuilderState<State> {
fn current_pos(&self) -> usize {
self.data.len() - FRAGMENT_OVERHEAD
}
}
impl TxBuilderState<SetPayload> {
pub fn set_payload<P: Payload>(mut self, payload: &P) -> TxBuilderState<SetTtl<P>> {
if P::HAS_DATA {
self.data.extend_from_slice(payload.payload_data().as_ref());
}
TxBuilderState {
data: self.data,
tstruct: self.tstruct,
phantom: PhantomData,
}
}
pub fn set_nopayload(self) -> TxBuilderState<SetTtl<NoExtra>> {
self.set_payload(&NoExtra)
}
}
impl<P> TxBuilderState<SetTtl<P>> {
pub fn set_expiry_date(mut self, valid_until: BlockDate) -> TxBuilderState<SetIOs<P>> {
fn write_date(data: &mut Vec<u8>, date: BlockDate) {
data.extend_from_slice(&date.epoch.to_be_bytes());
data.extend_from_slice(&date.slot_id.to_be_bytes());
}
write_date(&mut self.data, valid_until);
self.tstruct.valid_until = valid_until;
TxBuilderState {
data: self.data,
tstruct: self.tstruct,
phantom: PhantomData,
}
}
}
impl<P> TxBuilderState<SetIOs<P>> {
pub fn set_ios(
mut self,
inputs: &[Input],
outputs: &[Output<Address>],
) -> TxBuilderState<SetWitnesses<P>> {
assert!(inputs.len() < 256);
assert!(outputs.len() < 256);
let nb_inputs = inputs.len() as u8;
let nb_outputs = outputs.len() as u8;
self.data.push(nb_inputs);
self.data.push(nb_outputs);
self.tstruct.nb_inputs = nb_inputs;
self.tstruct.nb_outputs = nb_outputs;
self.tstruct.inputs = self.current_pos();
for i in inputs {
self.data.extend_from_slice(&i.bytes());
}
self.tstruct.outputs = self.current_pos();
for o in outputs {
self.data.extend_from_slice(&o.address.to_bytes());
self.data.extend_from_slice(&o.value.bytes());
}
TxBuilderState {
data: self.data,
tstruct: self.tstruct,
phantom: PhantomData,
}
}
}
impl<P> TxBuilderState<SetWitnesses<P>> {
pub fn get_auth_data_for_witness(&self) -> TransactionAuthData<'_> {
TransactionAuthData(&self.data[FRAGMENT_OVERHEAD..])
}
pub fn set_witnesses(self, witnesses: &[Witness]) -> TxBuilderState<SetAuthData<P>>
where
P: Payload,
{
assert_eq!(witnesses.len(), self.tstruct.nb_inputs as usize);
self.set_witnesses_unchecked(witnesses)
}
pub fn set_witnesses_unchecked(
mut self,
witnesses: &[Witness],
) -> TxBuilderState<SetAuthData<P>>
where
P: Payload,
{
self.tstruct.witnesses = self.current_pos();
for w in witnesses {
self.data.extend_from_slice(&w.to_bytes())
}
TxBuilderState {
data: self.data,
tstruct: self.tstruct,
phantom: PhantomData,
}
}
}
impl<P: Payload> TxBuilderState<SetAuthData<P>> {
pub fn get_auth_data(&self) -> TransactionBindingAuthData<'_> {
TransactionBindingAuthData(&self.data[FRAGMENT_OVERHEAD..])
}
pub fn set_payload_auth(mut self, auth_data: &P::Auth) -> Transaction<P> {
self.tstruct.payload_auth = self.current_pos();
if P::HAS_DATA && P::HAS_AUTH {
self.data
.extend_from_slice(<P as Payload>::payload_auth_data(auth_data).as_ref());
}
self.tstruct.sz = self.current_pos();
Transaction {
data: self.data.into(),
tstruct: self.tstruct,
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
testing::{
builders::witness_builder::make_witness,
data::{AddressData, AddressDataValue},
TestGen,
},
value::Value,
};
use chain_addr::Discrimination;
#[test]
#[should_panic]
pub fn test_internal_apply_transaction_witnesses_count_are_grater_than_inputs() {
let faucets = vec![
AddressDataValue::account(Discrimination::Test, Value(2)),
AddressDataValue::account(Discrimination::Test, Value(1)),
];
let reciever = AddressDataValue::utxo(Discrimination::Test, Value(2));
let block0_hash = TestGen::hash();
let tx_builder = TxBuilder::new()
.set_payload(&NoExtra)
.set_expiry_date(BlockDate::first().next_epoch())
.set_ios(&[faucets[0].make_input(None)], &[reciever.make_output()]);
let witness1 = make_witness(
&block0_hash,
&faucets[0].clone().into(),
&tx_builder.get_auth_data_for_witness().hash(),
);
let witness2 = make_witness(
&block0_hash,
&faucets[1].clone().into(),
&tx_builder.get_auth_data_for_witness().hash(),
);
tx_builder.set_witnesses(&[witness1, witness2]);
}
#[test]
#[should_panic]
pub fn test_internal_apply_transaction_witnesses_count_are_smaller_than_inputs() {
let faucets = vec![
AddressDataValue::account(Discrimination::Test, Value(1)),
AddressDataValue::account(Discrimination::Test, Value(1)),
];
let reciever = AddressData::utxo(Discrimination::Test);
let block0_hash = TestGen::hash();
let tx_builder = TxBuilder::new()
.set_payload(&NoExtra)
.set_expiry_date(BlockDate::first().next_epoch())
.set_ios(
&[faucets[0].make_input(None), faucets[1].make_input(None)],
&[reciever.make_output(Value(2))],
);
let witness = make_witness(
&block0_hash,
&faucets[0].clone().into(),
&tx_builder.get_auth_data_for_witness().hash(),
);
tx_builder.set_witnesses(&[witness]);
}
}