fuel_tx/transaction/types/
upload.rs1use crate::{
2 ConsensusParameters,
3 FeeParameters,
4 GasCosts,
5 Input,
6 Output,
7 TransactionRepr,
8 ValidityError,
9 transaction::{
10 Chargeable,
11 fee::min_gas,
12 id::PrepareSign,
13 metadata::CommonMetadata,
14 types::chargeable_transaction::{
15 ChargeableMetadata,
16 ChargeableTransaction,
17 UniqueFormatValidityChecks,
18 },
19 },
20};
21use core::ops::Deref;
22use educe::Educe;
23use fuel_types::{
24 Bytes32,
25 ChainId,
26 Word,
27 bytes::WORD_SIZE,
28 canonical::Serialize,
29};
30
31#[cfg(feature = "alloc")]
32use alloc::vec::Vec;
33
34pub type Upload = ChargeableTransaction<UploadBody, UploadMetadata>;
35
36#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
37pub struct UploadMetadata;
38
39#[derive(Clone, Default, Educe, serde::Serialize, serde::Deserialize)]
41#[cfg_attr(
42 feature = "da-compression",
43 derive(fuel_compression::Compress, fuel_compression::Decompress)
44)]
45#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
46#[canonical(prefix = TransactionRepr::Upload)]
47#[educe(Eq, PartialEq, Hash, Debug)]
48pub struct UploadBody {
49 pub root: Bytes32,
51 pub witness_index: u16,
53 pub subsection_index: u16,
55 pub subsections_number: u16,
57 pub proof_set: Vec<Bytes32>,
59}
60
61#[derive(
62 Clone, Default, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize,
63)]
64pub struct UploadSubsection {
65 pub root: Bytes32,
67 pub subsection: Vec<u8>,
69 pub subsection_index: u16,
71 pub subsections_number: u16,
73 pub proof_set: Vec<Bytes32>,
75}
76
77#[derive(
78 Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize,
79)]
80pub enum SplitError {
81 SubsectionSizeTooSmall,
83}
84
85impl UploadSubsection {
86 pub fn split_bytecode(
89 bytecode: &[u8],
90 subsection_size: usize,
91 ) -> Result<Vec<UploadSubsection>, SplitError> {
92 let subsections = bytecode
93 .chunks(subsection_size)
94 .map(|subsection| subsection.to_vec())
95 .collect::<Vec<_>>();
96
97 if subsections.len() > u16::MAX as usize {
98 return Err(SplitError::SubsectionSizeTooSmall);
99 }
100 let subsections_number =
101 u16::try_from(subsections.len()).expect("We've just checked it; qed");
102
103 let mut merkle_tree = fuel_merkle::binary::in_memory::MerkleTree::new();
104 subsections
105 .iter()
106 .for_each(|subsection| merkle_tree.push(subsection));
107
108 let merkle_root = merkle_tree.root();
109
110 let subsections = subsections
111 .into_iter()
112 .enumerate()
113 .map(|(index, subsection)| {
114 let (root, proof_set) = merkle_tree
115 .prove(index as u64)
116 .expect("We've just created a merkle tree, so it is valid; qed");
117 debug_assert_eq!(root, merkle_root);
118
119 UploadSubsection {
120 root: merkle_root.into(),
121 subsection,
122 subsection_index: u16::try_from(index).expect(
123 "The total number of subsections is less than u16::MAX; qed",
124 ),
125 subsections_number,
126 proof_set: proof_set.into_iter().map(Into::into).collect(),
127 }
128 })
129 .collect();
130
131 Ok(subsections)
132 }
133}
134
135impl PrepareSign for UploadBody {
136 fn prepare_sign(&mut self) {}
137}
138
139impl Chargeable for Upload {
140 fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word {
141 let bytecode_len = self
142 .witnesses
143 .get(self.body.witness_index as usize)
144 .map(|c| c.as_ref().len())
145 .unwrap_or(0);
146
147 let additional_charge_for_storage = gas_costs
151 .new_storage_per_byte()
152 .saturating_mul(bytecode_len as u64);
153
154 min_gas(self, gas_costs, fee).saturating_add(additional_charge_for_storage)
155 }
156
157 #[inline(always)]
158 fn metered_bytes_size(&self) -> usize {
159 Serialize::size(self)
160 }
161
162 #[inline(always)]
163 fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
164 let bytes = Serialize::size(self);
165 let tx_id_gas = gas_cost.s256().resolve(bytes as u64);
167
168 let bytecode_len = self
169 .witnesses
170 .get(self.body.witness_index as usize)
171 .map(|c| c.as_ref().len())
172 .unwrap_or(0);
173
174 let leaf_hash_gas = gas_cost.s256().resolve(bytecode_len as u64);
175 let verify_proof_gas = gas_cost
176 .state_root()
177 .resolve(self.body.subsections_number as u64);
178
179 tx_id_gas
180 .saturating_add(leaf_hash_gas)
181 .saturating_add(verify_proof_gas)
182 }
183}
184
185impl UniqueFormatValidityChecks for Upload {
186 fn check_unique_rules(
187 &self,
188 consensus_params: &ConsensusParameters,
189 ) -> Result<(), ValidityError> {
190 if self.body.subsections_number
191 > consensus_params.tx_params().max_bytecode_subsections()
192 {
193 return Err(ValidityError::TransactionUploadTooManyBytecodeSubsections);
194 }
195
196 let index = self.body.witness_index as usize;
197 let witness = self
198 .witnesses
199 .get(index)
200 .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
201
202 let proof_set = self
203 .body
204 .proof_set
205 .iter()
206 .map(|proof| (*proof).into())
207 .collect::<Vec<_>>();
208
209 let result = fuel_merkle::binary::verify(
212 self.body.root.deref(),
213 witness,
214 &proof_set,
215 self.body.subsection_index as u64,
216 self.body.subsections_number as u64,
217 );
218
219 if !result {
220 return Err(ValidityError::TransactionUploadRootVerificationFailed);
221 }
222
223 self.inputs
224 .iter()
225 .enumerate()
226 .try_for_each(|(index, input)| {
227 if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id())
228 && asset_id != consensus_params.base_asset_id()
229 {
230 return Err(ValidityError::TransactionInputContainsNonBaseAssetId {
231 index,
232 });
233 }
234
235 match input {
236 Input::Contract(_) => {
237 Err(ValidityError::TransactionInputContainsContract { index })
238 }
239 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
240 Err(ValidityError::TransactionInputContainsMessageData { index })
241 }
242 _ => Ok(()),
243 }
244 })?;
245
246 self.outputs
247 .iter()
248 .enumerate()
249 .try_for_each(|(index, output)| match output {
250 Output::Contract(_) => {
251 Err(ValidityError::TransactionOutputContainsContract { index })
252 }
253
254 Output::Variable { .. } => {
255 Err(ValidityError::TransactionOutputContainsVariable { index })
256 }
257
258 Output::Change { asset_id, .. }
259 if asset_id != consensus_params.base_asset_id() =>
260 {
261 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
262 }
263
264 Output::ContractCreated { .. } => {
265 Err(ValidityError::TransactionOutputContainsContractCreated { index })
266 }
267 _ => Ok(()),
268 })?;
269
270 Ok(())
271 }
272}
273
274impl crate::Cacheable for Upload {
275 fn is_computed(&self) -> bool {
276 self.metadata.is_some()
277 }
278
279 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
280 self.metadata = None;
281 self.metadata = Some(ChargeableMetadata {
282 common: CommonMetadata::compute(self, chain_id)?,
283 body: UploadMetadata {},
284 });
285 Ok(())
286 }
287}
288
289mod field {
290 use super::*;
291 use crate::field::{
292 BytecodeRoot,
293 BytecodeWitnessIndex,
294 ChargeableBody,
295 ProofSet,
296 SubsectionIndex,
297 SubsectionsNumber,
298 };
299
300 impl BytecodeRoot for Upload {
301 #[inline(always)]
302 fn bytecode_root(&self) -> &Bytes32 {
303 &self.body.root
304 }
305
306 #[inline(always)]
307 fn bytecode_root_mut(&mut self) -> &mut Bytes32 {
308 &mut self.body.root
309 }
310
311 #[inline(always)]
312 fn bytecode_root_offset_static() -> usize {
313 WORD_SIZE }
315 }
316
317 impl BytecodeWitnessIndex for Upload {
318 #[inline(always)]
319 fn bytecode_witness_index(&self) -> &u16 {
320 &self.body.witness_index
321 }
322
323 #[inline(always)]
324 fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
325 &mut self.body.witness_index
326 }
327
328 #[inline(always)]
329 fn bytecode_witness_index_offset_static() -> usize {
330 Self::bytecode_root_offset_static().saturating_add(Bytes32::LEN)
331 }
332 }
333
334 impl SubsectionIndex for Upload {
335 #[inline(always)]
336 fn subsection_index(&self) -> &u16 {
337 &self.body.subsection_index
338 }
339
340 #[inline(always)]
341 fn subsection_index_mut(&mut self) -> &mut u16 {
342 &mut self.body.subsection_index
343 }
344
345 #[inline(always)]
346 fn subsection_index_offset_static() -> usize {
347 Self::bytecode_witness_index_offset_static().saturating_add(WORD_SIZE)
348 }
349 }
350
351 impl SubsectionsNumber for Upload {
352 #[inline(always)]
353 fn subsections_number(&self) -> &u16 {
354 &self.body.subsections_number
355 }
356
357 #[inline(always)]
358 fn subsections_number_mut(&mut self) -> &mut u16 {
359 &mut self.body.subsections_number
360 }
361
362 #[inline(always)]
363 fn subsections_number_offset_static() -> usize {
364 Self::subsection_index_offset_static().saturating_add(WORD_SIZE)
365 }
366 }
367
368 impl ProofSet for Upload {
369 #[inline(always)]
370 fn proof_set(&self) -> &Vec<Bytes32> {
371 &self.body.proof_set
372 }
373
374 #[inline(always)]
375 fn proof_set_mut(&mut self) -> &mut Vec<Bytes32> {
376 &mut self.body.proof_set
377 }
378
379 #[inline(always)]
380 fn proof_set_offset_static() -> usize {
381 Self::subsections_number_offset_static().saturating_add(
382 WORD_SIZE
383 + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
389 }
390
391 #[inline(always)]
392 fn proof_set_offset_at(&self, idx: usize) -> Option<usize> {
393 if idx < self.body.proof_set.len() {
394 Some(
395 Self::proof_set_offset_static()
396 .checked_add(idx.checked_mul(Bytes32::LEN)?)?,
397 )
398 } else {
399 None
400 }
401 }
402 }
403
404 impl ChargeableBody<UploadBody> for Upload {
405 fn body(&self) -> &UploadBody {
406 &self.body
407 }
408
409 fn body_mut(&mut self) -> &mut UploadBody {
410 &mut self.body
411 }
412
413 fn body_offset_end(&self) -> usize {
414 Self::proof_set_offset_static()
415 .saturating_add(self.body.proof_set.len().saturating_mul(Bytes32::LEN))
416 }
417 }
418}