use alloc::{boxed::Box, vec::Vec};
use bee_common::packable::{Packable, Read, Write};
use iterator_sorted::is_sorted;
use crate::{
constants::{INPUT_OUTPUT_COUNT_RANGE, IOTA_SUPPLY},
input::Input,
output::Output,
payload::{option_payload_pack, option_payload_packed_len, option_payload_unpack, Payload},
Error,
};
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegularEssence {
inputs: Box<[Input]>,
outputs: Box<[Output]>,
payload: Option<Payload>,
}
impl RegularEssence {
pub const KIND: u8 = 0;
pub fn builder() -> RegularEssenceBuilder {
RegularEssenceBuilder::new()
}
pub fn inputs(&self) -> &[Input] {
&self.inputs
}
pub fn outputs(&self) -> &[Output] {
&self.outputs
}
pub fn payload(&self) -> &Option<Payload> {
&self.payload
}
}
impl Packable for RegularEssence {
type Error = Error;
fn packed_len(&self) -> usize {
0u16.packed_len()
+ self.inputs.iter().map(Packable::packed_len).sum::<usize>()
+ 0u16.packed_len()
+ self.outputs.iter().map(Packable::packed_len).sum::<usize>()
+ option_payload_packed_len(self.payload.as_ref())
}
fn pack<W: Write>(&self, writer: &mut W) -> Result<(), Self::Error> {
(self.inputs.len() as u16).pack(writer)?;
for input in self.inputs.iter() {
input.pack(writer)?;
}
(self.outputs.len() as u16).pack(writer)?;
for output in self.outputs.iter() {
output.pack(writer)?;
}
option_payload_pack(writer, self.payload.as_ref())?;
Ok(())
}
fn unpack_inner<R: Read + ?Sized, const CHECK: bool>(reader: &mut R) -> Result<Self, Self::Error> {
let inputs_len = u16::unpack_inner::<R, CHECK>(reader)? as usize;
if CHECK && !INPUT_OUTPUT_COUNT_RANGE.contains(&inputs_len) {
return Err(Error::InvalidInputOutputCount(inputs_len));
}
let mut inputs = Vec::with_capacity(inputs_len);
for _ in 0..inputs_len {
inputs.push(Input::unpack_inner::<R, CHECK>(reader)?);
}
let outputs_len = u16::unpack_inner::<R, CHECK>(reader)? as usize;
if CHECK && !INPUT_OUTPUT_COUNT_RANGE.contains(&outputs_len) {
return Err(Error::InvalidInputOutputCount(outputs_len));
}
let mut outputs = Vec::with_capacity(outputs_len);
for _ in 0..outputs_len {
outputs.push(Output::unpack_inner::<R, CHECK>(reader)?);
}
let mut builder = Self::builder().with_inputs(inputs).with_outputs(outputs);
if let (_, Some(payload)) = option_payload_unpack::<R, CHECK>(reader)? {
builder = builder.with_payload(payload);
}
builder.finish()
}
}
#[derive(Debug, Default)]
#[must_use]
pub struct RegularEssenceBuilder {
inputs: Vec<Input>,
outputs: Vec<Output>,
payload: Option<Payload>,
}
impl RegularEssenceBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
self.inputs = inputs;
self
}
pub fn add_input(mut self, input: Input) -> Self {
self.inputs.push(input);
self
}
pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
self.outputs = outputs;
self
}
pub fn add_output(mut self, output: Output) -> Self {
self.outputs.push(output);
self
}
pub fn with_payload(mut self, payload: Payload) -> Self {
self.payload = Some(payload);
self
}
pub fn finish(self) -> Result<RegularEssence, Error> {
if !INPUT_OUTPUT_COUNT_RANGE.contains(&self.inputs.len()) {
return Err(Error::InvalidInputOutputCount(self.inputs.len()));
}
if !INPUT_OUTPUT_COUNT_RANGE.contains(&self.outputs.len()) {
return Err(Error::InvalidInputOutputCount(self.outputs.len()));
}
if !matches!(self.payload, None | Some(Payload::Indexation(_))) {
return Err(Error::InvalidPayloadKind(self.payload.unwrap().kind()));
}
for input in self.inputs.iter() {
match input {
Input::Utxo(u) => {
if self.inputs.iter().filter(|i| *i == input).count() > 1 {
return Err(Error::DuplicateUtxo(u.clone()));
}
}
_ => return Err(Error::InvalidInputKind(input.kind())),
}
}
if !is_sorted(self.inputs.iter().map(Packable::pack_new)) {
return Err(Error::TransactionInputsNotSorted);
}
let mut total: u64 = 0;
for output in self.outputs.iter() {
match output {
Output::SignatureLockedSingle(single) => {
if self
.outputs
.iter()
.filter(|o| matches!(o, Output::SignatureLockedSingle(s) if s.address() == single.address()))
.count()
> 1
{
return Err(Error::DuplicateAddress(*single.address()));
}
total = total
.checked_add(single.amount())
.ok_or_else(|| Error::InvalidAccumulatedOutput((total + single.amount()) as u128))?;
}
Output::SignatureLockedDustAllowance(dust_allowance) => {
if self
.outputs
.iter()
.filter(
|o| matches!(o, Output::SignatureLockedDustAllowance(s) if s.address() == dust_allowance.address()),
)
.count()
> 1
{
return Err(Error::DuplicateAddress(*dust_allowance.address()));
}
total = total.checked_add(dust_allowance.amount()).ok_or_else(|| {
Error::InvalidAccumulatedOutput(total as u128 + dust_allowance.amount() as u128)
})?;
}
_ => return Err(Error::InvalidOutputKind(output.kind())),
}
if total > IOTA_SUPPLY {
return Err(Error::InvalidAccumulatedOutput(total as u128));
}
}
if !is_sorted(self.outputs.iter().map(Packable::pack_new)) {
return Err(Error::TransactionOutputsNotSorted);
}
Ok(RegularEssence {
inputs: self.inputs.into_boxed_slice(),
outputs: self.outputs.into_boxed_slice(),
payload: self.payload,
})
}
}