fuel_tx/transaction/types/
script.rs

1use core::ops::{
2    Deref,
3    DerefMut,
4};
5
6use crate::{
7    field::WitnessLimit,
8    transaction::{
9        field::{
10            ReceiptsRoot,
11            Script as ScriptField,
12            ScriptData,
13            ScriptGasLimit,
14            Witnesses,
15        },
16        id::PrepareSign,
17        metadata::CommonMetadata,
18        types::chargeable_transaction::{
19            ChargeableMetadata,
20            ChargeableTransaction,
21            UniqueFormatValidityChecks,
22        },
23        Chargeable,
24    },
25    ConsensusParameters,
26    FeeParameters,
27    GasCosts,
28    Output,
29    TransactionRepr,
30    ValidityError,
31};
32use educe::Educe;
33use fuel_types::{
34    bytes,
35    bytes::WORD_SIZE,
36    canonical::Serialize,
37    fmt_truncated_hex,
38    Bytes32,
39    ChainId,
40    Word,
41};
42
43#[cfg(feature = "alloc")]
44use alloc::vec::Vec;
45
46pub type Script = ChargeableTransaction<ScriptBody, ScriptMetadata>;
47
48#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
49pub struct ScriptMetadata {
50    pub script_data_offset: usize,
51}
52
53#[derive(Clone, Default, Educe, serde::Serialize, serde::Deserialize)]
54#[serde(transparent)]
55#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
56#[educe(Eq, PartialEq, Hash, Debug)]
57pub struct ScriptCode {
58    #[educe(Debug(method(fmt_truncated_hex::<16>)))]
59    pub bytes: Vec<u8>,
60}
61
62impl From<Vec<u8>> for ScriptCode {
63    fn from(bytes: Vec<u8>) -> Self {
64        Self { bytes }
65    }
66}
67
68impl From<&[u8]> for ScriptCode {
69    fn from(bytes: &[u8]) -> Self {
70        Self {
71            bytes: bytes.to_vec(),
72        }
73    }
74}
75
76impl AsRef<[u8]> for ScriptCode {
77    fn as_ref(&self) -> &[u8] {
78        &self.bytes
79    }
80}
81
82impl AsMut<[u8]> for ScriptCode {
83    fn as_mut(&mut self) -> &mut [u8] {
84        &mut self.bytes
85    }
86}
87
88impl Deref for ScriptCode {
89    type Target = Vec<u8>;
90
91    fn deref(&self) -> &Self::Target {
92        &self.bytes
93    }
94}
95
96impl DerefMut for ScriptCode {
97    fn deref_mut(&mut self) -> &mut Self::Target {
98        &mut self.bytes
99    }
100}
101
102#[cfg(feature = "da-compression")]
103impl fuel_compression::Compressible for ScriptCode {
104    type Compressed = fuel_compression::RegistryKey;
105}
106
107#[derive(
108    Clone,
109    Educe,
110    serde::Serialize,
111    serde::Deserialize,
112    fuel_types::canonical::Deserialize,
113    fuel_types::canonical::Serialize,
114)]
115#[cfg_attr(
116    feature = "da-compression",
117    derive(fuel_compression::Compress, fuel_compression::Decompress)
118)]
119#[canonical(prefix = TransactionRepr::Script)]
120#[educe(Eq, PartialEq, Hash, Debug)]
121pub struct ScriptBody {
122    pub(crate) script_gas_limit: Word,
123    #[cfg_attr(feature = "da-compression", compress(skip))]
124    pub(crate) receipts_root: Bytes32,
125    pub(crate) script: ScriptCode,
126    #[educe(Debug(method(fmt_truncated_hex::<16>)))]
127    pub(crate) script_data: Vec<u8>,
128}
129
130impl Default for ScriptBody {
131    fn default() -> Self {
132        // Create a valid transaction with a single return instruction
133        //
134        // The Return op is mandatory for the execution of any context
135        let script = fuel_asm::op::ret(0x10).to_bytes().to_vec();
136
137        Self {
138            script_gas_limit: Default::default(),
139            receipts_root: Default::default(),
140            script: script.into(),
141            script_data: Default::default(),
142        }
143    }
144}
145
146impl PrepareSign for ScriptBody {
147    fn prepare_sign(&mut self) {
148        // Prepare script for execution by clearing malleable fields.
149        self.receipts_root = Default::default();
150    }
151}
152
153impl Chargeable for Script {
154    #[inline(always)]
155    fn max_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word {
156        // The basic implementation of the `max_gas` + `gas_limit`.
157        let remaining_allowed_witness = self
158            .witness_limit()
159            .saturating_sub(self.witnesses().size_dynamic() as u64)
160            .saturating_mul(fee.gas_per_byte());
161
162        self.min_gas(gas_costs, fee)
163            .saturating_add(remaining_allowed_witness)
164            .saturating_add(self.body.script_gas_limit)
165    }
166
167    #[inline(always)]
168    fn metered_bytes_size(&self) -> usize {
169        Serialize::size(self)
170    }
171
172    #[inline(always)]
173    fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
174        let bytes = Serialize::size(self);
175        // Gas required to calculate the `tx_id`.
176        gas_cost.s256().resolve(bytes as u64)
177    }
178}
179
180impl UniqueFormatValidityChecks for Script {
181    fn check_unique_rules(
182        &self,
183        consensus_params: &ConsensusParameters,
184    ) -> Result<(), ValidityError> {
185        let script_params = consensus_params.script_params();
186        if self.body.script.len() as u64 > script_params.max_script_length() {
187            Err(ValidityError::TransactionScriptLength)?;
188        }
189
190        if self.body.script_data.len() as u64 > script_params.max_script_data_length() {
191            Err(ValidityError::TransactionScriptDataLength)?;
192        }
193
194        self.outputs
195            .iter()
196            .enumerate()
197            .try_for_each(|(index, output)| match output {
198                Output::ContractCreated { .. } => {
199                    Err(ValidityError::TransactionOutputContainsContractCreated { index })
200                }
201                _ => Ok(()),
202            })?;
203
204        Ok(())
205    }
206}
207
208impl crate::Cacheable for Script {
209    fn is_computed(&self) -> bool {
210        self.metadata.is_some()
211    }
212
213    fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
214        self.metadata = None;
215        self.metadata = Some(ChargeableMetadata {
216            common: CommonMetadata::compute(self, chain_id)?,
217            body: ScriptMetadata {
218                script_data_offset: self.script_data_offset(),
219            },
220        });
221        Ok(())
222    }
223}
224
225mod field {
226    use super::*;
227    use crate::field::ChargeableBody;
228
229    impl ScriptGasLimit for Script {
230        #[inline(always)]
231        fn script_gas_limit(&self) -> &Word {
232            &self.body.script_gas_limit
233        }
234
235        #[inline(always)]
236        fn script_gas_limit_mut(&mut self) -> &mut Word {
237            &mut self.body.script_gas_limit
238        }
239
240        #[inline(always)]
241        fn script_gas_limit_offset_static() -> usize {
242            WORD_SIZE // `Transaction` enum discriminant
243        }
244    }
245
246    impl ReceiptsRoot for Script {
247        #[inline(always)]
248        fn receipts_root(&self) -> &Bytes32 {
249            &self.body.receipts_root
250        }
251
252        #[inline(always)]
253        fn receipts_root_mut(&mut self) -> &mut Bytes32 {
254            &mut self.body.receipts_root
255        }
256
257        #[inline(always)]
258        fn receipts_root_offset_static() -> usize {
259            Self::script_gas_limit_offset_static().saturating_add(WORD_SIZE)
260        }
261    }
262
263    impl ScriptField for Script {
264        #[inline(always)]
265        fn script(&self) -> &Vec<u8> {
266            &self.body.script
267        }
268
269        #[inline(always)]
270        fn script_mut(&mut self) -> &mut Vec<u8> {
271            &mut self.body.script
272        }
273
274        #[inline(always)]
275        fn script_offset_static() -> usize {
276            Self::receipts_root_offset_static().saturating_add(
277                Bytes32::LEN // Receipts root
278                + WORD_SIZE // Script size
279                + WORD_SIZE // Script data size
280                + WORD_SIZE // Policies size
281                + WORD_SIZE // Inputs size
282                + WORD_SIZE // Outputs size
283                + WORD_SIZE, // Witnesses size
284            )
285        }
286    }
287
288    impl ScriptData for Script {
289        #[inline(always)]
290        fn script_data(&self) -> &Vec<u8> {
291            &self.body.script_data
292        }
293
294        #[inline(always)]
295        fn script_data_mut(&mut self) -> &mut Vec<u8> {
296            &mut self.body.script_data
297        }
298
299        #[inline(always)]
300        fn script_data_offset(&self) -> usize {
301            if let Some(ChargeableMetadata { body, .. }) = &self.metadata {
302                return body.script_data_offset;
303            }
304
305            self.script_offset().saturating_add(
306                bytes::padded_len(self.body.script.as_slice()).unwrap_or(usize::MAX),
307            )
308        }
309    }
310
311    impl ChargeableBody<ScriptBody> for Script {
312        fn body(&self) -> &ScriptBody {
313            &self.body
314        }
315
316        fn body_mut(&mut self) -> &mut ScriptBody {
317            &mut self.body
318        }
319
320        fn body_offset_end(&self) -> usize {
321            self.script_data_offset().saturating_add(
322                bytes::padded_len(self.body.script_data.as_slice()).unwrap_or(usize::MAX),
323            )
324        }
325    }
326}