fuel_tx/transaction/types/
upgrade.rs1use crate::{
2 transaction::{
3 id::PrepareSign,
4 metadata::CommonMetadata,
5 types::chargeable_transaction::{
6 ChargeableMetadata,
7 ChargeableTransaction,
8 UniqueFormatValidityChecks,
9 },
10 Chargeable,
11 },
12 ConsensusParameters,
13 GasCosts,
14 Input,
15 Output,
16 TransactionRepr,
17 ValidityError,
18};
19use educe::Educe;
20use fuel_types::{
21 bytes::WORD_SIZE,
22 canonical::Serialize,
23 Bytes32,
24 ChainId,
25 Word,
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 if metadata.body != calculated_metadata {
210 return Err(ValidityError::TransactionMetadataMismatch);
211 }
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 if asset_id != consensus_params.base_asset_id() {
221 return Err(
222 ValidityError::TransactionInputContainsNonBaseAssetId {
223 index,
224 },
225 );
226 }
227 }
228
229 match input {
230 Input::Contract(_) => {
231 Err(ValidityError::TransactionInputContainsContract { index })
232 }
233 Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => {
234 Err(ValidityError::TransactionInputContainsMessageData { index })
235 }
236 _ => Ok(()),
237 }
238 })?;
239
240 self.outputs
242 .iter()
243 .enumerate()
244 .try_for_each(|(index, output)| match output {
245 Output::Contract(_) => {
246 Err(ValidityError::TransactionOutputContainsContract { index })
247 }
248
249 Output::Variable { .. } => {
250 Err(ValidityError::TransactionOutputContainsVariable { index })
251 }
252
253 Output::Change { asset_id, .. }
254 if asset_id != consensus_params.base_asset_id() =>
255 {
256 Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { index })
257 }
258
259 Output::ContractCreated { .. } => {
260 Err(ValidityError::TransactionOutputContainsContractCreated { index })
261 }
262 _ => Ok(()),
263 })?;
264
265 Ok(())
266 }
267}
268
269impl crate::Cacheable for Upgrade {
270 fn is_computed(&self) -> bool {
271 self.metadata.is_some()
272 }
273
274 fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
275 self.metadata = None;
276 self.metadata = Some(ChargeableMetadata {
277 common: CommonMetadata::compute(self, chain_id)?,
278 body: UpgradeMetadata::compute(self)?,
279 });
280 Ok(())
281 }
282}
283
284mod field {
285 use super::*;
286 use crate::field::{
287 ChargeableBody,
288 UpgradePurpose as UpgradePurposeTrait,
289 };
290
291 impl UpgradePurposeTrait for Upgrade {
292 #[inline(always)]
293 fn upgrade_purpose(&self) -> &UpgradePurpose {
294 &self.body.purpose
295 }
296
297 #[inline(always)]
298 fn upgrade_purpose_mut(&mut self) -> &mut UpgradePurpose {
299 &mut self.body.purpose
300 }
301
302 #[inline(always)]
303 fn upgrade_purpose_offset_static() -> usize {
304 WORD_SIZE }
306 }
307
308 impl ChargeableBody<UpgradeBody> for Upgrade {
309 fn body(&self) -> &UpgradeBody {
310 &self.body
311 }
312
313 fn body_mut(&mut self) -> &mut UpgradeBody {
314 &mut self.body
315 }
316
317 fn body_offset_end(&self) -> usize {
318 Self::upgrade_purpose_offset_static()
319 .saturating_add(self.body.purpose.size())
320 .saturating_add(
321 WORD_SIZE + WORD_SIZE + WORD_SIZE + WORD_SIZE, )
326 }
327 }
328}