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 = bitcoin_txid.into();
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 pub fn get_utxos(
122 outpoints: Vec<subcoin_runtime_primitives::OutPoint>,
123 ) -> Vec<Option<subcoin_runtime_primitives::Coin>> {
124 outpoints
125 .into_iter()
126 .map(|outpoint| {
127 let txid: Txid = outpoint.txid;
128 Coins::<T>::get(txid, outpoint.vout)
129 })
130 .collect()
131 }
132
133 fn process_bitcoin_transaction(tx: bitcoin::Transaction) {
134 let txid = tx.compute_txid();
135 let is_coinbase = tx.is_coinbase();
136 let height = frame_system::Pallet::<T>::current_block_number();
137
138 const BIP30_EXCEPTION_HEIGHTS: &[u32] = &[91722, 91812];
140 if is_coinbase && BIP30_EXCEPTION_HEIGHTS.contains(&height.saturated_into()) {
141 return;
143 }
144
145 let new_coins: Vec<_> = tx
147 .output
148 .into_iter()
149 .enumerate()
150 .filter_map(|(index, txout)| {
151 let out_point = bitcoin::OutPoint {
152 txid,
153 vout: index as u32,
154 };
155
156 if txout.script_pubkey.is_op_return() {
160 None
161 } else {
162 let coin = Coin {
163 is_coinbase,
164 amount: txout.value.to_sat(),
165 script_pubkey: txout.script_pubkey.into_bytes(),
166 height: height.saturated_into(),
167 };
168 Some((out_point, coin))
169 }
170 })
171 .collect();
172
173 let num_created = new_coins.len();
174
175 if is_coinbase {
176 for (out_point, coin) in new_coins {
178 let OutPoint { txid, vout } = OutPoint::from(out_point);
179 Coins::<T>::insert(txid, vout, coin);
180 }
181 CoinsCount::<T>::mutate(|v| {
182 *v += num_created as u64;
183 });
184 return;
185 }
186
187 let num_consumed = tx.input.len();
188
189 for input in tx.input {
191 let previous_output = input.previous_output;
192 let OutPoint { txid, vout } = OutPoint::from(previous_output);
193 if let Some(_spent) = Coins::<T>::take(txid, vout) {
194 } else {
195 panic!("Corruputed state, UTXO {previous_output:?} not found");
196 }
197 }
198
199 for (out_point, coin) in new_coins {
201 let OutPoint { txid, vout } = OutPoint::from(out_point);
202 Coins::<T>::insert(txid, vout, coin);
203 }
204
205 CoinsCount::<T>::mutate(|v| {
206 *v = *v + num_created as u64 - num_consumed as u64;
207 });
208 }
209}