use super::{Balance, Input, Output, Payload, PayloadSlice};
use crate::fee::FeeAlgorithm;
use crate::value::{Value, ValueError};
use chain_addr::Address;
use std::error;
use std::fmt;
pub struct InputOutputBuilder {
inputs: Vec<Input>,
outputs: Vec<Output<Address>>,
}
pub struct InputOutput {
pub inputs: Box<[Input]>,
pub outputs: Box<[Output<Address>]>,
}
#[derive(Debug, Clone)]
pub enum Error {
TxInvalidNoInput,
TxInvalidNoOutput,
TxTooManyInputs,
TxTooManyOutputs,
TxNotEnoughTotalInput,
TxTooMuchTotalInput,
MathErr(ValueError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::TxInvalidNoInput => write!(f, "transaction has no inputs"),
Error::TxInvalidNoOutput => write!(f, "transaction has no outputs"),
Error::TxTooManyInputs => write!(f, "transaction has too many inputs"),
Error::TxTooManyOutputs => write!(f, "transaction has too many outputs"),
Error::TxNotEnoughTotalInput => write!(f, "not enough input for making transaction"),
Error::TxTooMuchTotalInput => write!(f, "too muny input value for making transaction"),
Error::MathErr(v) => write!(f, "error in arithmetics {:?}", v),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}
#[derive(Debug, Clone)]
pub enum OutputPolicy {
One(Address),
Forget,
}
impl InputOutputBuilder {
pub fn empty() -> InputOutputBuilder {
InputOutputBuilder {
inputs: Vec::new(),
outputs: Vec::new(),
}
}
pub fn new<'a, IITER, OITER>(inputs: IITER, outputs: OITER) -> Result<InputOutputBuilder, Error>
where
IITER: Iterator<Item = &'a Input>,
OITER: Iterator<Item = &'a Output<Address>>,
{
let inputs: Vec<_> = inputs.cloned().collect();
let outputs: Vec<_> = outputs.cloned().collect();
if inputs.len() > 255 {
return Err(Error::TxTooManyInputs);
}
if outputs.len() > 255 {
return Err(Error::TxTooManyOutputs);
}
Ok(InputOutputBuilder { inputs, outputs })
}
pub fn build(self) -> InputOutput {
InputOutput {
inputs: self.inputs.into(),
outputs: self.outputs.into(),
}
}
pub fn add_input(&mut self, input: &Input) -> Result<(), Error> {
if self.inputs.len() == 255 {
return Err(Error::TxTooManyInputs);
}
self.inputs.push(input.clone());
Ok(())
}
pub fn add_output(&mut self, address: Address, value: Value) -> Result<(), Error> {
if self.outputs.len() == 255 {
return Err(Error::TxTooManyOutputs);
}
self.outputs.push(Output { address, value });
Ok(())
}
pub fn remove_input(&mut self, input: usize) {
if input < self.inputs.len() {
let _ = self.inputs.remove(input);
}
}
pub fn remove_output(&mut self, output: usize) {
if output < self.outputs.len() {
let _ = self.outputs.remove(output);
}
}
pub fn balance(&self, fee: Value) -> Result<Balance, ValueError> {
let inputs = Value::sum(self.inputs.iter().map(|i| i.value()))?;
let outputs = Value::sum(self.outputs.iter().map(|o| o.value))?;
let z = (outputs + fee)?;
match inputs.cmp(&z) {
std::cmp::Ordering::Greater => Ok(Balance::Positive((inputs - z)?)),
std::cmp::Ordering::Less => Ok(Balance::Negative((z - inputs)?)),
std::cmp::Ordering::Equal => Ok(Balance::Zero),
}
}
pub fn estimate_fee<P: Payload, F: FeeAlgorithm>(
&self,
payload: PayloadSlice<'_, P>,
fee_algorithm: &F,
) -> Value {
fee_algorithm.calculate(
payload.into_certificate_slice(),
self.inputs.len() as u8,
self.outputs.len() as u8,
)
}
pub fn get_balance<P: Payload, F: FeeAlgorithm>(
&self,
payload: PayloadSlice<'_, P>,
fee_algorithm: &F,
) -> Result<Balance, ValueError> {
let fee = self.estimate_fee(payload, fee_algorithm);
self.balance(fee)
}
pub fn get_balance_with_placeholders<P: Payload, F: FeeAlgorithm>(
&self,
payload: PayloadSlice<'_, P>,
fee_algorithm: &F,
inputs_placeholders: u8,
outputs_placeholders: u8,
) -> Result<Balance, Error> {
if self.inputs.len() + inputs_placeholders as usize >= 256 {
return Err(Error::TxTooManyInputs);
}
if self.outputs.len() + outputs_placeholders as usize >= 256 {
return Err(Error::TxTooManyOutputs);
}
let nb_inputs = self.inputs.len() as u8 + inputs_placeholders;
let nb_outputs = self.outputs.len() as u8 + outputs_placeholders;
let fee = fee_algorithm.calculate(payload.into_certificate_slice(), nb_inputs, nb_outputs);
self.balance(fee).map_err(Error::MathErr)
}
pub fn get_balance_without_fee(&self) -> Result<Balance, ValueError> {
self.balance(Value::zero())
}
pub fn seal<P: Payload, F: FeeAlgorithm>(
self,
payload: PayloadSlice<P>,
fee_algorithm: &F,
) -> Result<InputOutput, Error> {
match self.get_balance(payload, fee_algorithm) {
Err(err) => Err(Error::MathErr(err)),
Ok(Balance::Negative(_)) => Err(Error::TxNotEnoughTotalInput),
Ok(Balance::Positive(_)) => Err(Error::TxTooMuchTotalInput),
Ok(Balance::Zero) => Ok(self.build()),
}
}
pub fn seal_with_output_policy<P: Payload, F: FeeAlgorithm>(
mut self,
payload: PayloadSlice<P>,
fee_algorithm: &F,
policy: OutputPolicy,
) -> Result<(Balance, Vec<Output<Address>>, InputOutput), Error> {
let fee = self.estimate_fee(payload.clone(), fee_algorithm);
let pos = match self.balance(fee) {
Ok(Balance::Negative(_)) => return Err(Error::TxNotEnoughTotalInput),
Ok(Balance::Positive(v)) => v,
Ok(Balance::Zero) => {
return Ok((Balance::Zero, vec![], self.build()));
}
Err(err) => return Err(Error::MathErr(err)),
};
match policy {
OutputPolicy::Forget => Ok((Balance::Positive(pos), vec![], self.build())),
OutputPolicy::One(address) => {
self.add_output(address.clone(), Value::zero())?;
let fee = self.estimate_fee(payload, fee_algorithm);
match self.balance(fee) {
Ok(Balance::Positive(value)) => {
let _ = self.outputs.pop();
let output = Output { address, value };
self.outputs.push(output.clone());
Ok((Balance::Zero, vec![output], self.build()))
}
_ => {
let _ = self.outputs.pop();
Ok((Balance::Positive(pos), vec![], self.build()))
}
}
}
}
}
}