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