fuel_tx/transaction/types/
script.rs

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