fuels_types/
transaction_builders.rs

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(&params)
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    /// Craft a transaction used to transfer funds to a contract.
238    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        // This script loads:
256        //  - a pointer to the contract id,
257        //  - the actual amount
258        //  - a pointer to the asset id
259        // into the registers 0x10, 0x12, 0x13
260        // and calls the TR instruction
261        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    /// Craft a transaction used to transfer funds to the base chain.
281    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        // This script loads:
293        //  - a pointer to the recipient address,
294        //  - the amount
295        // into the registers 0x10, 0x11
296        // and calls the SMO instruction
297        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), //placeholder offset
328            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 have to be sorted otherwise we'd get a `TransactionCreateStorageSlotOrder`
350        // error.
351        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}