fuel_tx/transaction/types/
create.rs1use crate::{
2 Chargeable,
3 ConsensusParameters,
4 Contract,
5 GasCosts,
6 Input,
7 Output,
8 PrepareSign,
9 StorageSlot,
10 TransactionRepr,
11 ValidityError,
12 transaction::{
13 field::{
14 BytecodeWitnessIndex,
15 Salt as SaltField,
16 StorageSlots,
17 },
18 metadata::CommonMetadata,
19 types::chargeable_transaction::{
20 ChargeableMetadata,
21 ChargeableTransaction,
22 UniqueFormatValidityChecks,
23 },
24 },
25};
26use educe::Educe;
27use fuel_types::{
28 Bytes4,
29 Bytes32,
30 ChainId,
31 ContractId,
32 Salt,
33 Word,
34 bytes::WORD_SIZE,
35 canonical,
36};
37
38#[cfg(feature = "alloc")]
39use alloc::vec::Vec;
40
41#[cfg(all(test, feature = "std"))]
42mod ser_de_tests;
43
44pub type Create = ChargeableTransaction<CreateBody, CreateMetadata>;
45
46impl Create {
47 pub fn bytecode(&self) -> Result<&[u8], ValidityError> {
48 let Create {
49 body:
50 CreateBody {
51 bytecode_witness_index,
52 ..
53 },
54 witnesses,
55 ..
56 } = self;
57
58 witnesses
59 .get(*bytecode_witness_index as usize)
60 .map(|c| c.as_ref())
61 .ok_or(ValidityError::TransactionCreateBytecodeWitnessIndex)
62 }
63}
64
65#[derive(Default, Debug, Clone, Educe)]
66#[educe(Eq, PartialEq, Hash)]
67pub struct CreateMetadata {
68 pub contract_id: ContractId,
69 pub contract_root: Bytes32,
70 pub state_root: Bytes32,
71}
72
73impl CreateMetadata {
74 pub fn compute(tx: &Create) -> Result<Self, ValidityError> {
76 let salt = tx.salt();
77 let storage_slots = tx.storage_slots();
78 let bytecode = tx.bytecode()?;
79 let contract_root = Contract::root_from_code(bytecode);
80 let state_root = Contract::initial_state_root(storage_slots.iter());
81 let contract_id = Contract::id(salt, &contract_root, &state_root);
82
83 Ok(Self {
84 contract_id,
85 contract_root,
86 state_root,
87 })
88 }
89}
90
91#[derive(Default, Debug, Clone, Educe, serde::Serialize, serde::Deserialize)]
92#[cfg_attr(
93 feature = "da-compression",
94 derive(fuel_compression::Compress, fuel_compression::Decompress)
95)]
96#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
97#[canonical(prefix = TransactionRepr::Create)]
98#[educe(Eq, PartialEq, Hash)]
99pub struct CreateBody {
100 pub(crate) bytecode_witness_index: u16,
101 pub(crate) salt: Salt,
102 pub(crate) storage_slots: Vec<StorageSlot>,
103}
104
105impl PrepareSign for CreateBody {
106 fn prepare_sign(&mut self) {}
107}
108
109impl Chargeable for Create {
110 #[inline(always)]
111 fn metered_bytes_size(&self) -> usize {
112 canonical::Serialize::size(self)
113 }
114
115 fn gas_used_by_metadata(&self, gas_costs: &GasCosts) -> Word {
116 let Create {
117 body:
118 CreateBody {
119 bytecode_witness_index,
120 storage_slots,
121 ..
122 },
123 witnesses,
124 ..
125 } = self;
126
127 let contract_len = witnesses
128 .get(*bytecode_witness_index as usize)
129 .map(|c| c.as_ref().len())
130 .unwrap_or(0);
131
132 let contract_root_gas = gas_costs.contract_root().resolve(contract_len as Word);
133 let state_root_length = storage_slots.len() as Word;
134 let state_root_gas = gas_costs.state_root().resolve(state_root_length);
135
136 let contract_id_input_length =
138 Bytes4::LEN + Salt::LEN + Bytes32::LEN + Bytes32::LEN;
139 let contract_id_gas = gas_costs.s256().resolve(contract_id_input_length as Word);
140 let bytes = canonical::Serialize::size(self);
141 let tx_id_gas = gas_costs.s256().resolve(bytes as u64);
143
144 contract_root_gas
145 .saturating_add(state_root_gas)
146 .saturating_add(contract_id_gas)
147 .saturating_add(tx_id_gas)
148 }
149}
150
151impl UniqueFormatValidityChecks for Create {
152 fn check_unique_rules(
153 &self,
154 consensus_params: &ConsensusParameters,
155 ) -> Result<(), ValidityError> {
156 let contract_params = consensus_params.contract_params();
157 let base_asset_id = consensus_params.base_asset_id();
158
159 let bytecode_witness_len = self
160 .witnesses
161 .get(self.body.bytecode_witness_index as usize)
162 .map(|w| w.as_ref().len() as Word)
163 .ok_or(ValidityError::TransactionCreateBytecodeWitnessIndex)?;
164
165 if bytecode_witness_len > contract_params.contract_max_size() {
166 return Err(ValidityError::TransactionCreateBytecodeLen);
167 }
168
169 if self.body.storage_slots.len() as u64 > contract_params.max_storage_slots() {
172 return Err(ValidityError::TransactionCreateStorageSlotMax);
173 }
174
175 if !self
177 .body
178 .storage_slots
179 .as_slice()
180 .windows(2)
181 .all(|s| s[0] < s[1])
182 {
183 return Err(ValidityError::TransactionCreateStorageSlotOrder);
184 }
185
186 self.inputs
187 .iter()
188 .enumerate()
189 .try_for_each(|(index, input)| {
190 if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id())
191 && asset_id != consensus_params.base_asset_id()
192 {
193 return Err(ValidityError::TransactionInputContainsNonBaseAssetId {
194 index,
195 });
196 }
197
198 match input {
199 Input::Contract(_) => {
200 Err(ValidityError::TransactionInputContainsContract { index })
201 }
202 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
203 Err(ValidityError::TransactionInputContainsMessageData { index })
204 }
205 _ => Ok(()),
206 }
207 })?;
208
209 debug_assert!(
210 self.metadata.is_some(),
211 "`check_without_signatures` is called without cached metadata"
212 );
213 let (state_root_calculated, contract_id_calculated) =
214 if let Some(metadata) = &self.metadata {
215 (metadata.body.state_root, metadata.body.contract_id)
216 } else {
217 let metadata = CreateMetadata::compute(self)?;
218 (metadata.state_root, metadata.contract_id)
219 };
220
221 let mut contract_created = false;
222 self.outputs
223 .iter()
224 .enumerate()
225 .try_for_each(|(index, output)| match output {
226 Output::Contract(_) => {
227 Err(ValidityError::TransactionOutputContainsContract { index })
228 }
229
230 Output::Variable { .. } => {
231 Err(ValidityError::TransactionOutputContainsVariable { index })
232 }
233
234 Output::Change { asset_id, .. } if asset_id != base_asset_id => {
235 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
236 }
237
238 Output::ContractCreated {
239 contract_id,
240 state_root,
241 } if contract_id != &contract_id_calculated
242 || state_root != &state_root_calculated =>
243 {
244 Err(
245 ValidityError::TransactionCreateOutputContractCreatedDoesntMatch {
246 index,
247 },
248 )
249 }
250
251 Output::ContractCreated { .. } if contract_created => {
255 Err(ValidityError::TransactionCreateOutputContractCreatedMultiple {
256 index,
257 })
258 }
259
260 Output::ContractCreated { .. } => {
261 contract_created = true;
262
263 Ok(())
264 }
265
266 _ => Ok(()),
267 })?;
268
269 if !contract_created {
270 return Err(ValidityError::TransactionOutputDoesntContainContractCreated);
271 }
272
273 Ok(())
274 }
275}
276
277impl crate::Cacheable for Create {
278 fn is_computed(&self) -> bool {
279 self.metadata.is_some()
280 }
281
282 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
283 self.metadata = None;
284 self.metadata = Some(ChargeableMetadata {
285 common: CommonMetadata::compute(self, chain_id)?,
286 body: CreateMetadata::compute(self)?,
287 });
288 Ok(())
289 }
290}
291
292mod field {
293 use super::*;
294 use crate::field::{
295 ChargeableBody,
296 StorageSlotRef,
297 };
298
299 impl BytecodeWitnessIndex for Create {
300 #[inline(always)]
301 fn bytecode_witness_index(&self) -> &u16 {
302 &self.body.bytecode_witness_index
303 }
304
305 #[inline(always)]
306 fn bytecode_witness_index_mut(&mut self) -> &mut u16 {
307 &mut self.body.bytecode_witness_index
308 }
309
310 #[inline(always)]
311 fn bytecode_witness_index_offset_static() -> usize {
312 WORD_SIZE }
314 }
315
316 impl SaltField for Create {
317 #[inline(always)]
318 fn salt(&self) -> &Salt {
319 &self.body.salt
320 }
321
322 #[inline(always)]
323 fn salt_mut(&mut self) -> &mut Salt {
324 &mut self.body.salt
325 }
326
327 #[inline(always)]
328 fn salt_offset_static() -> usize {
329 Self::bytecode_witness_index_offset_static().saturating_add(WORD_SIZE)
330 }
331 }
332
333 impl StorageSlots for Create {
334 #[inline(always)]
335 fn storage_slots(&self) -> &Vec<StorageSlot> {
336 &self.body.storage_slots
337 }
338
339 #[inline(always)]
340 fn storage_slots_mut(&mut self) -> StorageSlotRef<'_> {
341 StorageSlotRef {
342 storage_slots: &mut self.body.storage_slots,
343 }
344 }
345
346 #[inline(always)]
347 fn storage_slots_offset_static() -> usize {
348 Self::salt_offset_static().saturating_add(
349 Salt::LEN
350 + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
356 }
357
358 fn storage_slots_offset_at(&self, idx: usize) -> Option<usize> {
359 if idx < self.body.storage_slots.len() {
360 Some(
361 Self::storage_slots_offset_static()
362 .checked_add(idx.checked_mul(StorageSlot::SLOT_SIZE)?)?,
363 )
364 } else {
365 None
366 }
367 }
368 }
369
370 impl ChargeableBody<CreateBody> for Create {
371 fn body(&self) -> &CreateBody {
372 &self.body
373 }
374
375 fn body_mut(&mut self) -> &mut CreateBody {
376 &mut self.body
377 }
378
379 fn body_offset_end(&self) -> usize {
380 Self::storage_slots_offset_static().saturating_add(
381 self.body
382 .storage_slots
383 .len()
384 .saturating_mul(StorageSlot::SLOT_SIZE),
385 )
386 }
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::{
394 builder::Finalizable,
395 transaction::validity::FormatValidityChecks,
396 };
397 use fuel_types::Bytes32;
398
399 #[test]
400 fn storage_slots_sorting() {
401 let mut slot_data = [0u8; 64];
403
404 let storage_slots = (0..10u64)
405 .map(|i| {
406 slot_data[..8].copy_from_slice(&i.to_be_bytes());
407 StorageSlot::from(&slot_data.into())
408 })
409 .collect::<Vec<StorageSlot>>();
410
411 let mut tx = crate::TransactionBuilder::create(
412 vec![].into(),
413 Salt::zeroed(),
414 storage_slots,
415 )
416 .add_fee_input()
417 .finalize();
418 tx.body.storage_slots.reverse();
419
420 let err = tx
421 .check(0.into(), &ConsensusParameters::standard())
422 .expect_err("Expected erroneous transaction");
423
424 assert_eq!(ValidityError::TransactionCreateStorageSlotOrder, err);
425 }
426
427 #[test]
428 fn storage_slots_no_duplicates() {
429 let storage_slots = vec![
430 StorageSlot::new(Bytes32::zeroed(), Bytes32::zeroed()),
431 StorageSlot::new(Bytes32::zeroed(), Bytes32::zeroed()),
432 ];
433
434 let err = crate::TransactionBuilder::create(
435 vec![].into(),
436 Salt::zeroed(),
437 storage_slots,
438 )
439 .add_fee_input()
440 .finalize()
441 .check(0.into(), &ConsensusParameters::standard())
442 .expect_err("Expected erroneous transaction");
443
444 assert_eq!(ValidityError::TransactionCreateStorageSlotOrder, err);
445 }
446}