1#![cfg(feature = "std")]
2
3use fuel_asm::{op, GTFArgs, RegId};
4use fuel_tx::{
5 ConsensusParameters, FormatValidityChecks, Input as FuelInput, Output, StorageSlot,
6 Transaction as FuelTransaction, TransactionFee, TxPointer, Witness,
7};
8use fuel_types::{bytes::padded_len_usize, Address, AssetId, Bytes32, ContractId, Salt};
9
10use crate::{
11 coin::Coin,
12 coin_type::CoinType,
13 constants::{BASE_ASSET_ID, WORD_SIZE},
14 errors::{Error, Result},
15 input::Input,
16 message::Message,
17 offsets,
18 transaction::{CreateTransaction, ScriptTransaction, Transaction, TxParameters},
19};
20
21pub trait TransactionBuilder: Send {
22 type TxType: Transaction;
23
24 fn build(self) -> Result<Self::TxType>;
25
26 fn fee_checked_from_tx(&self, params: &ConsensusParameters) -> Option<TransactionFee>;
27
28 fn check_without_signatures(
29 &self,
30 block_height: u32,
31 parameters: &ConsensusParameters,
32 ) -> Result<()>;
33
34 fn set_maturity(self, maturity: u32) -> Self;
35 fn set_gas_price(self, gas_price: u64) -> Self;
36 fn set_gas_limit(self, gas_limit: u64) -> Self;
37 fn set_tx_params(self, tx_params: TxParameters) -> Self;
38 fn set_inputs(self, inputs: Vec<Input>) -> Self;
39 fn set_outputs(self, outputs: Vec<Output>) -> Self;
40 fn set_witnesses(self, witnesses: Vec<Witness>) -> Self;
41 fn set_consensus_parameters(self, consensus_parameters: ConsensusParameters) -> Self;
42 fn inputs(&self) -> &Vec<Input>;
43 fn inputs_mut(&mut self) -> &mut Vec<Input>;
44 fn outputs(&self) -> &Vec<Output>;
45 fn outputs_mut(&mut self) -> &mut Vec<Output>;
46 fn witnesses(&self) -> &Vec<Witness>;
47 fn witnesses_mut(&mut self) -> &mut Vec<Witness>;
48}
49
50macro_rules! impl_tx_trait {
51 ($ty: ty, $tx_ty: ty) => {
52 impl TransactionBuilder for $ty {
53 type TxType = $tx_ty;
54 fn build(self) -> Result<$tx_ty> {
55 let base_offset = if self.is_using_predicates() {
56 let params = self
57 .consensus_parameters
58 .ok_or(Error::TransactionBuildError)?;
59 self.base_offset(¶ms)
60 } else {
61 0
62 };
63
64 Ok(self.convert_to_fuel_tx(base_offset))
65 }
66
67 fn fee_checked_from_tx(&self, params: &ConsensusParameters) -> Option<TransactionFee> {
68 let tx = &self.clone().build().expect("Error in build").tx;
69 TransactionFee::checked_from_tx(params, tx)
70 }
71
72 fn check_without_signatures(
73 &self,
74 block_height: u32,
75 parameters: &ConsensusParameters,
76 ) -> Result<()> {
77 Ok(self
78 .clone()
79 .build()
80 .expect("Error in build")
81 .tx
82 .check_without_signatures(block_height.into(), parameters)?)
83 }
84
85 fn set_maturity(mut self, maturity: u32) -> Self {
86 self.maturity = maturity.into();
87 self
88 }
89
90 fn set_gas_price(mut self, gas_price: u64) -> Self {
91 self.gas_price = gas_price;
92 self
93 }
94
95 fn set_gas_limit(mut self, gas_limit: u64) -> Self {
96 self.gas_limit = gas_limit;
97 self
98 }
99
100 fn set_tx_params(self, tx_params: TxParameters) -> Self {
101 self.set_gas_limit(tx_params.gas_limit())
102 .set_gas_price(tx_params.gas_price())
103 .set_maturity(tx_params.maturity().into())
104 }
105
106 fn set_inputs(mut self, inputs: Vec<Input>) -> Self {
107 self.inputs = inputs;
108 self
109 }
110
111 fn set_outputs(mut self, outputs: Vec<Output>) -> Self {
112 self.outputs = outputs;
113 self
114 }
115
116 fn set_witnesses(mut self, witnesses: Vec<Witness>) -> Self {
117 self.witnesses = witnesses;
118 self
119 }
120
121 fn set_consensus_parameters(
122 mut self,
123 consensus_parameters: ConsensusParameters,
124 ) -> Self {
125 self.consensus_parameters = Some(consensus_parameters);
126 self
127 }
128
129 fn inputs(&self) -> &Vec<Input> {
130 self.inputs.as_ref()
131 }
132
133 fn inputs_mut(&mut self) -> &mut Vec<Input> {
134 &mut self.inputs
135 }
136
137 fn outputs(&self) -> &Vec<Output> {
138 self.outputs.as_ref()
139 }
140
141 fn outputs_mut(&mut self) -> &mut Vec<Output> {
142 &mut self.outputs
143 }
144
145 fn witnesses(&self) -> &Vec<Witness> {
146 self.witnesses.as_ref()
147 }
148
149 fn witnesses_mut(&mut self) -> &mut Vec<Witness> {
150 &mut self.witnesses
151 }
152 }
153
154 impl $ty {
155 fn is_using_predicates(&self) -> bool {
156 self.inputs()
157 .iter()
158 .any(|input| matches!(input, Input::ResourcePredicate { .. }))
159 }
160 }
161 };
162}
163
164#[derive(Debug, Clone, Default)]
165pub struct ScriptTransactionBuilder {
166 pub gas_price: u64,
167 pub gas_limit: u64,
168 pub maturity: u32,
169 pub script: Vec<u8>,
170 pub script_data: Vec<u8>,
171 pub inputs: Vec<Input>,
172 pub outputs: Vec<Output>,
173 pub witnesses: Vec<Witness>,
174 pub(crate) consensus_parameters: Option<ConsensusParameters>,
175}
176
177#[derive(Debug, Clone, Default)]
178pub struct CreateTransactionBuilder {
179 pub gas_price: u64,
180 pub gas_limit: u64,
181 pub maturity: u32,
182 pub bytecode_length: u64,
183 pub bytecode_witness_index: u8,
184 pub storage_slots: Vec<StorageSlot>,
185 pub inputs: Vec<Input>,
186 pub outputs: Vec<Output>,
187 pub witnesses: Vec<Witness>,
188 pub salt: Salt,
189 pub(crate) consensus_parameters: Option<ConsensusParameters>,
190}
191
192impl_tx_trait!(ScriptTransactionBuilder, ScriptTransaction);
193impl_tx_trait!(CreateTransactionBuilder, CreateTransaction);
194
195impl ScriptTransactionBuilder {
196 fn convert_to_fuel_tx(self, base_offset: usize) -> ScriptTransaction {
197 FuelTransaction::script(
198 self.gas_price,
199 self.gas_limit,
200 self.maturity.into(),
201 self.script,
202 self.script_data,
203 convert_to_fuel_inputs(&self.inputs, base_offset),
204 self.outputs,
205 self.witnesses,
206 )
207 .into()
208 }
209
210 fn base_offset(&self, consensus_parameters: &ConsensusParameters) -> usize {
211 offsets::base_offset_script(consensus_parameters)
212 + padded_len_usize(self.script_data.len())
213 + padded_len_usize(self.script.len())
214 }
215
216 pub fn set_script(mut self, script: Vec<u8>) -> Self {
217 self.script = script;
218 self
219 }
220
221 pub fn set_script_data(mut self, script_data: Vec<u8>) -> Self {
222 self.script_data = script_data;
223 self
224 }
225
226 pub fn prepare_transfer(
227 inputs: Vec<Input>,
228 outputs: Vec<Output>,
229 params: TxParameters,
230 ) -> Self {
231 ScriptTransactionBuilder::default()
232 .set_inputs(inputs)
233 .set_outputs(outputs)
234 .set_tx_params(params)
235 }
236
237 pub fn prepare_contract_transfer(
239 to: ContractId,
240 amount: u64,
241 asset_id: AssetId,
242 inputs: Vec<Input>,
243 outputs: Vec<Output>,
244 params: TxParameters,
245 ) -> Self {
246 let script_data: Vec<u8> = [
247 to.to_vec(),
248 amount.to_be_bytes().to_vec(),
249 asset_id.to_vec(),
250 ]
251 .into_iter()
252 .flatten()
253 .collect();
254
255 let script = vec![
262 op::gtf(0x10, 0x00, GTFArgs::ScriptData.into()),
263 op::addi(0x11, 0x10, ContractId::LEN as u16),
264 op::lw(0x12, 0x11, 0),
265 op::addi(0x13, 0x11, WORD_SIZE as u16),
266 op::tr(0x10, 0x12, 0x13),
267 op::ret(RegId::ONE),
268 ]
269 .into_iter()
270 .collect();
271
272 ScriptTransactionBuilder::default()
273 .set_tx_params(params)
274 .set_script(script)
275 .set_script_data(script_data)
276 .set_inputs(inputs)
277 .set_outputs(outputs)
278 }
279
280 pub fn prepare_message_to_output(
282 to: Address,
283 amount: u64,
284 inputs: Vec<Input>,
285 params: TxParameters,
286 ) -> Self {
287 let script_data: Vec<u8> = [to.to_vec(), amount.to_be_bytes().to_vec()]
288 .into_iter()
289 .flatten()
290 .collect();
291
292 let script: Vec<u8> = vec![
298 op::gtf(0x10, 0x00, GTFArgs::ScriptData.into()),
299 op::addi(0x11, 0x10, Bytes32::LEN as u16),
300 op::lw(0x11, 0x11, 0),
301 op::smo(0x10, 0x00, 0x00, 0x11),
302 op::ret(RegId::ONE),
303 ]
304 .into_iter()
305 .collect();
306
307 let outputs = vec![Output::change(to, 0, BASE_ASSET_ID)];
308
309 ScriptTransactionBuilder::default()
310 .set_tx_params(params)
311 .set_script(script)
312 .set_script_data(script_data)
313 .set_inputs(inputs)
314 .set_outputs(outputs)
315 }
316}
317
318impl CreateTransactionBuilder {
319 fn convert_to_fuel_tx(self, base_offset: usize) -> CreateTransaction {
320 FuelTransaction::create(
321 self.gas_price,
322 self.gas_limit,
323 self.maturity.into(),
324 self.bytecode_witness_index,
325 self.salt,
326 self.storage_slots,
327 convert_to_fuel_inputs(&self.inputs, base_offset), self.outputs,
329 self.witnesses,
330 )
331 .into()
332 }
333
334 fn base_offset(&self, consensus_parameters: &ConsensusParameters) -> usize {
335 offsets::base_offset_create(consensus_parameters)
336 }
337
338 pub fn set_bytecode_length(mut self, bytecode_length: u64) -> Self {
339 self.bytecode_length = bytecode_length;
340 self
341 }
342
343 pub fn set_bytecode_witness_index(mut self, bytecode_witness_index: u8) -> Self {
344 self.bytecode_witness_index = bytecode_witness_index;
345 self
346 }
347
348 pub fn set_storage_slots(mut self, mut storage_slots: Vec<StorageSlot>) -> Self {
349 storage_slots.sort();
352 self.storage_slots = storage_slots;
353 self
354 }
355
356 pub fn set_salt(mut self, salt: impl Into<Salt>) -> Self {
357 self.salt = salt.into();
358 self
359 }
360
361 pub fn prepare_contract_deployment(
362 binary: Vec<u8>,
363 contract_id: ContractId,
364 state_root: Bytes32,
365 salt: Salt,
366 storage_slots: Vec<StorageSlot>,
367 params: TxParameters,
368 ) -> Self {
369 let bytecode_witness_index = 0;
370 let outputs = vec![Output::contract_created(contract_id, state_root)];
371 let witnesses = vec![binary.into()];
372
373 CreateTransactionBuilder::default()
374 .set_tx_params(params)
375 .set_bytecode_witness_index(bytecode_witness_index)
376 .set_salt(salt)
377 .set_storage_slots(storage_slots)
378 .set_outputs(outputs)
379 .set_witnesses(witnesses)
380 }
381}
382
383fn convert_to_fuel_inputs(inputs: &[Input], offset: usize) -> Vec<FuelInput> {
384 let mut new_offset = offset;
385
386 inputs
387 .iter()
388 .map(|input| match input {
389 Input::ResourcePredicate {
390 resource: CoinType::Coin(coin),
391 code,
392 data,
393 } => {
394 new_offset += offsets::coin_predicate_data_offset(code.len());
395
396 let data = data.clone().resolve(new_offset as u64);
397 new_offset += data.len();
398
399 create_coin_predicate(coin.clone(), coin.asset_id, code.clone(), data)
400 }
401 Input::ResourcePredicate {
402 resource: CoinType::Message(message),
403 code,
404 data,
405 } => {
406 new_offset +=
407 offsets::message_predicate_data_offset(message.data.len(), code.len());
408
409 let data = data.clone().resolve(new_offset as u64);
410 new_offset += data.len();
411
412 create_coin_message_predicate(message.clone(), code.clone(), data)
413 }
414 Input::ResourceSigned {
415 resource,
416 witness_index,
417 } => match resource {
418 CoinType::Coin(coin) => {
419 new_offset += offsets::coin_signed_data_offset();
420 create_coin_input(coin.clone(), *witness_index)
421 }
422 CoinType::Message(message) => {
423 new_offset += offsets::message_signed_data_offset(message.data.len());
424 create_coin_message_input(message.clone(), *witness_index)
425 }
426 },
427 Input::Contract {
428 utxo_id,
429 balance_root,
430 state_root,
431 tx_pointer,
432 contract_id,
433 } => {
434 new_offset += offsets::contract_input_offset();
435 FuelInput::contract(
436 *utxo_id,
437 *balance_root,
438 *state_root,
439 *tx_pointer,
440 *contract_id,
441 )
442 }
443 })
444 .collect::<Vec<FuelInput>>()
445}
446
447pub fn create_coin_input(coin: Coin, witness_index: u8) -> FuelInput {
448 FuelInput::coin_signed(
449 coin.utxo_id,
450 coin.owner.into(),
451 coin.amount,
452 coin.asset_id,
453 TxPointer::default(),
454 witness_index,
455 0u32.into(),
456 )
457}
458
459pub fn create_coin_message_input(message: Message, witness_index: u8) -> FuelInput {
460 FuelInput::message_coin_signed(
461 message.sender.into(),
462 message.recipient.into(),
463 message.amount,
464 message.nonce,
465 witness_index,
466 )
467}
468
469pub fn create_coin_predicate(
470 coin: Coin,
471 asset_id: AssetId,
472 code: Vec<u8>,
473 predicate_data: Vec<u8>,
474) -> FuelInput {
475 FuelInput::coin_predicate(
476 coin.utxo_id,
477 coin.owner.into(),
478 coin.amount,
479 asset_id,
480 TxPointer::default(),
481 0u32.into(),
482 code,
483 predicate_data,
484 )
485}
486
487pub fn create_coin_message_predicate(
488 message: Message,
489 code: Vec<u8>,
490 predicate_data: Vec<u8>,
491) -> FuelInput {
492 FuelInput::message_coin_predicate(
493 message.sender.into(),
494 message.recipient.into(),
495 message.amount,
496 message.nonce,
497 code,
498 predicate_data,
499 )
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 #[test]
507 fn storage_slots_are_sorted_when_set() {
508 let unsorted_storage_slots = [2, 1].map(given_a_storage_slot).to_vec();
509 let sorted_storage_slots = [1, 2].map(given_a_storage_slot).to_vec();
510
511 let builder = CreateTransactionBuilder::default().set_storage_slots(unsorted_storage_slots);
512
513 assert_eq!(builder.storage_slots, sorted_storage_slots);
514 }
515
516 fn given_a_storage_slot(key: u8) -> StorageSlot {
517 let mut bytes_32 = Bytes32::zeroed();
518 bytes_32[0] = key;
519
520 StorageSlot::new(bytes_32, Default::default())
521 }
522}