fuel_tx/transaction/types/
upload.rs

1use 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/// The body of the [`Upload`] transaction.
40#[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    /// The root of the Merkle tree is created over the bytecode.
50    pub root: Bytes32,
51    /// The witness index of the subsection of the bytecode.
52    pub witness_index: u16,
53    /// The index of the subsection of the bytecode.
54    pub subsection_index: u16,
55    /// The total number of subsections on which bytecode was divided.
56    pub subsections_number: u16,
57    /// The proof set helps to verify the connection of the subsection to the `root`.
58    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    /// The root of the Merkle tree is created over the bytecode.
66    pub root: Bytes32,
67    /// The subsection of the bytecode.
68    pub subsection: Vec<u8>,
69    /// The index of the subsection.
70    pub subsection_index: u16,
71    /// The total number of subsections on which bytecode was divided.
72    pub subsections_number: u16,
73    /// The proof set helps to verify the connection of the subsection to the `root`.
74    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    /// The size of the subsection is too small to fit all subsections into `u16::MAX`.
82    SubsectionSizeTooSmall,
83}
84
85impl UploadSubsection {
86    /// Splits the bytecode into verifiable subsections and returns a vector of
87    /// [`UploadSubsection`]s.
88    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        // Since the `Upload` transaction occupies much of the storage, we want to
148        // discourage people from using it too much. For that, we charge additional gas
149        // for the storage.
150        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        // Gas required to calculate the `tx_id`.
166        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        // Verify that subsection of the bytecode is connected to the `root` of the
210        // bytecode.
211        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 // `Transaction` enum discriminant
314        }
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 // Proof set size
384                + WORD_SIZE // Policies size
385                + WORD_SIZE // Inputs size
386                + WORD_SIZE // Outputs size
387                + WORD_SIZE, // Witnesses size
388            )
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}