1#![cfg_attr(not(feature = "std"), no_std)]
12
13#[cfg(test)]
14mod mock;
15#[cfg(test)]
16mod tests;
17pub mod types;
18
19use self::types::{OutPoint, Transaction, Txid};
20use frame_support::dispatch::DispatchResult;
21use frame_support::weights::Weight;
22use sp_runtime::SaturatedConversion;
23use sp_runtime::traits::BlockNumberProvider;
24use sp_std::prelude::*;
25use sp_std::vec::Vec;
26use subcoin_runtime_primitives::Coin;
27
28pub use pallet::*;
30
31pub type OutputIndex = u32;
33
34pub trait WeightInfo {
36 fn transact(btc_tx: &Transaction) -> Weight;
38}
39
40impl WeightInfo for () {
41 fn transact(_: &Transaction) -> Weight {
42 Weight::zero()
43 }
44}
45
46pub struct BitcoinTransactionWeight;
48
49impl WeightInfo for BitcoinTransactionWeight {
50 fn transact(btc_tx: &Transaction) -> Weight {
51 let btc_weight = bitcoin::Transaction::from(btc_tx.clone()).weight().to_wu();
52 Weight::from_parts(btc_weight, 0u64)
53 }
54}
55
56#[frame_support::pallet]
57pub mod pallet {
58 use super::*;
59 use frame_support::pallet_prelude::*;
60 use frame_system::pallet_prelude::*;
61
62 #[pallet::config]
63 pub trait Config: frame_system::Config {
64 type WeightInfo: WeightInfo;
65 }
66
67 #[pallet::pallet]
68 pub struct Pallet<T>(_);
69
70 #[pallet::call(weight(<T as Config>::WeightInfo))]
71 impl<T: Config> Pallet<T> {
72 #[pallet::call_index(0)]
74 #[pallet::weight((T::WeightInfo::transact(btc_tx), DispatchClass::Normal, Pays::No))]
75 pub fn transact(origin: OriginFor<T>, btc_tx: Transaction) -> DispatchResult {
76 ensure_none(origin)?;
77
78 Self::process_bitcoin_transaction(btc_tx.into());
79
80 Ok(())
81 }
82 }
83
84 #[pallet::event]
85 pub enum Event<T: Config> {}
86
87 #[pallet::storage]
93 pub type Coins<T> =
94 StorageDoubleMap<_, Identity, Txid, Identity, OutputIndex, Coin, OptionQuery>;
95
96 #[pallet::storage]
98 pub type CoinsCount<T> = StorageValue<_, u64, ValueQuery>;
99}
100
101pub fn coin_storage_key<T: Config>(bitcoin_txid: bitcoin::Txid, vout: OutputIndex) -> Vec<u8> {
103 use frame_support::storage::generator::StorageDoubleMap;
104
105 let txid = Txid::from_bitcoin_txid(bitcoin_txid);
106 Coins::<T>::storage_double_map_final_key(txid, vout)
107}
108
109pub fn coin_storage_prefix<T: Config>() -> [u8; 32] {
111 use frame_support::StoragePrefixedMap;
112
113 Coins::<T>::final_prefix()
114}
115
116impl<T: Config> Pallet<T> {
117 pub fn coins_count() -> u64 {
118 CoinsCount::<T>::get()
119 }
120
121 fn process_bitcoin_transaction(tx: bitcoin::Transaction) {
122 let txid = tx.compute_txid();
123 let is_coinbase = tx.is_coinbase();
124 let height = frame_system::Pallet::<T>::current_block_number();
125
126 const BIP30_EXCEPTION_HEIGHTS: &[u32] = &[91722, 91812];
128 if is_coinbase && BIP30_EXCEPTION_HEIGHTS.contains(&height.saturated_into()) {
129 return;
131 }
132
133 let new_coins: Vec<_> = tx
135 .output
136 .into_iter()
137 .enumerate()
138 .filter_map(|(index, txout)| {
139 let out_point = bitcoin::OutPoint {
140 txid,
141 vout: index as u32,
142 };
143
144 if txout.script_pubkey.is_op_return() {
148 None
149 } else {
150 let coin = Coin {
151 is_coinbase,
152 amount: txout.value.to_sat(),
153 script_pubkey: txout.script_pubkey.into_bytes(),
154 height: height.saturated_into(),
155 };
156 Some((out_point, coin))
157 }
158 })
159 .collect();
160
161 let num_created = new_coins.len();
162
163 if is_coinbase {
164 for (out_point, coin) in new_coins {
166 let OutPoint { txid, output_index } = OutPoint::from(out_point);
167 Coins::<T>::insert(txid, output_index, coin);
168 }
169 CoinsCount::<T>::mutate(|v| {
170 *v += num_created as u64;
171 });
172 return;
173 }
174
175 let num_consumed = tx.input.len();
176
177 for input in tx.input {
179 let previous_output = input.previous_output;
180 let OutPoint { txid, output_index } = OutPoint::from(previous_output);
181 if let Some(_spent) = Coins::<T>::take(txid, output_index) {
182 } else {
183 panic!("Corruputed state, UTXO {previous_output:?} not found");
184 }
185 }
186
187 for (out_point, coin) in new_coins {
189 let OutPoint { txid, output_index } = OutPoint::from(out_point);
190 Coins::<T>::insert(txid, output_index, coin);
191 }
192
193 CoinsCount::<T>::mutate(|v| {
194 *v = *v + num_created as u64 - num_consumed as u64;
195 });
196 }
197}