bee_block/payload/transaction/essence/
regular.rs1use alloc::vec::Vec;
5
6use hashbrown::HashSet;
7use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable};
8
9use crate::{
10 input::{Input, INPUT_COUNT_RANGE},
11 output::{InputsCommitment, NativeTokens, Output, OUTPUT_COUNT_RANGE},
12 payload::{OptionalPayload, Payload},
13 protocol::ProtocolParameters,
14 Error,
15};
16
17#[derive(Debug, Clone)]
19#[must_use]
20pub struct RegularTransactionEssenceBuilder {
21 inputs: Vec<Input>,
22 inputs_commitment: InputsCommitment,
23 outputs: Vec<Output>,
24 payload: Option<Payload>,
25}
26
27impl RegularTransactionEssenceBuilder {
28 pub fn new(inputs_commitment: InputsCommitment) -> Self {
30 Self {
31 inputs: Vec::new(),
32 inputs_commitment,
33 outputs: Vec::new(),
34 payload: None,
35 }
36 }
37
38 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
40 self.inputs = inputs;
41 self
42 }
43
44 pub fn add_input(mut self, input: Input) -> Self {
46 self.inputs.push(input);
47 self
48 }
49
50 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
52 self.outputs = outputs;
53 self
54 }
55
56 pub fn add_output(mut self, output: Output) -> Self {
58 self.outputs.push(output);
59 self
60 }
61
62 pub fn with_payload(mut self, payload: Payload) -> Self {
64 self.payload = Some(payload);
65 self
66 }
67
68 pub fn finish(self, protocol_parameters: &ProtocolParameters) -> Result<RegularTransactionEssence, Error> {
70 let inputs: BoxedSlicePrefix<Input, InputCount> = self
71 .inputs
72 .into_boxed_slice()
73 .try_into()
74 .map_err(Error::InvalidInputCount)?;
75
76 verify_inputs::<true>(&inputs)?;
77
78 let outputs: BoxedSlicePrefix<Output, OutputCount> = self
79 .outputs
80 .into_boxed_slice()
81 .try_into()
82 .map_err(Error::InvalidOutputCount)?;
83
84 verify_outputs::<true>(&outputs, protocol_parameters)?;
85
86 let payload = OptionalPayload::from(self.payload);
87
88 verify_payload::<true>(&payload)?;
89
90 Ok(RegularTransactionEssence {
91 network_id: protocol_parameters.network_id(),
92 inputs,
93 inputs_commitment: self.inputs_commitment,
94 outputs,
95 payload,
96 })
97 }
98}
99
100pub(crate) type InputCount = BoundedU16<{ *INPUT_COUNT_RANGE.start() }, { *INPUT_COUNT_RANGE.end() }>;
101pub(crate) type OutputCount = BoundedU16<{ *OUTPUT_COUNT_RANGE.start() }, { *OUTPUT_COUNT_RANGE.end() }>;
102
103#[derive(Clone, Debug, Eq, PartialEq, Packable)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106#[packable(unpack_error = Error)]
107#[packable(unpack_visitor = ProtocolParameters)]
108pub struct RegularTransactionEssence {
109 #[packable(verify_with = verify_network_id)]
111 network_id: u64,
112 #[packable(verify_with = verify_inputs_packable)]
113 #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidInputCount(p.into())))]
114 inputs: BoxedSlicePrefix<Input, InputCount>,
115 inputs_commitment: InputsCommitment,
117 #[packable(verify_with = verify_outputs)]
118 #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidOutputCount(p.into())))]
119 outputs: BoxedSlicePrefix<Output, OutputCount>,
120 #[packable(verify_with = verify_payload_packable)]
121 payload: OptionalPayload,
122}
123
124impl RegularTransactionEssence {
125 pub const KIND: u8 = 1;
127
128 pub fn builder(inputs_commitment: InputsCommitment) -> RegularTransactionEssenceBuilder {
130 RegularTransactionEssenceBuilder::new(inputs_commitment)
131 }
132
133 pub fn network_id(&self) -> u64 {
135 self.network_id
136 }
137
138 pub fn inputs(&self) -> &[Input] {
140 &self.inputs
141 }
142
143 pub fn inputs_commitment(&self) -> &InputsCommitment {
145 &self.inputs_commitment
146 }
147
148 pub fn outputs(&self) -> &[Output] {
150 &self.outputs
151 }
152
153 pub fn payload(&self) -> Option<&Payload> {
155 self.payload.as_ref()
156 }
157}
158
159fn verify_network_id<const VERIFY: bool>(network_id: &u64, visitor: &ProtocolParameters) -> Result<(), Error> {
160 if VERIFY {
161 let expected = visitor.network_id();
162
163 if *network_id != expected {
164 return Err(Error::NetworkIdMismatch {
165 expected,
166 actual: *network_id,
167 });
168 }
169 }
170
171 Ok(())
172}
173
174fn verify_inputs<const VERIFY: bool>(inputs: &[Input]) -> Result<(), Error> {
175 if VERIFY {
176 let mut seen_utxos = HashSet::new();
177
178 for input in inputs.iter() {
179 match input {
180 Input::Utxo(utxo) => {
181 if !seen_utxos.insert(utxo) {
182 return Err(Error::DuplicateUtxo(utxo.clone()));
183 }
184 }
185 _ => return Err(Error::InvalidInputKind(input.kind())),
186 }
187 }
188 }
189
190 Ok(())
191}
192
193fn verify_inputs_packable<const VERIFY: bool>(inputs: &[Input], _visitor: &ProtocolParameters) -> Result<(), Error> {
194 verify_inputs::<VERIFY>(inputs)
195}
196
197fn verify_outputs<const VERIFY: bool>(outputs: &[Output], visitor: &ProtocolParameters) -> Result<(), Error> {
198 if VERIFY {
199 let mut amount_sum: u64 = 0;
200 let mut native_tokens_count: u8 = 0;
201
202 for output in outputs.iter() {
203 let (amount, native_tokens) = match output {
204 Output::Basic(output) => (output.amount(), output.native_tokens()),
205 Output::Alias(output) => (output.amount(), output.native_tokens()),
206 Output::Foundry(output) => (output.amount(), output.native_tokens()),
207 Output::Nft(output) => (output.amount(), output.native_tokens()),
208 _ => return Err(Error::InvalidOutputKind(output.kind())),
209 };
210
211 amount_sum = amount_sum
212 .checked_add(amount)
213 .ok_or(Error::InvalidTransactionAmountSum(amount_sum as u128 + amount as u128))?;
214
215 if amount_sum > visitor.token_supply() {
217 return Err(Error::InvalidTransactionAmountSum(amount_sum as u128));
218 }
219
220 native_tokens_count = native_tokens_count.checked_add(native_tokens.len() as u8).ok_or(
221 Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16 + native_tokens.len() as u16),
222 )?;
223
224 if native_tokens_count > NativeTokens::COUNT_MAX {
225 return Err(Error::InvalidTransactionNativeTokensCount(native_tokens_count as u16));
226 }
227
228 output.verify_storage_deposit(visitor.rent_structure().clone(), visitor.token_supply())?;
229 }
230 }
231
232 Ok(())
233}
234
235fn verify_payload<const VERIFY: bool>(payload: &OptionalPayload) -> Result<(), Error> {
236 if VERIFY {
237 match &payload.0 {
238 Some(Payload::TaggedData(_)) | None => Ok(()),
239 Some(payload) => Err(Error::InvalidPayloadKind(payload.kind())),
240 }
241 } else {
242 Ok(())
243 }
244}
245
246fn verify_payload_packable<const VERIFY: bool>(
247 payload: &OptionalPayload,
248 _visitor: &ProtocolParameters,
249) -> Result<(), Error> {
250 verify_payload::<VERIFY>(payload)
251}
252
253#[cfg(feature = "dto")]
254#[allow(missing_docs)]
255pub mod dto {
256 use core::str::FromStr;
257
258 use serde::{Deserialize, Serialize};
259
260 use super::*;
261 use crate::{error::dto::DtoError, input::dto::InputDto, output::dto::OutputDto, payload::dto::PayloadDto};
262
263 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
265 pub struct RegularTransactionEssenceDto {
266 #[serde(rename = "type")]
267 pub kind: u8,
268 #[serde(rename = "networkId")]
269 pub network_id: String,
270 pub inputs: Vec<InputDto>,
271 #[serde(rename = "inputsCommitment")]
272 pub inputs_commitment: String,
273 pub outputs: Vec<OutputDto>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub payload: Option<PayloadDto>,
276 }
277
278 impl From<&RegularTransactionEssence> for RegularTransactionEssenceDto {
279 fn from(value: &RegularTransactionEssence) -> Self {
280 RegularTransactionEssenceDto {
281 kind: RegularTransactionEssence::KIND,
282 network_id: value.network_id().to_string(),
283 inputs: value.inputs().iter().map(Into::into).collect::<Vec<_>>(),
284 inputs_commitment: value.inputs_commitment().to_string(),
285 outputs: value.outputs().iter().map(Into::into).collect::<Vec<_>>(),
286 payload: match value.payload() {
287 Some(Payload::TaggedData(i)) => Some(PayloadDto::TaggedData(Box::new(i.as_ref().into()))),
288 Some(_) => unimplemented!(),
289 None => None,
290 },
291 }
292 }
293 }
294
295 impl RegularTransactionEssence {
296 pub fn try_from_dto(
297 value: &RegularTransactionEssenceDto,
298 protocol_parameters: &ProtocolParameters,
299 ) -> Result<RegularTransactionEssence, DtoError> {
300 let inputs = value
301 .inputs
302 .iter()
303 .map(TryInto::try_into)
304 .collect::<Result<Vec<Input>, DtoError>>()?;
305 let outputs = value
306 .outputs
307 .iter()
308 .map(|o| Output::try_from_dto(o, protocol_parameters.token_supply()))
309 .collect::<Result<Vec<Output>, DtoError>>()?;
310
311 let mut builder = RegularTransactionEssence::builder(InputsCommitment::from_str(&value.inputs_commitment)?)
312 .with_inputs(inputs)
313 .with_outputs(outputs);
314 builder = if let Some(p) = &value.payload {
315 if let PayloadDto::TaggedData(i) = p {
316 builder.with_payload(Payload::TaggedData(Box::new((i.as_ref()).try_into()?)))
317 } else {
318 return Err(DtoError::InvalidField("payload"));
319 }
320 } else {
321 builder
322 };
323
324 builder.finish(protocol_parameters).map_err(Into::into)
325 }
326 }
327}