fuel_tx/transaction/types/
mint.rs

1use crate::{
2    input,
3    output,
4    transaction::{
5        field::TxPointer as TxPointerField,
6        validity::{
7            check_size,
8            FormatValidityChecks,
9        },
10    },
11    ConsensusParameters,
12    TransactionRepr,
13    TxPointer,
14    ValidityError,
15};
16use educe::Educe;
17use fuel_asm::Word;
18use fuel_types::{
19    bytes::WORD_SIZE,
20    AssetId,
21    BlockHeight,
22    Bytes32,
23};
24
25use fuel_types::ChainId;
26
27use fuel_types::canonical::Serialize;
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub(crate) struct MintMetadata {
31    pub id: Bytes32,
32}
33
34impl MintMetadata {
35    fn compute<Tx>(tx: &Tx, chain_id: &ChainId) -> Self
36    where
37        Tx: crate::UniqueIdentifier,
38    {
39        let id = tx.id(chain_id);
40
41        Self { id }
42    }
43}
44
45/// The definition of the `Mint` transaction from the specification:
46/// <https://github.com/FuelLabs/fuel-specs/blob/master/src/tx-format/transaction.md#transactionmint>
47///
48/// This transaction can be created by the block producer and included in the block only
49/// by it.
50#[derive(Default, Debug, Clone, Educe, serde::Serialize, serde::Deserialize)]
51#[cfg_attr(feature = "da-compression", derive(fuel_compression::Compress))]
52#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)]
53#[canonical(prefix = TransactionRepr::Mint)]
54#[educe(Eq, PartialEq, Hash)]
55pub struct Mint {
56    /// The location of the transaction in the block.
57    #[cfg_attr(feature = "da-compression", compress(skip))]
58    pub(crate) tx_pointer: TxPointer,
59    /// The `Input::Contract` that assets are minted to.
60    pub(crate) input_contract: input::contract::Contract,
61    /// The `Output::Contract` that assets are being minted to.
62    pub(crate) output_contract: output::contract::Contract,
63    /// The amount of funds minted.
64    pub(crate) mint_amount: Word,
65    /// The asset IDs corresponding to the minted amount.
66    pub(crate) mint_asset_id: AssetId,
67    /// Gas Price used for current block
68    pub(crate) gas_price: Word,
69    #[serde(skip)]
70    #[educe(PartialEq(ignore))]
71    #[educe(Hash(ignore))]
72    #[canonical(skip)]
73    #[cfg_attr(feature = "da-compression", compress(skip))]
74    pub(crate) metadata: Option<MintMetadata>,
75}
76
77impl crate::UniqueIdentifier for Mint {
78    fn id(&self, chain_id: &ChainId) -> Bytes32 {
79        if let Some(id) = self.cached_id() {
80            return id;
81        }
82
83        let mut clone = self.clone();
84        clone.input_contract.prepare_sign();
85        clone.output_contract.prepare_sign();
86
87        crate::transaction::compute_transaction_id(chain_id, &mut clone)
88    }
89
90    fn cached_id(&self) -> Option<Bytes32> {
91        self.metadata.as_ref().map(|m| m.id)
92    }
93}
94
95impl FormatValidityChecks for Mint {
96    fn check_signatures(&self, _: &ChainId) -> Result<(), ValidityError> {
97        Ok(())
98    }
99
100    fn check_without_signatures(
101        &self,
102        block_height: BlockHeight,
103        consensus_params: &ConsensusParameters,
104    ) -> Result<(), ValidityError> {
105        check_size(self, consensus_params.tx_params())?;
106
107        if self.tx_pointer().block_height() != block_height {
108            return Err(ValidityError::TransactionMintIncorrectBlockHeight);
109        }
110
111        if self.output_contract.input_index != 0 {
112            return Err(ValidityError::TransactionMintIncorrectOutputIndex);
113        }
114
115        // It is temporary check until https://github.com/FuelLabs/fuel-core/issues/1205
116        if &self.mint_asset_id != consensus_params.base_asset_id() {
117            return Err(ValidityError::TransactionMintNonBaseAsset);
118        }
119
120        Ok(())
121    }
122}
123
124impl crate::Cacheable for Mint {
125    fn is_computed(&self) -> bool {
126        self.metadata.is_some()
127    }
128
129    fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> {
130        self.metadata = None;
131        self.metadata = Some(MintMetadata::compute(self, chain_id));
132        Ok(())
133    }
134}
135
136#[cfg(any(test, feature = "test-helpers"))]
137impl Mint {
138    // This is a function to clear malleable fields just like it
139    // does on other transactions types. Mint never needs this,
140    // but we use it for some tests.
141    pub fn prepare_sign(&mut self) {
142        self.input_contract.prepare_sign();
143        self.output_contract.prepare_sign();
144    }
145}
146
147mod field {
148    use super::*;
149    use crate::field::{
150        InputContract,
151        MintAmount,
152        MintAssetId,
153        MintGasPrice,
154        OutputContract,
155    };
156
157    impl TxPointerField for Mint {
158        #[inline(always)]
159        fn tx_pointer(&self) -> &TxPointer {
160            &self.tx_pointer
161        }
162
163        #[inline(always)]
164        fn tx_pointer_mut(&mut self) -> &mut TxPointer {
165            &mut self.tx_pointer
166        }
167
168        #[inline(always)]
169        fn tx_pointer_static() -> usize {
170            WORD_SIZE // `Transaction` enum discriminant
171        }
172    }
173
174    impl InputContract for Mint {
175        #[inline(always)]
176        fn input_contract(&self) -> &input::contract::Contract {
177            &self.input_contract
178        }
179
180        #[inline(always)]
181        fn input_contract_mut(&mut self) -> &mut input::contract::Contract {
182            &mut self.input_contract
183        }
184
185        #[inline(always)]
186        fn input_contract_offset(&self) -> usize {
187            Self::tx_pointer_static().saturating_add(TxPointer::LEN)
188        }
189    }
190
191    impl OutputContract for Mint {
192        #[inline(always)]
193        fn output_contract(&self) -> &output::contract::Contract {
194            &self.output_contract
195        }
196
197        #[inline(always)]
198        fn output_contract_mut(&mut self) -> &mut output::contract::Contract {
199            &mut self.output_contract
200        }
201
202        #[inline(always)]
203        fn output_contract_offset(&self) -> usize {
204            self.input_contract_offset()
205                .saturating_add(self.input_contract.size())
206        }
207    }
208
209    impl MintAmount for Mint {
210        #[inline(always)]
211        fn mint_amount(&self) -> &fuel_types::Word {
212            &self.mint_amount
213        }
214
215        #[inline(always)]
216        fn mint_amount_mut(&mut self) -> &mut fuel_types::Word {
217            &mut self.mint_amount
218        }
219
220        #[inline(always)]
221        fn mint_amount_offset(&self) -> usize {
222            self.output_contract_offset()
223                .saturating_add(self.output_contract.size())
224        }
225    }
226
227    impl MintAssetId for Mint {
228        #[inline(always)]
229        fn mint_asset_id(&self) -> &AssetId {
230            &self.mint_asset_id
231        }
232
233        #[inline(always)]
234        fn mint_asset_id_mut(&mut self) -> &mut AssetId {
235            &mut self.mint_asset_id
236        }
237
238        #[inline(always)]
239        fn mint_asset_id_offset(&self) -> usize {
240            self.mint_amount_offset().saturating_add(WORD_SIZE)
241        }
242    }
243
244    impl MintGasPrice for Mint {
245        #[inline(always)]
246        fn gas_price(&self) -> &Word {
247            &self.gas_price
248        }
249
250        #[inline(always)]
251        fn gas_price_mut(&mut self) -> &mut Word {
252            &mut self.gas_price
253        }
254
255        #[inline(always)]
256        fn gas_price_offset(&self) -> usize {
257            self.mint_asset_id_offset().saturating_add(AssetId::LEN)
258        }
259    }
260}