1use crate::{
2 Chargeable,
3 ConsensusParameters,
4 Input,
5 Output,
6 Transaction,
7 Witness,
8 field::{
9 Expiration,
10 Maturity,
11 Owner,
12 },
13 input::{
14 coin::{
15 CoinPredicate,
16 CoinSigned,
17 },
18 message::{
19 MessageCoinPredicate,
20 MessageCoinSigned,
21 MessageDataPredicate,
22 MessageDataSigned,
23 },
24 },
25 output,
26 policies::PolicyType,
27 transaction::{
28 Executable,
29 consensus_parameters::{
30 PredicateParameters,
31 TxParameters,
32 },
33 field,
34 },
35};
36use core::hash::Hash;
37use fuel_types::{
38 Address,
39 BlockHeight,
40 Bytes32,
41 ChainId,
42 canonical,
43 canonical::Serialize,
44};
45use hashbrown::HashMap;
46use itertools::Itertools;
47
48mod error;
49
50#[cfg(test)]
51mod tests;
52
53pub use error::ValidityError;
54
55impl Input {
56 #[cfg(any(feature = "typescript", test))]
57 pub fn check(
58 &self,
59 index: usize,
60 txhash: &Bytes32,
61 outputs: &[Output],
62 witnesses: &[Witness],
63 predicate_params: &PredicateParameters,
64 recovery_cache: &mut Option<HashMap<u16, Address>>,
65 ) -> Result<(), ValidityError> {
66 self.check_without_signature(index, outputs, witnesses, predicate_params)?;
67 self.check_signature(index, txhash, witnesses, recovery_cache)?;
68
69 Ok(())
70 }
71
72 pub fn check_signature(
73 &self,
74 index: usize,
75 txhash: &Bytes32,
76 witnesses: &[Witness],
77 recovery_cache: &mut Option<HashMap<u16, Address>>,
78 ) -> Result<(), ValidityError> {
79 match self {
80 Self::CoinSigned(CoinSigned {
81 witness_index,
82 owner,
83 ..
84 })
85 | Self::MessageCoinSigned(MessageCoinSigned {
86 witness_index,
87 recipient: owner,
88 ..
89 })
90 | Self::MessageDataSigned(MessageDataSigned {
91 witness_index,
92 recipient: owner,
93 ..
94 }) => {
95 let recover_address = || -> Result<Address, ValidityError> {
97 let witness = witnesses
98 .get(*witness_index as usize)
99 .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
100
101 witness.recover_witness(txhash, index)
102 };
103
104 let recovered_address = if let Some(cache) = recovery_cache {
107 if let Some(recovered_address) = cache.get(witness_index) {
108 *recovered_address
109 } else {
110 let recovered_address = recover_address()?;
113 cache.insert(*witness_index, recovered_address);
114 recovered_address
115 }
116 } else {
117 recover_address()?
118 };
119
120 if owner != &recovered_address {
121 return Err(ValidityError::InputInvalidSignature { index });
122 }
123
124 Ok(())
125 }
126
127 Self::CoinPredicate(CoinPredicate {
128 owner, predicate, ..
129 })
130 | Self::MessageCoinPredicate(MessageCoinPredicate {
131 recipient: owner,
132 predicate,
133 ..
134 })
135 | Self::MessageDataPredicate(MessageDataPredicate {
136 recipient: owner,
137 predicate,
138 ..
139 }) if !Input::is_predicate_owner_valid(owner, &**predicate) => {
140 Err(ValidityError::InputPredicateOwner { index })
141 }
142
143 _ => Ok(()),
144 }
145 }
146
147 pub fn check_without_signature(
148 &self,
149 index: usize,
150 outputs: &[Output],
151 witnesses: &[Witness],
152 predicate_params: &PredicateParameters,
153 ) -> Result<(), ValidityError> {
154 match self {
155 Self::CoinPredicate(CoinPredicate { predicate, .. })
156 | Self::MessageCoinPredicate(MessageCoinPredicate { predicate, .. })
157 | Self::MessageDataPredicate(MessageDataPredicate { predicate, .. })
158 if predicate.is_empty() =>
159 {
160 Err(ValidityError::InputPredicateEmpty { index })
161 }
162
163 Self::CoinPredicate(CoinPredicate { predicate, .. })
164 | Self::MessageCoinPredicate(MessageCoinPredicate { predicate, .. })
165 | Self::MessageDataPredicate(MessageDataPredicate { predicate, .. })
166 if predicate.len() as u64 > predicate_params.max_predicate_length() =>
167 {
168 Err(ValidityError::InputPredicateLength { index })
169 }
170
171 Self::CoinPredicate(CoinPredicate { predicate_data, .. })
172 | Self::MessageCoinPredicate(MessageCoinPredicate {
173 predicate_data, ..
174 })
175 | Self::MessageDataPredicate(MessageDataPredicate {
176 predicate_data, ..
177 }) if predicate_data.len() as u64
178 > predicate_params.max_predicate_data_length() =>
179 {
180 Err(ValidityError::InputPredicateDataLength { index })
181 }
182
183 Self::CoinSigned(CoinSigned { witness_index, .. })
184 | Self::MessageCoinSigned(MessageCoinSigned { witness_index, .. })
185 | Self::MessageDataSigned(MessageDataSigned { witness_index, .. })
186 if *witness_index as usize >= witnesses.len() =>
187 {
188 Err(ValidityError::InputWitnessIndexBounds { index })
189 }
190
191 Self::Contract { .. }
194 if 1 != outputs
195 .iter()
196 .filter_map(|output| match output {
197 Output::Contract(output::contract::Contract {
198 input_index,
199 ..
200 }) if *input_index as usize == index => Some(()),
201 _ => None,
202 })
203 .count() =>
204 {
205 Err(ValidityError::InputContractAssociatedOutputContract { index })
206 }
207
208 Self::MessageDataSigned(MessageDataSigned { data, .. })
209 | Self::MessageDataPredicate(MessageDataPredicate { data, .. })
210 if data.is_empty()
211 || data.len() as u64 > predicate_params.max_message_data_length() =>
212 {
213 Err(ValidityError::InputMessageDataLength { index })
214 }
215
216 _ => Ok(()),
219 }
220 }
221}
222
223impl Output {
224 pub fn check(&self, index: usize, inputs: &[Input]) -> Result<(), ValidityError> {
231 match self {
232 Self::Contract(output::contract::Contract { input_index, .. }) => {
233 match inputs.get(*input_index as usize) {
234 Some(Input::Contract { .. }) => Ok(()),
235 _ => Err(ValidityError::OutputContractInputIndex { index }),
236 }
237 }
238
239 _ => Ok(()),
240 }
241 }
242}
243
244pub trait FormatValidityChecks {
248 fn check(
251 &self,
252 block_height: BlockHeight,
253 consensus_params: &ConsensusParameters,
254 ) -> Result<(), ValidityError> {
255 self.check_without_signatures(block_height, consensus_params)?;
256 self.check_signatures(&consensus_params.chain_id())?;
257
258 Ok(())
259 }
260
261 fn check_signatures(&self, chain_id: &ChainId) -> Result<(), ValidityError>;
264
265 fn check_without_signatures(
268 &self,
269 block_height: BlockHeight,
270 consensus_params: &ConsensusParameters,
271 ) -> Result<(), ValidityError>;
272}
273
274impl FormatValidityChecks for Transaction {
275 fn check_signatures(&self, chain_id: &ChainId) -> Result<(), ValidityError> {
276 match self {
277 Self::Script(tx) => tx.check_signatures(chain_id),
278 Self::Create(tx) => tx.check_signatures(chain_id),
279 Self::Mint(tx) => tx.check_signatures(chain_id),
280 Self::Upgrade(tx) => tx.check_signatures(chain_id),
281 Self::Upload(tx) => tx.check_signatures(chain_id),
282 Self::Blob(tx) => tx.check_signatures(chain_id),
283 }
284 }
285
286 fn check_without_signatures(
287 &self,
288 block_height: BlockHeight,
289 consensus_params: &ConsensusParameters,
290 ) -> Result<(), ValidityError> {
291 match self {
292 Self::Script(tx) => {
293 tx.check_without_signatures(block_height, consensus_params)
294 }
295 Self::Create(tx) => {
296 tx.check_without_signatures(block_height, consensus_params)
297 }
298 Self::Mint(tx) => tx.check_without_signatures(block_height, consensus_params),
299 Self::Upgrade(tx) => {
300 tx.check_without_signatures(block_height, consensus_params)
301 }
302 Self::Upload(tx) => {
303 tx.check_without_signatures(block_height, consensus_params)
304 }
305 Self::Blob(tx) => tx.check_without_signatures(block_height, consensus_params),
306 }
307 }
308}
309
310pub(crate) fn check_size<T>(tx: &T, tx_params: &TxParameters) -> Result<(), ValidityError>
315where
316 T: canonical::Serialize,
317{
318 if tx.size() as u64 > tx_params.max_size() {
319 Err(ValidityError::TransactionSizeLimitExceeded)?;
320 }
321
322 Ok(())
323}
324
325pub(crate) fn check_owner<T>(tx: &T) -> Result<(), ValidityError>
326where
327 T: Chargeable,
328{
329 if let Some(owner) = tx.owner() {
330 let owner = u32::try_from(owner)
331 .map_err(|_| ValidityError::TransactionOwnerIndexOutOfBounds)?;
332 if owner as usize >= tx.inputs().len() {
333 Err(ValidityError::TransactionOwnerIndexOutOfBounds)?
334 }
335 if tx
336 .inputs()
337 .get(owner as usize)
338 .and_then(|input| input.input_owner())
339 .is_none()
340 {
341 Err(ValidityError::TransactionOwnerInputHasNoOwner {
342 index: owner as usize,
343 })?
344 }
345 }
346 Ok(())
347}
348
349pub(crate) fn check_common_part<T>(
350 tx: &T,
351 block_height: BlockHeight,
352 consensus_params: &ConsensusParameters,
353) -> Result<(), ValidityError>
354where
355 T: canonical::Serialize + Chargeable + field::Outputs,
356{
357 let tx_params = consensus_params.tx_params();
358 let predicate_params = consensus_params.predicate_params();
359 let base_asset_id = consensus_params.base_asset_id();
360 let gas_costs = consensus_params.gas_costs();
361 let fee_params = consensus_params.fee_params();
362
363 check_size(tx, tx_params)?;
364
365 if !tx.policies().is_valid() {
366 Err(ValidityError::TransactionPoliciesAreInvalid)?
367 }
368
369 if let Some(witness_limit) = tx.policies().get(PolicyType::WitnessLimit) {
370 let witness_size = tx.witnesses().size_dynamic();
371 if witness_size as u64 > witness_limit {
372 Err(ValidityError::TransactionWitnessLimitExceeded)?
373 }
374 }
375
376 let max_gas = tx.max_gas(gas_costs, fee_params);
377 if max_gas > tx_params.max_gas_per_tx() {
378 Err(ValidityError::TransactionMaxGasExceeded)?
379 }
380
381 if !tx.policies().is_set(PolicyType::MaxFee) {
382 Err(ValidityError::TransactionMaxFeeNotSet)?
383 };
384
385 if tx.maturity() > block_height {
386 Err(ValidityError::TransactionMaturity)?;
387 }
388
389 if tx.expiration() < block_height {
390 Err(ValidityError::TransactionExpiration)?;
391 }
392
393 if tx.inputs().len() > tx_params.max_inputs() as usize {
394 Err(ValidityError::TransactionInputsMax)?
395 }
396
397 if tx.outputs().len() > tx_params.max_outputs() as usize {
398 Err(ValidityError::TransactionOutputsMax)?
399 }
400
401 if tx.witnesses().len() > tx_params.max_witnesses() as usize {
402 Err(ValidityError::TransactionWitnessesMax)?
403 }
404
405 check_owner(tx)?;
406
407 let any_spendable_input = tx.inputs().iter().find(|input| match input {
408 Input::CoinSigned(_)
409 | Input::CoinPredicate(_)
410 | Input::MessageCoinSigned(_)
411 | Input::MessageCoinPredicate(_) => true,
412 Input::MessageDataSigned(_)
413 | Input::MessageDataPredicate(_)
414 | Input::Contract(_) => false,
415 });
416
417 if any_spendable_input.is_none() {
418 Err(ValidityError::NoSpendableInput)?
419 }
420
421 tx.input_asset_ids_unique(base_asset_id)
422 .try_for_each(|input_asset_id| {
423 if tx
425 .outputs()
426 .iter()
427 .filter_map(|output| match output {
428 Output::Change { asset_id, .. } if input_asset_id == asset_id => {
429 Some(())
430 }
431 _ => None,
432 })
433 .count()
434 > 1
435 {
436 return Err(ValidityError::TransactionOutputChangeAssetIdDuplicated(
437 *input_asset_id,
438 ));
439 }
440
441 Ok(())
442 })?;
443
444 let duplicated_utxo_id = tx
446 .inputs()
447 .iter()
448 .filter_map(|i| i.is_coin().then(|| i.utxo_id()).flatten());
449
450 if let Some(utxo_id) = next_duplicate(duplicated_utxo_id).copied() {
451 return Err(ValidityError::DuplicateInputUtxoId { utxo_id });
452 }
453
454 let duplicated_contract_id = tx.inputs().iter().filter_map(Input::contract_id);
456
457 if let Some(contract_id) = next_duplicate(duplicated_contract_id).copied() {
458 return Err(ValidityError::DuplicateInputContractId { contract_id });
459 }
460
461 let duplicated_nonce = tx.inputs().iter().filter_map(Input::nonce);
463 if let Some(nonce) = next_duplicate(duplicated_nonce).copied() {
464 return Err(ValidityError::DuplicateInputNonce { nonce });
465 }
466
467 tx.inputs()
469 .iter()
470 .enumerate()
471 .try_for_each(|(index, input)| {
472 input.check_without_signature(
473 index,
474 tx.outputs(),
475 tx.witnesses(),
476 predicate_params,
477 )
478 })?;
479
480 tx.outputs()
481 .iter()
482 .enumerate()
483 .try_for_each(|(index, output)| {
484 output.check(index, tx.inputs())?;
485
486 if let Output::Change { asset_id, .. } = output
487 && !tx
488 .input_asset_ids(base_asset_id)
489 .any(|input_asset_id| input_asset_id == asset_id)
490 {
491 return Err(ValidityError::TransactionOutputChangeAssetIdNotFound(
492 *asset_id,
493 ));
494 }
495
496 if let Output::Coin { asset_id, .. } = output
497 && !tx
498 .input_asset_ids(base_asset_id)
499 .any(|input_asset_id| input_asset_id == asset_id)
500 {
501 return Err(ValidityError::TransactionOutputCoinAssetIdNotFound(
502 *asset_id,
503 ));
504 }
505
506 Ok(())
507 })?;
508
509 Ok(())
510}
511
512pub(crate) fn next_duplicate<U>(iter: impl Iterator<Item = U>) -> Option<U>
514where
515 U: PartialEq + Ord + Copy + Hash,
516{
517 #[cfg(not(feature = "std"))]
518 {
519 iter.sorted()
520 .as_slice()
521 .windows(2)
522 .filter_map(|u| (u[0] == u[1]).then(|| u[0]))
523 .next()
524 }
525
526 #[cfg(feature = "std")]
527 {
528 iter.duplicates().next()
529 }
530}
531
532#[cfg(feature = "typescript")]
533mod typescript {
534 use crate::{
535 Witness,
536 transaction::consensus_parameters::typescript::PredicateParameters,
537 };
538 use fuel_types::Bytes32;
539 use wasm_bindgen::JsValue;
540
541 use alloc::{
542 format,
543 vec::Vec,
544 };
545
546 use crate::transaction::{
547 input_ts::Input,
548 output_ts::Output,
549 };
550
551 #[wasm_bindgen::prelude::wasm_bindgen]
552 pub fn check_input(
553 input: &Input,
554 index: usize,
555 txhash: &Bytes32,
556 outputs: Vec<JsValue>,
557 witnesses: Vec<JsValue>,
558 predicate_params: &PredicateParameters,
559 ) -> Result<(), js_sys::Error> {
560 let outputs: Vec<crate::Output> = outputs
561 .into_iter()
562 .map(|v| serde_wasm_bindgen::from_value::<Output>(v).map(|v| *v.0))
563 .collect::<Result<Vec<_>, _>>()
564 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))?;
565
566 let witnesses: Vec<Witness> = witnesses
567 .into_iter()
568 .map(serde_wasm_bindgen::from_value::<Witness>)
569 .collect::<Result<Vec<_>, _>>()
570 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))?;
571
572 input
573 .0
574 .check(
575 index,
576 txhash,
577 &outputs,
578 &witnesses,
579 predicate_params.as_ref(),
580 &mut None,
581 )
582 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))
583 }
584
585 #[wasm_bindgen::prelude::wasm_bindgen]
586 pub fn check_output(
587 output: &Output,
588 index: usize,
589 inputs: Vec<JsValue>,
590 ) -> Result<(), js_sys::Error> {
591 let inputs: Vec<crate::Input> = inputs
592 .into_iter()
593 .map(|v| serde_wasm_bindgen::from_value::<Input>(v).map(|v| *v.0))
594 .collect::<Result<Vec<_>, _>>()
595 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))?;
596
597 output
598 .0
599 .check(index, &inputs)
600 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))
601 }
602}