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