fuel_core_chain_config/config/
coin.rs1use 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 pub tx_id: Bytes32,
35 pub output_index: u16,
36 pub tx_pointer_block_height: BlockHeight,
38 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 #[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}