ree_exchange_sdk/
lib.rs

1//! The REE Exchange SDK provides a set of types and interfaces for building REE exchanges.
2//!
3//! # Example
4//! ```rust
5//! use self::exchange::*;
6//! use candid::CandidType;
7//! use ic_cdk::{query, update};
8//! use ree_exchange_sdk::{prelude::*, types::*};
9//! use serde::{Deserialize, Serialize};
10//!
11//! #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
12//! pub struct DummyPoolState {
13//!     pub txid: Txid,
14//!     pub nonce: u64,
15//!     pub coin_reserved: Vec<CoinBalance>,
16//!     pub btc_reserved: u64,
17//!     pub utxos: Vec<Utxo>,
18//!     pub attributes: String,
19//! }
20//!
21//! impl StateView for DummyPoolState {
22//!     fn inspect_state(&self) -> StateInfo {
23//!         StateInfo {
24//!             txid: self.txid,
25//!             nonce: self.nonce,
26//!             coin_reserved: self.coin_reserved.clone(),
27//!             btc_reserved: self.btc_reserved,
28//!             utxos: self.utxos.clone(),
29//!             attributes: "{}".to_string(),
30//!         }
31//!     }
32//! }
33//!
34//! #[exchange]
35//! pub mod exchange {
36//!     use super::*;
37//!
38//!     #[pools]
39//!     pub struct DummyPools;
40//!
41//!     impl Pools for DummyPools {
42//!         type PoolState = DummyPoolState;
43//!
44//!         type BlockState = u32;
45//!
46//!         const POOL_STATE_MEMORY: u8 = 1;
47//!
48//!         const BLOCK_STATE_MEMORY: u8 = 2;
49//!
50//!         fn network() -> Network {
51//!             Network::Testnet4
52//!         }
53//!     }
54//!
55//!     // This is optional
56//!     #[hook]
57//!     impl Hook for DummyPools {}
58//!
59//!     // `swap` is the action function that will be called by the REE Orchestrator
60//!     // All actions should return an `ActionResult<S>` where `S` is the pool state of `Pools`.
61//!     // The SDK will automatically commit this state to the IC stable memory.
62//!     #[action(name = "swap")]
63//!     pub async fn execute_swap(
64//!         psbt: &bitcoin::Psbt,
65//!         args: ActionArgs,
66//!     ) -> ActionResult<DummyPoolState> {
67//!         let pool = DummyPools::get(&args.intention.pool_address)
68//!             .ok_or_else(|| format!("Pool not found: {}", args.intention.pool_address))?;
69//!         let mut state = pool.last_state().cloned().unwrap_or_default();
70//!         // do some checks...
71//!         state.nonce = state.nonce + 1;
72//!         state.txid = args.txid.clone();
73//!         Ok(state)
74//!     }
75//! }
76//!
77//! #[update]
78//! pub async fn new_pool(name: String) {
79//!     let metadata = Metadata::new::<DummyPools>(name)
80//!         .await
81//!         .expect("Failed to call chain-key API");
82//!     let pool = Pool::new(metadata);
83//!     DummyPools::insert(pool);
84//! }
85//!
86//! #[query]
87//! pub fn pre_swap(addr: String) -> Option<StateInfo> {
88//!     DummyPools::get(&addr).and_then(|pool| pool.last_state().map(|s| s.inspect_state()))
89//! }
90//!
91//! ic_cdk::export_candid!();
92//!```
93
94#[doc(hidden)]
95pub mod schnorr;
96#[doc(hidden)]
97pub mod states;
98pub mod store;
99pub mod prelude {
100    pub use crate::*;
101    pub use ree_exchange_sdk_macro::*;
102}
103
104use crate::types::{
105    CoinBalance, Intention, IntentionSet, Pubkey, TxRecord, Txid, Utxo, exchange_interfaces::*,
106};
107use candid::CandidType;
108use ic_stable_structures::{
109    BTreeMap, DefaultMemoryImpl, Storable, memory_manager::VirtualMemory, storable::Bound,
110};
111use serde::{Deserialize, Serialize};
112
113/// essential types of REE
114pub use ree_types as types;
115
116#[doc(hidden)]
117pub type BlockStateStorage<S> = BTreeMap<u32, GlobalStateWrapper<S>, Memory>;
118#[doc(hidden)]
119pub type Memory = VirtualMemory<DefaultMemoryImpl>;
120#[doc(hidden)]
121pub type BlockStorage = BTreeMap<u32, Block, Memory>;
122#[doc(hidden)]
123pub type UnconfirmedTxStorage = BTreeMap<Txid, TxRecord, Memory>;
124#[doc(hidden)]
125pub type PoolStorage<S> = BTreeMap<String, Pool<S>, Memory>;
126
127/// The network enum defines the networks supported by the exchange.
128#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Copy)]
129pub enum Network {
130    Bitcoin,
131    Testnet4,
132}
133
134impl Into<crate::types::bitcoin::Network> for Network {
135    fn into(self) -> crate::types::bitcoin::Network {
136        match self {
137            Network::Bitcoin => crate::types::bitcoin::Network::Bitcoin,
138            Network::Testnet4 => crate::types::bitcoin::Network::Testnet4,
139        }
140    }
141}
142
143#[doc(hidden)]
144pub fn ensure_access<P: Pools>() -> Result<(), String> {
145    match P::network() {
146        Network::Bitcoin => crate::types::orchestrator_interfaces::ensure_orchestrator(),
147        Network::Testnet4 => crate::types::orchestrator_interfaces::ensure_testnet4_orchestrator(),
148    }
149}
150
151/// The parameters for the hook `on_block_confirmed` and `on_block_finalized`
152#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
153pub struct Block {
154    /// The height of the block just received
155    pub block_height: u32,
156    /// The block hash
157    pub block_hash: String,
158    /// The block timestamp in seconds since the Unix epoch.
159    pub block_timestamp: u64,
160    /// transactions confirmed in this block
161    pub txs: Vec<TxRecord>,
162}
163
164impl Storable for Block {
165    fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
166        let bytes = bincode::serialize(self).unwrap();
167        std::borrow::Cow::Owned(bytes)
168    }
169
170    fn into_bytes(self) -> Vec<u8> {
171        bincode::serialize(&self).unwrap()
172    }
173
174    fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
175        bincode::deserialize(bytes.as_ref()).unwrap()
176    }
177
178    const BOUND: Bound = Bound::Unbounded;
179}
180
181/// The metadata for the pool, which includes the key, name, and address.
182/// Typically, the key and address should be generated by the IC chain-key.
183#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
184pub struct Metadata {
185    pub key: Pubkey,
186    pub key_derivation_path: Vec<Vec<u8>>,
187    pub name: String,
188    pub address: String,
189}
190
191impl Metadata {
192    /// Creates a new metadata instance with the given name. It will automatically generate the key and address.
193    pub async fn new<P: Pools>(name: String) -> Result<Self, String> {
194        let key_derivation_path: Vec<Vec<u8>> = vec![name.clone().into_bytes()];
195        let (key, _, address) =
196            crate::schnorr::request_p2tr_address(key_derivation_path.clone(), P::network())
197                .await
198                .map_err(|e| format!("Failed to generate pool address: {}", e))?;
199        Ok(Self {
200            key,
201            key_derivation_path,
202            name,
203            address: address.to_string(),
204        })
205    }
206}
207
208/// The essential information about the pool state.
209#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
210pub struct StateInfo {
211    pub nonce: u64,
212    pub txid: Txid,
213    pub coin_reserved: Vec<CoinBalance>,
214    pub btc_reserved: u64,
215    pub utxos: Vec<Utxo>,
216    pub attributes: String,
217}
218
219/// The parameter for the action function, which is used to execute a transaction in the exchange.
220#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
221pub struct ActionArgs {
222    pub txid: Txid,
223    pub initiator_address: String,
224    pub intention: Intention,
225    pub other_intentions: Vec<Intention>,
226    pub unconfirmed_tx_count: usize,
227}
228
229impl From<ExecuteTxArgs> for ActionArgs {
230    fn from(args: ExecuteTxArgs) -> Self {
231        let ExecuteTxArgs {
232            psbt_hex: _,
233            txid,
234            intention_set,
235            intention_index,
236            zero_confirmed_tx_queue_length,
237        } = args;
238        let IntentionSet {
239            mut intentions,
240            initiator_address,
241            tx_fee_in_sats: _,
242        } = intention_set;
243        let intention = intentions.swap_remove(intention_index as usize);
244        Self {
245            txid,
246            initiator_address,
247            intention,
248            other_intentions: intentions,
249            unconfirmed_tx_count: zero_confirmed_tx_queue_length as usize,
250        }
251    }
252}
253
254/// The result type for actions in the exchange, which can either be successful with a state or an error message.
255pub type ActionResult<S> = Result<S, String>;
256
257/// User must implement the `StateView` trait for customized state to provide this information.
258pub trait StateView {
259    fn inspect_state(&self) -> StateInfo;
260}
261
262/// The concrete type stored in the IC stable memory.
263/// The SDK will automatically manage the pool state `S`.
264#[derive(Clone, Debug, Deserialize, Serialize)]
265pub struct Pool<S> {
266    metadata: Metadata,
267    states: Vec<S>,
268}
269
270impl<S> Storable for Pool<S>
271where
272    S: Serialize + for<'de> Deserialize<'de>,
273{
274    const BOUND: Bound = Bound::Unbounded;
275
276    fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
277        let bytes = bincode::serialize(self).unwrap();
278        std::borrow::Cow::Owned(bytes)
279    }
280
281    fn into_bytes(self) -> Vec<u8> {
282        bincode::serialize(&self).unwrap()
283    }
284
285    fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
286        bincode::deserialize(bytes.as_ref()).unwrap()
287    }
288}
289
290impl<S> Pool<S>
291where
292    S: StateView,
293{
294    /// Creates a new pool with the given metadata.
295    pub fn new(metadata: Metadata) -> Self {
296        Self {
297            metadata,
298            states: Vec::new(),
299        }
300    }
301
302    /// Returns the metadata of the pool.
303    pub fn metadata(&self) -> &Metadata {
304        &self.metadata
305    }
306
307    /// Returns a reference of the last state of the pool.
308    pub fn last_state(&self) -> Option<&S> {
309        self.states.last()
310    }
311
312    /// Returns the states of the pool.
313    pub fn states(&self) -> &Vec<S> {
314        &self.states
315    }
316
317    /// Return the state matches the given txid.
318    pub fn get(&self, txid: Txid) -> Option<&S> {
319        self.states
320            .iter()
321            .find(|state| state.inspect_state().txid == txid)
322    }
323
324    /// Returns a mutable reference to the states of the pool.
325    pub fn states_mut(&mut self) -> &mut Vec<S> {
326        &mut self.states
327    }
328}
329
330#[doc(hidden)]
331pub trait ReePool<S> {
332    fn get_pool_info(&self) -> PoolInfo;
333
334    fn get_pool_basic(&self) -> PoolBasic;
335
336    fn rollback(&mut self, txid: Txid) -> Result<Vec<S>, String>;
337
338    fn finalize(&mut self, txid: Txid) -> Result<(), String>;
339}
340
341#[doc(hidden)]
342impl<S> ReePool<S> for Pool<S>
343where
344    S: StateView,
345{
346    fn get_pool_basic(&self) -> PoolBasic {
347        PoolBasic {
348            name: self.metadata.name.clone(),
349            address: self.metadata.address.clone(),
350        }
351    }
352
353    fn get_pool_info(&self) -> PoolInfo {
354        let metadata: Metadata = self.metadata.clone();
355        let Metadata {
356            key,
357            key_derivation_path,
358            name,
359            address,
360        } = metadata;
361        let state = self
362            .states
363            .last()
364            .map(|s| s.inspect_state())
365            .unwrap_or_default();
366        let StateInfo {
367            txid: _,
368            nonce,
369            coin_reserved,
370            btc_reserved,
371            utxos,
372            attributes,
373        } = state;
374        PoolInfo {
375            key,
376            key_derivation_path,
377            name,
378            address,
379            nonce,
380            coin_reserved,
381            btc_reserved,
382            utxos,
383            attributes,
384        }
385    }
386
387    fn rollback(&mut self, txid: Txid) -> Result<Vec<S>, String> {
388        let idx = self
389            .states
390            .iter()
391            .position(|state| state.inspect_state().txid == txid)
392            .ok_or("txid not found".to_string())?;
393
394        let mut rollbacked_states = vec![];
395        while self.states.len() > idx {
396            rollbacked_states.push(self.states.pop().unwrap());
397        }
398
399        Ok(rollbacked_states)
400    }
401
402    fn finalize(&mut self, txid: Txid) -> Result<(), String> {
403        let idx = self
404            .states
405            .iter()
406            .position(|state| state.inspect_state().txid == txid)
407            .ok_or("txid not found".to_string())?;
408        if idx == 0 {
409            return Ok(());
410        }
411        self.states.rotate_left(idx);
412        self.states.truncate(self.states.len() - idx);
413        Ok(())
414    }
415}
416
417/// The Pools trait defines the interface for the exchange pools, must be marked as `#[ree_exchange_sdk::pools]`.
418pub trait Pools {
419    /// The concrete type of the pool state.
420    type PoolState: StateView + Serialize + for<'de> Deserialize<'de>;
421
422    /// The concret type of the block state.
423    type BlockState: Serialize + for<'de> Deserialize<'de>;
424
425    /// The memory ID for the block state storage.
426    const BLOCK_STATE_MEMORY: u8;
427
428    /// The memory ID for the pool state storage.
429    const POOL_STATE_MEMORY: u8;
430
431    /// useful for ensuring that the exchange is running on the correct network.
432    fn network() -> Network;
433
434    /// Returns the state finalize threshold, useful for determining when a transaction is considered finalized.
435    /// For `Testnet4`, it should be great than 60 while in `Bitcoin` it should be ~ 3-6.
436    fn finalize_threshold() -> u32 {
437        60
438    }
439}
440
441/// A hook that can be implemented to respond to block event in the exchange lifecycle.
442/// It must be implemented over the `BlockState` type and marked as `#[ree_exchange_sdk::hook]`.
443pub trait Hook: Pools {
444    /// This function is called when a transaction is rejected and never confirmed.
445    fn on_tx_rollbacked(
446        _address: String,
447        _txid: Txid,
448        _reason: String,
449        _rollbacked_states: Vec<Self::PoolState>,
450    ) {
451    }
452
453    /// This function is called when a transaction is placed in a new block, before the `on_block_confirmed`.
454    fn on_tx_confirmed(_address: String, _txid: Txid, _block: Block) {}
455
456    /// This function is called when a block is received.
457    fn on_block_confirmed(_block: Block) {}
458
459    /// This function is called when a block is received but before any other hooks.
460    fn pre_block_confirmed(_height: u32) {}
461}
462
463/// A trait for accessing the pool storage.
464/// The user-defined `Pools` type will automatically implement this trait.
465pub trait PoolStorageAccess<P: Pools> {
466    fn block_state() -> Option<P::BlockState>;
467
468    fn commit(height: u32, block_state: P::BlockState) -> Result<(), String>;
469
470    fn get(address: &String) -> Option<Pool<P::PoolState>>;
471
472    fn insert(pool: Pool<P::PoolState>);
473
474    fn remove(address: &String) -> Option<Pool<P::PoolState>>;
475
476    fn iter() -> iter::PoolIterator<P>;
477}
478
479#[doc(hidden)]
480#[derive(Clone, Debug, Deserialize, Serialize)]
481pub struct GlobalStateWrapper<S> {
482    pub inner: S,
483}
484
485#[doc(hidden)]
486impl<S> GlobalStateWrapper<S> {
487    pub fn new(s: S) -> Self {
488        Self { inner: s }
489    }
490}
491
492#[doc(hidden)]
493impl<S> Storable for GlobalStateWrapper<S>
494where
495    S: Serialize + for<'de> Deserialize<'de>,
496{
497    const BOUND: Bound = Bound::Unbounded;
498
499    fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
500        let bytes = bincode::serialize(self).unwrap();
501        std::borrow::Cow::Owned(bytes)
502    }
503
504    fn into_bytes(self) -> Vec<u8> {
505        bincode::serialize(&self).unwrap()
506    }
507
508    fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
509        bincode::deserialize(bytes.as_ref()).unwrap()
510    }
511}
512
513/// The Upgrade trait is used to handle state migrations when the state type of a Pools implementation changes.
514/// Assume `MyPools` originally has a pool state type `MyPoolState` and block state type `MyBlockState`.
515///
516/// ```rust
517/// #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
518/// pub struct MyPoolState {
519///     pub txid: Txid,
520///     pub nonce: u64,
521///     pub coin_reserved: Vec<CoinBalance>,
522///     pub btc_reserved: u64,
523///     pub utxos: Vec<Utxo>,
524///     pub attributes: String,
525/// }
526///
527/// #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
528/// pub struct MyBlockState {
529///     pub block_number: u32,
530/// }
531///
532/// impl Pools for MyPools {
533///     type PoolState = MyPoolState;
534///
535///     type BlockState = MyBlockState;
536///
537///     const POOL_STATE_MEMORY: u8 = 1;
538///
539///     const BLOCK_STATE_MEMORY: u8 = 2;
540/// }
541/// ```
542/// Now we would like to update the `MyPoolState` type.
543///
544/// The best practice is to rename the `MyPoolState` to `OldPoolState` and define a new state type `MyPoolState`
545///
546/// ```rust
547/// #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
548/// pub struct OldPoolState {
549///     pub txid: Txid,
550///     pub nonce: u64,
551///     pub coin_reserved: Vec<CoinBalance>,
552///     pub btc_reserved: u64,
553///     pub utxos: Vec<Utxo>,
554///     pub attributes: String,
555/// }
556///
557/// #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
558/// pub struct MyPoolState {
559///     pub txid: Txid,
560///     pub nonce: u64,
561///     pub coin_reserved: Vec<CoinBalance>,
562///     pub btc_reserved: u64,
563///     pub utxos: Vec<Utxo>,
564///     pub attributes: String,
565///     pub new_field: u32,
566/// }
567///
568/// impl Into<MyState> for OldState {
569///     fn into(self) -> MyState {
570///         // ...
571///     }
572/// }
573///
574/// #[upgrade]
575/// impl Upgrade<MyPools> for MyPools {
576///     type PoolState = OldState;
577///
578///     type BlockState = u32;
579///
580///     // there is where we store the pool data before upgrade
581///     const POOL_STATE_MEMORY: u8 = 1;
582///
583///     const BLOCK_STATE_MEMORY: u8 = 2;
584/// }
585///
586/// impl Pools for MyPools {
587///     type PoolState = MyPoolState;
588///
589///     type BlockState = u32;
590///
591///     // this is where we store the pool data after upgrade
592///     const POOL_STATE_MEMORY: u8 = 3;
593///
594///     const BLOCK_STATE_MEMORY: u8 = 4;
595/// }
596///
597/// ```
598/// Now you can call `MyPools::upgrade()` in the `post_upgrade` hook.
599pub trait Upgrade<P: Pools> {
600    /// The previous pool state type before the upgrade.
601    type PoolState: Into<P::PoolState> + for<'de> Deserialize<'de> + Clone;
602
603    /// The previous block state type before the upgrade.
604    type BlockState: Into<P::BlockState> + for<'de> Deserialize<'de> + Clone;
605
606    /// The memory ID for the pool state storage in the previous version.
607    const POOL_STATE_MEMORY: u8;
608
609    /// The memory ID for the block state storage in the previous version.
610    const BLOCK_STATE_MEMORY: u8;
611}
612
613#[doc(hidden)]
614pub fn iterator<P>(memory: Memory) -> iter::PoolIterator<P>
615where
616    P: Pools,
617{
618    let inner = PoolStorage::<P::PoolState>::init(memory);
619    let keys = inner.keys().collect::<Vec<_>>();
620    iter::PoolIterator {
621        inner,
622        cursor: 0,
623        keys,
624    }
625}
626
627#[doc(hidden)]
628pub mod iter {
629    pub struct PoolIterator<P: super::Pools> {
630        pub(crate) inner: super::PoolStorage<P::PoolState>,
631        pub(crate) cursor: usize,
632        pub(crate) keys: Vec<String>,
633    }
634
635    impl<P> std::iter::Iterator for PoolIterator<P>
636    where
637        P: super::Pools,
638    {
639        type Item = (String, super::Pool<P::PoolState>);
640
641        fn next(&mut self) -> Option<Self::Item> {
642            if self.cursor < self.keys.len() {
643                let key = self.keys[self.cursor].clone();
644                self.cursor += 1;
645                self.inner.get(&key).map(|v| (key.clone(), v))
646            } else {
647                None
648            }
649        }
650    }
651}
652
653#[cfg(test)]
654pub mod test {
655    use std::str::FromStr;
656
657    use super::*;
658
659    #[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
660    struct DummyPoolState {
661        nonce: u64,
662        txid: Txid,
663        coin_reserved: Vec<CoinBalance>,
664        btc_reserved: u64,
665        utxos: Vec<Utxo>,
666        attributes: String,
667    }
668
669    impl StateView for DummyPoolState {
670        fn inspect_state(&self) -> StateInfo {
671            StateInfo {
672                txid: self.txid.clone(),
673                nonce: self.nonce,
674                coin_reserved: self.coin_reserved.clone(),
675                btc_reserved: self.btc_reserved,
676                utxos: self.utxos.clone(),
677                attributes: self.attributes.clone(),
678            }
679        }
680    }
681
682    #[test]
683    pub fn test_candid_and_bincode_serialize() {
684        let state = DummyPoolState {
685            nonce: 1,
686            txid: Txid::default(),
687            coin_reserved: vec![],
688            btc_reserved: 0,
689            utxos: vec![],
690            attributes: "{}".to_string(),
691        };
692        let pool = Pool::<DummyPoolState> {
693            metadata: Metadata {
694                key: Pubkey::from_raw(vec![2u8; 33]).unwrap(),
695                key_derivation_path: vec![vec![0; 32]],
696                name: "Test Pool".to_string(),
697                address: "test-address".to_string(),
698            },
699            states: vec![state.clone()],
700        };
701        let bincode_serialized = pool.to_bytes();
702        Pool::<DummyPoolState>::from_bytes(bincode_serialized);
703        assert_eq!(pool.metadata.name, "Test Pool");
704
705        let mut candid_ser = candid::ser::IDLBuilder::new();
706        candid_ser.arg(&state).unwrap();
707        let candid_serialized = candid_ser.serialize_to_vec();
708        assert!(candid_serialized.is_ok());
709        let candid_serialized = candid_serialized.unwrap();
710        let mut candid_de = candid::de::IDLDeserialize::new(&candid_serialized).unwrap();
711        let candid_deserialized = candid_de.get_value::<DummyPoolState>();
712        assert!(candid_deserialized.is_ok());
713    }
714
715    #[test]
716    fn test_pool_rollback() {
717        let mut pool = Pool::<DummyPoolState> {
718            metadata: Metadata {
719                key: Pubkey::from_raw(vec![2u8; 33]).unwrap(),
720                key_derivation_path: vec![vec![0; 32]],
721                name: "Test Pool".to_string(),
722                address: "test-address".to_string(),
723            },
724            states: vec![],
725        };
726        let push_random_state_by_txid = |txid: &str, pool: &mut Pool<DummyPoolState>| {
727            let txid = Txid::from_str(txid).unwrap();
728            let nonce = pool.states.len() as u64;
729            let state = DummyPoolState {
730                nonce,
731                txid,
732                coin_reserved: vec![],
733                btc_reserved: 0,
734                utxos: vec![],
735                attributes: "{}".to_string(),
736            };
737            pool.states.push(state);
738        };
739
740        let txs = [
741            "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a",
742            "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2b",
743            "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2c",
744        ];
745
746        let init_pool_state = |pool: &mut Pool<DummyPoolState>| {
747            pool.states.clear();
748            for txid in txs.iter() {
749                push_random_state_by_txid(txid, pool);
750            }
751        };
752
753        // test rollback first tx
754        init_pool_state(&mut pool);
755        assert_eq!(pool.states.len(), 3);
756        let before_rollback_states = pool.states.clone();
757        let rollbacked_states = pool.rollback(Txid::from_str(txs[0]).unwrap()).unwrap();
758        assert_eq!(rollbacked_states.len(), 3);
759        assert_eq!(pool.states.len(), 0);
760        assert_eq!(rollbacked_states[0], before_rollback_states[2]);
761        assert_eq!(rollbacked_states[1], before_rollback_states[1]);
762        assert_eq!(rollbacked_states[2], before_rollback_states[0]);
763
764        // test rollback mid tx
765        init_pool_state(&mut pool);
766        assert_eq!(pool.states.len(), 3);
767        let before_rollback_states = pool.states.clone();
768        let rollbacked_states = pool.rollback(Txid::from_str(txs[1]).unwrap()).unwrap();
769        assert_eq!(rollbacked_states.len(), 2);
770        assert_eq!(pool.states.len(), 1);
771        assert_eq!(pool.states[0], before_rollback_states[0]);
772        assert_eq!(rollbacked_states[0], before_rollback_states[2]);
773        assert_eq!(rollbacked_states[1], before_rollback_states[1]);
774
775        // test rollback last tx
776        init_pool_state(&mut pool);
777        assert_eq!(pool.states.len(), 3);
778        let before_rollback_states = pool.states.clone();
779        let rollbacked_states = pool.rollback(Txid::from_str(txs[2]).unwrap()).unwrap();
780        assert_eq!(rollbacked_states.len(), 1);
781        assert_eq!(pool.states.len(), 2);
782        assert_eq!(pool.states[0], before_rollback_states[0]);
783        assert_eq!(pool.states[1], before_rollback_states[1]);
784        assert_eq!(rollbacked_states[0], before_rollback_states[2]);
785    }
786}