fuel_tx/transaction/types/
upload.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 core::ops::Deref;
22use educe::Educe;
23use fuel_types::{
24 bytes::WORD_SIZE,
25 canonical::Serialize,
26 Bytes32,
27 ChainId,
28 Word,
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 if asset_id != consensus_params.base_asset_id() {
229 return Err(
230 ValidityError::TransactionInputContainsNonBaseAssetId {
231 index,
232 },
233 );
234 }
235 }
236
237 match input {
238 Input::Contract(_) => {
239 Err(ValidityError::TransactionInputContainsContract { index })
240 }
241 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
242 Err(ValidityError::TransactionInputContainsMessageData { index })
243 }
244 _ => Ok(()),
245 }
246 })?;
247
248 self.outputs
249 .iter()
250 .enumerate()
251 .try_for_each(|(index, output)| match output {
252 Output::Contract(_) => {
253 Err(ValidityError::TransactionOutputContainsContract { index })
254 }
255
256 Output::Variable { .. } => {
257 Err(ValidityError::TransactionOutputContainsVariable { index })
258 }
259
260 Output::Change { asset_id, .. }
261 if asset_id != consensus_params.base_asset_id() =>
262 {
263 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
264 }
265
266 Output::ContractCreated { .. } => {
267 Err(ValidityError::TransactionOutputContainsContractCreated { index })
268 }
269 _ => Ok(()),
270 })?;
271
272 Ok(())
273 }
274}
275
276impl crate::Cacheable for Upload {
277 fn is_computed(&self) -> bool {
278 self.metadata.is_some()
279 }
280
281 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
282 self.metadata = None;
283 self.metadata = Some(ChargeableMetadata {
284 common: CommonMetadata::compute(self, chain_id)?,
285 body: UploadMetadata {},
286 });
287 Ok(())
288 }
289}
290
291mod field {
292 use super::*;
293 use crate::field::{
294 BytecodeRoot,
295 BytecodeWitnessIndex,
296 ChargeableBody,
297 ProofSet,
298 SubsectionIndex,
299 SubsectionsNumber,
300 };
301
302 impl BytecodeRoot for Upload {
303 #[inline(always)]
304 fn bytecode_root(&self) -> &Bytes32 {
305 &self.body.root
306 }
307
308 #[inline(always)]
309 fn bytecode_root_mut(&mut self) -> &mut Bytes32 {
310 &mut self.body.root
311 }
312
313 #[inline(always)]
314 fn bytecode_root_offset_static() -> usize {
315 WORD_SIZE }
317 }
318
319 impl BytecodeWitnessIndex for Upload {
320 #[inline(always)]
321 fn bytecode_witness_index(&self) -> &u16 {
322 &self.body.witness_index
323 }
324
325 #[inline(always)]
326 fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
327 &mut self.body.witness_index
328 }
329
330 #[inline(always)]
331 fn bytecode_witness_index_offset_static() -> usize {
332 Self::bytecode_root_offset_static().saturating_add(Bytes32::LEN)
333 }
334 }
335
336 impl SubsectionIndex for Upload {
337 #[inline(always)]
338 fn subsection_index(&self) -> &u16 {
339 &self.body.subsection_index
340 }
341
342 #[inline(always)]
343 fn subsection_index_mut(&mut self) -> &mut u16 {
344 &mut self.body.subsection_index
345 }
346
347 #[inline(always)]
348 fn subsection_index_offset_static() -> usize {
349 Self::bytecode_witness_index_offset_static().saturating_add(WORD_SIZE)
350 }
351 }
352
353 impl SubsectionsNumber for Upload {
354 #[inline(always)]
355 fn subsections_number(&self) -> &u16 {
356 &self.body.subsections_number
357 }
358
359 #[inline(always)]
360 fn subsections_number_mut(&mut self) -> &mut u16 {
361 &mut self.body.subsections_number
362 }
363
364 #[inline(always)]
365 fn subsections_number_offset_static() -> usize {
366 Self::subsection_index_offset_static().saturating_add(WORD_SIZE)
367 }
368 }
369
370 impl ProofSet for Upload {
371 #[inline(always)]
372 fn proof_set(&self) -> &Vec<Bytes32> {
373 &self.body.proof_set
374 }
375
376 #[inline(always)]
377 fn proof_set_mut(&mut self) -> &mut Vec<Bytes32> {
378 &mut self.body.proof_set
379 }
380
381 #[inline(always)]
382 fn proof_set_offset_static() -> usize {
383 Self::subsections_number_offset_static().saturating_add(
384 WORD_SIZE
385 + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
391 }
392
393 #[inline(always)]
394 fn proof_set_offset_at(&self, idx: usize) -> Option<usize> {
395 if idx < self.body.proof_set.len() {
396 Some(
397 Self::proof_set_offset_static()
398 .checked_add(idx.checked_mul(Bytes32::LEN)?)?,
399 )
400 } else {
401 None
402 }
403 }
404 }
405
406 impl ChargeableBody<UploadBody> for Upload {
407 fn body(&self) -> &UploadBody {
408 &self.body
409 }
410
411 fn body_mut(&mut self) -> &mut UploadBody {
412 &mut self.body
413 }
414
415 fn body_offset_end(&self) -> usize {
416 Self::proof_set_offset_static()
417 .saturating_add(self.body.proof_set.len().saturating_mul(Bytes32::LEN))
418 }
419 }
420}