fuel_tx/transaction/types/
upgrade.rs1use crate::{
2 ConsensusParameters,
3 GasCosts,
4 Input,
5 Output,
6 TransactionRepr,
7 ValidityError,
8 transaction::{
9 Chargeable,
10 id::PrepareSign,
11 metadata::CommonMetadata,
12 types::chargeable_transaction::{
13 ChargeableMetadata,
14 ChargeableTransaction,
15 UniqueFormatValidityChecks,
16 },
17 },
18};
19use educe::Educe;
20use fuel_types::{
21 Bytes32,
22 ChainId,
23 Word,
24 bytes::WORD_SIZE,
25 canonical::Serialize,
26};
27
28use fuel_crypto::Hasher;
29
30#[cfg(feature = "alloc")]
31use alloc::boxed::Box;
32
33pub type Upgrade = ChargeableTransaction<UpgradeBody, UpgradeMetadata>;
34
35#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
36pub enum UpgradeMetadata {
37 ConsensusParameters {
39 consensus_parameters: Box<ConsensusParameters>,
41 calculated_checksum: Bytes32,
43 },
44 #[default]
46 StateTransition,
47}
48
49impl UpgradeMetadata {
50 pub fn compute(tx: &Upgrade) -> Result<Self, ValidityError> {
51 match &tx.body.purpose {
52 UpgradePurpose::ConsensusParameters {
53 witness_index,
54 checksum,
55 } => {
56 let index = *witness_index as usize;
57 let witness = tx
58 .witnesses
59 .get(index)
60 .ok_or(ValidityError::InputWitnessIndexBounds { index })?;
61
62 let serialized_consensus_parameters = witness.as_vec();
63 let actual_checksum = Hasher::hash(serialized_consensus_parameters);
64
65 if &actual_checksum != checksum {
66 Err(ValidityError::TransactionUpgradeConsensusParametersChecksumMismatch)?;
67 }
68
69 let consensus_parameters = postcard::from_bytes::<ConsensusParameters>(
76 serialized_consensus_parameters,
77 )
78 .map_err(|_| {
79 ValidityError::TransactionUpgradeConsensusParametersDeserialization
80 })?;
81
82 Ok(Self::ConsensusParameters {
83 consensus_parameters: Box::new(consensus_parameters),
84 calculated_checksum: actual_checksum,
85 })
86 }
87 UpgradePurpose::StateTransition { .. } => {
88 Ok(Self::StateTransition)
90 }
91 }
92 }
93}
94
95#[derive(
98 Copy, Clone, Educe, strum_macros::EnumCount, serde::Serialize, serde::Deserialize,
99)]
100#[cfg_attr(
101 feature = "da-compression",
102 derive(fuel_compression::Compress, fuel_compression::Decompress)
103)]
104#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
105#[educe(Eq, PartialEq, Hash, Debug)]
106pub enum UpgradePurpose {
107 ConsensusParameters {
109 witness_index: u16,
112 checksum: Bytes32,
117 },
118 StateTransition {
120 root: Bytes32,
124 },
125}
126
127#[derive(Clone, Educe, serde::Serialize, serde::Deserialize)]
129#[cfg_attr(
130 feature = "da-compression",
131 derive(fuel_compression::Compress, fuel_compression::Decompress)
132)]
133#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
134#[canonical(prefix = TransactionRepr::Upgrade)]
135#[educe(Eq, PartialEq, Hash, Debug)]
136pub struct UpgradeBody {
137 pub(crate) purpose: UpgradePurpose,
139}
140
141impl Default for UpgradeBody {
142 fn default() -> Self {
143 Self {
144 purpose: UpgradePurpose::StateTransition {
145 root: Default::default(),
146 },
147 }
148 }
149}
150
151impl PrepareSign for UpgradeBody {
152 fn prepare_sign(&mut self) {}
153}
154
155impl Chargeable for Upgrade {
156 #[inline(always)]
157 fn metered_bytes_size(&self) -> usize {
158 Serialize::size(self)
159 }
160
161 #[inline(always)]
162 fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word {
163 let bytes = Serialize::size(self);
164 let tx_id_gas = gas_cost.s256().resolve(bytes as u64);
166
167 let purpose_gas = match &self.body.purpose {
168 UpgradePurpose::ConsensusParameters { witness_index, .. } => {
169 let len = self
170 .witnesses
171 .get(*witness_index as usize)
172 .map_or(0, |w| w.as_vec().len());
173 gas_cost.s256().resolve(len as u64)
174 }
175 UpgradePurpose::StateTransition { .. } => {
176 0
180 }
181 };
182
183 tx_id_gas.saturating_add(purpose_gas)
184 }
185}
186
187impl UniqueFormatValidityChecks for Upgrade {
188 fn check_unique_rules(
189 &self,
190 consensus_params: &ConsensusParameters,
191 ) -> Result<(), ValidityError> {
192 self.inputs
194 .iter()
195 .find(|input| {
196 if let Some(owner) = input.input_owner() {
197 owner == consensus_params.privileged_address()
198 } else {
199 false
200 }
201 })
202 .ok_or(ValidityError::TransactionUpgradeNoPrivilegedAddress)?;
203
204 let calculated_metadata = UpgradeMetadata::compute(self)?;
207
208 if let Some(metadata) = self.metadata.as_ref()
209 && metadata.body != calculated_metadata
210 {
211 return Err(ValidityError::TransactionMetadataMismatch);
212 }
213
214 self.inputs
216 .iter()
217 .enumerate()
218 .try_for_each(|(index, input)| {
219 if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id())
220 && asset_id != consensus_params.base_asset_id()
221 {
222 return Err(ValidityError::TransactionInputContainsNonBaseAssetId {
223 index,
224 });
225 }
226
227 match input {
228 Input::Contract(_) => {
229 Err(ValidityError::TransactionInputContainsContract { index })
230 }
231 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
232 Err(ValidityError::TransactionInputContainsMessageData { index })
233 }
234 _ => Ok(()),
235 }
236 })?;
237
238 self.outputs
240 .iter()
241 .enumerate()
242 .try_for_each(|(index, output)| match output {
243 Output::Contract(_) => {
244 Err(ValidityError::TransactionOutputContainsContract { index })
245 }
246
247 Output::Variable { .. } => {
248 Err(ValidityError::TransactionOutputContainsVariable { index })
249 }
250
251 Output::Change { asset_id, .. }
252 if asset_id != consensus_params.base_asset_id() =>
253 {
254 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
255 }
256
257 Output::ContractCreated { .. } => {
258 Err(ValidityError::TransactionOutputContainsContractCreated { index })
259 }
260 _ => Ok(()),
261 })?;
262
263 Ok(())
264 }
265}
266
267impl crate::Cacheable for Upgrade {
268 fn is_computed(&self) -> bool {
269 self.metadata.is_some()
270 }
271
272 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
273 self.metadata = None;
274 self.metadata = Some(ChargeableMetadata {
275 common: CommonMetadata::compute(self, chain_id)?,
276 body: UpgradeMetadata::compute(self)?,
277 });
278 Ok(())
279 }
280}
281
282mod field {
283 use super::*;
284 use crate::field::{
285 ChargeableBody,
286 UpgradePurpose as UpgradePurposeTrait,
287 };
288
289 impl UpgradePurposeTrait for Upgrade {
290 #[inline(always)]
291 fn upgrade_purpose(&self) -> &UpgradePurpose {
292 &self.body.purpose
293 }
294
295 #[inline(always)]
296 fn upgrade_purpose_mut(&mut self) -> &mut UpgradePurpose {
297 &mut self.body.purpose
298 }
299
300 #[inline(always)]
301 fn upgrade_purpose_offset_static() -> usize {
302 WORD_SIZE }
304 }
305
306 impl ChargeableBody<UpgradeBody> for Upgrade {
307 fn body(&self) -> &UpgradeBody {
308 &self.body
309 }
310
311 fn body_mut(&mut self) -> &mut UpgradeBody {
312 &mut self.body
313 }
314
315 fn body_offset_end(&self) -> usize {
316 Self::upgrade_purpose_offset_static()
317 .saturating_add(self.body.purpose.size())
318 .saturating_add(
319 WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
324 }
325 }
326}