fuel_core_chain_config/config/
coin.rs

1use crate::GenesisCommitment;
2use fuel_core_storage::{
3    tables::Coins,
4    MerkleRoot,
5};
6use fuel_core_types::{
7    entities::coins::coin::{
8        Coin,
9        CompressedCoin,
10        CompressedCoinV1,
11    },
12    fuel_crypto::Hasher,
13    fuel_tx::{
14        TxPointer,
15        UtxoId,
16    },
17    fuel_types::{
18        Address,
19        AssetId,
20        BlockHeight,
21        Bytes32,
22    },
23};
24use serde::{
25    Deserialize,
26    Serialize,
27};
28
29use super::table_entry::TableEntry;
30
31#[derive(Default, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
32pub struct CoinConfig {
33    /// auto-generated if None
34    pub tx_id: Bytes32,
35    pub output_index: u16,
36    /// used if coin is forked from another chain to preserve id & tx_pointer
37    pub tx_pointer_block_height: BlockHeight,
38    /// used if coin is forked from another chain to preserve id & tx_pointer
39    /// The index of the originating tx within `tx_pointer_block_height`
40    pub tx_pointer_tx_idx: u16,
41    pub owner: Address,
42    pub amount: u64,
43    pub asset_id: AssetId,
44}
45
46impl From<TableEntry<Coins>> for CoinConfig {
47    fn from(value: TableEntry<Coins>) -> Self {
48        CoinConfig {
49            tx_id: *value.key.tx_id(),
50            output_index: value.key.output_index(),
51            tx_pointer_block_height: value.value.tx_pointer().block_height(),
52            tx_pointer_tx_idx: value.value.tx_pointer().tx_index(),
53            owner: *value.value.owner(),
54            amount: *value.value.amount(),
55            asset_id: *value.value.asset_id(),
56        }
57    }
58}
59
60impl From<CoinConfig> for TableEntry<Coins> {
61    fn from(config: CoinConfig) -> Self {
62        Self {
63            key: UtxoId::new(config.tx_id, config.output_index),
64            value: CompressedCoin::V1(CompressedCoinV1 {
65                owner: config.owner,
66                amount: config.amount,
67                asset_id: config.asset_id,
68                tx_pointer: TxPointer::new(
69                    config.tx_pointer_block_height,
70                    config.tx_pointer_tx_idx,
71                ),
72            }),
73        }
74    }
75}
76
77impl CoinConfig {
78    pub fn utxo_id(&self) -> UtxoId {
79        UtxoId::new(self.tx_id, self.output_index)
80    }
81
82    pub fn tx_pointer(&self) -> TxPointer {
83        TxPointer::new(self.tx_pointer_block_height, self.tx_pointer_tx_idx)
84    }
85}
86
87#[cfg(feature = "test-helpers")]
88impl crate::Randomize for CoinConfig {
89    fn randomize(mut rng: impl ::rand::Rng) -> Self {
90        Self {
91            tx_id: crate::Randomize::randomize(&mut rng),
92            output_index: rng.gen(),
93            tx_pointer_block_height: rng.gen(),
94            tx_pointer_tx_idx: rng.gen(),
95            owner: crate::Randomize::randomize(&mut rng),
96            amount: rng.gen(),
97            asset_id: crate::Randomize::randomize(&mut rng),
98        }
99    }
100}
101
102impl GenesisCommitment for Coin {
103    fn root(&self) -> anyhow::Result<MerkleRoot> {
104        let owner = self.owner;
105        let amount = self.amount;
106        let asset_id = self.asset_id;
107        let tx_pointer = self.tx_pointer;
108        let utxo_id = self.utxo_id;
109
110        let coin_hash = *Hasher::default()
111            .chain(owner)
112            .chain(amount.to_be_bytes())
113            .chain(asset_id)
114            .chain(tx_pointer.block_height().to_be_bytes())
115            .chain(tx_pointer.tx_index().to_be_bytes())
116            .chain(utxo_id.tx_id())
117            .chain(utxo_id.output_index().to_be_bytes())
118            .finalize();
119
120        Ok(coin_hash)
121    }
122}
123
124#[cfg(feature = "test-helpers")]
125pub mod coin_config_helpers {
126    use crate::CoinConfig;
127    use fuel_core_types::{
128        fuel_types::{
129            Address,
130            Bytes32,
131        },
132        fuel_vm::SecretKey,
133    };
134
135    type CoinCount = u16;
136
137    /// Generates a new coin config with a unique utxo id for testing
138    #[derive(Default, Debug)]
139    pub struct CoinConfigGenerator {
140        count: CoinCount,
141    }
142
143    pub fn tx_id(count: CoinCount) -> Bytes32 {
144        let mut bytes = [0u8; 32];
145        bytes[..size_of::<CoinCount>()].copy_from_slice(&count.to_be_bytes());
146        bytes.into()
147    }
148
149    impl CoinConfigGenerator {
150        pub fn new() -> Self {
151            Self { count: 0 }
152        }
153
154        pub fn generate(&mut self) -> CoinConfig {
155            let tx_id = tx_id(self.count);
156
157            let config = CoinConfig {
158                tx_id,
159                output_index: self.count,
160                ..Default::default()
161            };
162
163            self.count = self.count.checked_add(1).expect("Max coin count reached");
164
165            config
166        }
167
168        pub fn generate_with(&mut self, secret: SecretKey, amount: u64) -> CoinConfig {
169            let owner = Address::from(*secret.public_key().hash());
170
171            CoinConfig {
172                amount,
173                owner,
174                ..self.generate()
175            }
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use fuel_core_types::{
184        fuel_types::Address,
185        fuel_vm::SecretKey,
186    };
187
188    #[test]
189    fn test_generate_unique_utxo_id() {
190        let mut generator = coin_config_helpers::CoinConfigGenerator::new();
191        let config1 = generator.generate();
192        let config2 = generator.generate();
193
194        assert_ne!(config1.utxo_id(), config2.utxo_id());
195    }
196
197    #[test]
198    fn test_generate_with_owner_and_amount() {
199        let mut rng = rand::thread_rng();
200        let secret = SecretKey::random(&mut rng);
201        let amount = 1000;
202
203        let mut generator = coin_config_helpers::CoinConfigGenerator::new();
204        let config = generator.generate_with(secret, amount);
205
206        assert_eq!(config.owner, Address::from(*secret.public_key().hash()));
207        assert_eq!(config.amount, amount);
208    }
209}