fuel_tx/transaction/types/
script.rs1use 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 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 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 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_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 }
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 + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
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}