fuel_tx/transaction/types/
blob.rs1use crate::{
2 transaction::{
3 fee::min_gas,
4 id::PrepareSign,
5 metadata::CommonMetadata,
6 types::chargeable_transaction::{
7 ChargeableMetadata,
8 ChargeableTransaction,
9 UniqueFormatValidityChecks,
10 },
11 Chargeable,
12 },
13 ConsensusParameters,
14 FeeParameters,
15 GasCosts,
16 Input,
17 Output,
18 TransactionRepr,
19 ValidityError,
20};
21use educe::Educe;
22use fuel_types::{
23 bytes::WORD_SIZE,
24 canonical::Serialize,
25 BlobId,
26 ChainId,
27 Word,
28};
29
30pub trait BlobIdExt {
32 fn compute(data: &[u8]) -> BlobId;
34}
35
36impl BlobIdExt for BlobId {
37 fn compute(data: &[u8]) -> Self {
38 Self::new(*fuel_crypto::Hasher::hash(data))
39 }
40}
41
42pub type Blob = ChargeableTransaction<BlobBody, BlobMetadata>;
43
44#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
45pub struct BlobMetadata;
46
47#[derive(Clone, Default, Educe, serde::Serialize, serde::Deserialize)]
49#[cfg_attr(
50 feature = "da-compression",
51 derive(fuel_compression::Compress, fuel_compression::Decompress)
52)]
53#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
54#[canonical(prefix = TransactionRepr::Blob)]
55#[educe(Eq, PartialEq, Hash, Debug)]
56pub struct BlobBody {
57 pub id: BlobId,
60 pub witness_index: u16,
62}
63
64impl PrepareSign for BlobBody {
65 fn prepare_sign(&mut self) {}
66}
67
68impl Chargeable for Blob {
69 fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word {
70 min_gas(self, gas_costs, fee)
71 }
72
73 #[inline(always)]
74 fn metered_bytes_size(&self) -> usize {
75 Serialize::size(self)
76 }
77
78 #[inline(always)]
79 fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
80 let bytes = Serialize::size(self);
81 let blob_len = self
82 .witnesses
83 .get(self.body.witness_index as usize)
84 .map(|c| c.as_ref().len())
85 .unwrap_or(0);
86
87 gas_cost
89 .s256()
90 .resolve(bytes as u64)
91 .saturating_add(gas_cost.s256().resolve(blob_len as u64))
92 }
93}
94
95impl UniqueFormatValidityChecks for Blob {
96 fn check_unique_rules(
97 &self,
98 consensus_params: &ConsensusParameters,
99 ) -> Result<(), ValidityError> {
100 let index = self.body.witness_index as usize;
101 let witness = self
102 .witnesses
103 .get(index)
104 .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
105
106 if BlobId::compute(witness.as_ref()) != self.body.id {
108 return Err(ValidityError::TransactionBlobIdVerificationFailed);
109 }
110
111 self.inputs
112 .iter()
113 .enumerate()
114 .try_for_each(|(index, input)| {
115 if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id()) {
116 if asset_id != consensus_params.base_asset_id() {
117 return Err(
118 ValidityError::TransactionInputContainsNonBaseAssetId {
119 index,
120 },
121 );
122 }
123 }
124
125 match input {
126 Input::Contract(_) => {
127 Err(ValidityError::TransactionInputContainsContract { index })
128 }
129 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
130 Err(ValidityError::TransactionInputContainsMessageData { index })
131 }
132 _ => Ok(()),
133 }
134 })?;
135
136 self.outputs
137 .iter()
138 .enumerate()
139 .try_for_each(|(index, output)| match output {
140 Output::Contract(_) => {
141 Err(ValidityError::TransactionOutputContainsContract { index })
142 }
143
144 Output::Variable { .. } => {
145 Err(ValidityError::TransactionOutputContainsVariable { index })
146 }
147
148 Output::Change { asset_id, .. } => {
149 if asset_id != consensus_params.base_asset_id() {
150 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset {
151 index,
152 })
153 } else {
154 Ok(())
155 }
156 }
157
158 Output::ContractCreated { .. } => {
159 Err(ValidityError::TransactionOutputContainsContractCreated { index })
160 }
161
162 Output::Coin { .. } => Ok(()),
163 })?;
164
165 Ok(())
166 }
167}
168
169impl crate::Cacheable for Blob {
170 fn is_computed(&self) -> bool {
171 self.metadata.is_some()
172 }
173
174 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
175 self.metadata = None;
176 self.metadata = Some(ChargeableMetadata {
177 common: CommonMetadata::compute(self, chain_id)?,
178 body: BlobMetadata {},
179 });
180 Ok(())
181 }
182}
183
184mod field {
185 use super::*;
186 use crate::field::{
187 self,
188 BlobId as BlobIdField,
189 BytecodeWitnessIndex,
190 };
191
192 impl field::BlobId for Blob {
193 #[inline(always)]
194 fn blob_id(&self) -> &BlobId {
195 &self.body.id
196 }
197
198 #[inline(always)]
199 fn blob_id_mut(&mut self) -> &mut BlobId {
200 &mut self.body.id
201 }
202
203 #[inline(always)]
204 fn blob_id_offset_static() -> usize {
205 WORD_SIZE }
207 }
208
209 impl field::BytecodeWitnessIndex for Blob {
210 #[inline(always)]
211 fn bytecode_witness_index(&self) -> &u16 {
212 &self.body.witness_index
213 }
214
215 #[inline(always)]
216 fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
217 &mut self.body.witness_index
218 }
219
220 #[inline(always)]
221 fn bytecode_witness_index_offset_static() -> usize {
222 Self::blob_id_offset_static().saturating_add(BlobId::LEN)
223 }
224 }
225
226 impl field::ChargeableBody<BlobBody> for Blob {
227 fn body(&self) -> &BlobBody {
228 &self.body
229 }
230
231 fn body_mut(&mut self) -> &mut BlobBody {
232 &mut self.body
233 }
234
235 #[allow(clippy::arithmetic_side_effects)] fn body_offset_end(&self) -> usize {
237 Self::bytecode_witness_index_offset_static().saturating_add(
238 WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
244 }
245 }
246}