1#[doc(hidden)]
99pub mod schnorr;
100#[doc(hidden)]
101pub mod states;
102pub mod store;
103pub mod prelude {
104 pub use crate::*;
105 pub use ree_exchange_sdk_macro::*;
106}
107
108use crate::types::{
109 CoinBalance, Intention, IntentionSet, Pubkey, TxRecord, Txid, Utxo, exchange_interfaces::*,
110};
111use candid::CandidType;
112use ic_stable_structures::{
113 BTreeMap, DefaultMemoryImpl, Storable, memory_manager::VirtualMemory, storable::Bound,
114};
115use serde::{Deserialize, Serialize};
116
117pub use ree_types as types;
119
120pub mod error {
121 pub const POOL_NOT_FOUND: u16 = 101;
122 pub const NONCE_EXPIRED: u16 = 102;
123 pub const UNKNOWN_ACTION: u16 = 103;
124 pub const ILLEGAL_PSBT: u16 = 104;
125 pub const POOL_BEING_EXECUTED: u16 = 105;
126 pub const TXID_NOT_FOUND: u16 = 106;
127 pub const NONCE_NOT_FOUND: u16 = 107;
128
129 #[derive(Clone, Debug, PartialEq, Eq)]
130 pub enum Error {
131 PoolNotFound,
132 NonceExpired,
133 UnknownAction,
134 IllegalPsbt,
135 PoolBeingExecuted,
136 TxidNotFound,
137 NonceNotFound,
138 Custom(u16, String),
139 }
140
141 impl std::fmt::Display for Error {
142 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
143 match self {
144 Error::PoolNotFound => write!(f, "{}:Pool not found", POOL_NOT_FOUND),
145 Error::NonceExpired => write!(f, "{}:Nonce expired", NONCE_EXPIRED),
146 Error::UnknownAction => write!(f, "{}:Unknown action", UNKNOWN_ACTION),
147 Error::IllegalPsbt => write!(f, "{}:Illegal PSBT", ILLEGAL_PSBT),
148 Error::TxidNotFound => write!(f, "{}:Txid not found", TXID_NOT_FOUND),
149 Error::NonceNotFound => write!(f, "{}:Nonce not found", NONCE_NOT_FOUND),
150 Error::PoolBeingExecuted => {
151 write!(f, "{}:Pool is being executed", POOL_BEING_EXECUTED)
152 }
153 Error::Custom(code, msg) => write!(f, "{}:{}", code % 100 + 200, msg),
154 }
155 }
156 }
157}
158
159#[doc(hidden)]
160pub type BlockStateStorage<S> = BTreeMap<u32, GlobalStateWrapper<S>, Memory>;
161#[doc(hidden)]
162pub type Memory = VirtualMemory<DefaultMemoryImpl>;
163#[doc(hidden)]
164pub type BlockStorage = BTreeMap<u32, Block, Memory>;
165#[doc(hidden)]
166pub type UnconfirmedTxStorage = BTreeMap<Txid, TxRecord, Memory>;
167#[doc(hidden)]
168pub type PoolStorage<S> = BTreeMap<String, Pool<S>, Memory>;
169
170#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Copy)]
172pub enum Network {
173 Bitcoin,
174 Testnet4,
175}
176
177impl Into<crate::types::bitcoin::Network> for Network {
178 fn into(self) -> crate::types::bitcoin::Network {
179 match self {
180 Network::Bitcoin => crate::types::bitcoin::Network::Bitcoin,
181 Network::Testnet4 => crate::types::bitcoin::Network::Testnet4,
182 }
183 }
184}
185
186#[doc(hidden)]
187pub fn ensure_access<P: Pools>() -> Result<(), String> {
188 match P::network() {
189 Network::Bitcoin => crate::types::orchestrator_interfaces::ensure_orchestrator(),
190 Network::Testnet4 => crate::types::orchestrator_interfaces::ensure_testnet4_orchestrator(),
191 }
192}
193
194#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
196pub struct Block {
197 pub block_height: u32,
199 pub block_hash: String,
201 pub block_timestamp: u64,
203 pub txs: Vec<TxRecord>,
205}
206
207impl Storable for Block {
208 fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
209 let bytes = bincode::serialize(self).unwrap();
210 std::borrow::Cow::Owned(bytes)
211 }
212
213 fn into_bytes(self) -> Vec<u8> {
214 bincode::serialize(&self).unwrap()
215 }
216
217 fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
218 bincode::deserialize(bytes.as_ref()).unwrap()
219 }
220
221 const BOUND: Bound = Bound::Unbounded;
222}
223
224#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
227pub struct Metadata {
228 pub key: Pubkey,
229 pub key_derivation_path: Vec<Vec<u8>>,
230 pub name: String,
231 pub address: String,
232}
233
234impl Metadata {
235 pub async fn new<P: Pools>(name: String) -> Result<Self, String> {
237 let key_derivation_path: Vec<Vec<u8>> = vec![name.clone().into_bytes()];
238 let (key, _, address) =
239 crate::schnorr::request_p2tr_address(key_derivation_path.clone(), P::network())
240 .await
241 .map_err(|e| format!("Failed to generate pool address: {}", e))?;
242 Ok(Self {
243 key,
244 key_derivation_path,
245 name,
246 address: address.to_string(),
247 })
248 }
249}
250
251#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
253pub struct StateInfo {
254 pub nonce: u64,
255 pub txid: Txid,
256 pub coin_reserved: Vec<CoinBalance>,
257 pub btc_reserved: u64,
258 pub utxos: Vec<Utxo>,
259 pub attributes: String,
260}
261
262#[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
264pub struct ActionArgs {
265 pub txid: Txid,
266 pub initiator_address: String,
267 pub intention: Intention,
268 pub other_intentions: Vec<Intention>,
269 pub unconfirmed_tx_count: usize,
270 pub is_reapply: bool,
271}
272
273impl From<ExecuteTxArgs> for ActionArgs {
274 fn from(args: ExecuteTxArgs) -> Self {
275 let ExecuteTxArgs {
276 psbt_hex: _,
277 txid,
278 intention_set,
279 intention_index,
280 zero_confirmed_tx_queue_length,
281 is_reapply,
282 } = args;
283 let IntentionSet {
284 mut intentions,
285 initiator_address,
286 tx_fee_in_sats: _,
287 } = intention_set;
288 let intention = intentions.swap_remove(intention_index as usize);
289 Self {
290 txid,
291 initiator_address,
292 intention,
293 other_intentions: intentions,
294 unconfirmed_tx_count: zero_confirmed_tx_queue_length as usize,
295 is_reapply: is_reapply.unwrap_or(false),
296 }
297 }
298}
299
300pub type ActionResult<S> = Result<S, error::Error>;
302
303pub trait StateView {
305 fn inspect_state(&self) -> StateInfo;
306
307 fn set_nonce(&mut self, nonce: u64);
308}
309
310#[derive(Clone, Debug, Deserialize, Serialize)]
313pub struct Pool<S> {
314 metadata: Metadata,
315 states: Vec<S>,
316}
317
318impl<S> Storable for Pool<S>
319where
320 S: Serialize + for<'de> Deserialize<'de>,
321{
322 const BOUND: Bound = Bound::Unbounded;
323
324 fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
325 let bytes = bincode::serialize(self).unwrap();
326 std::borrow::Cow::Owned(bytes)
327 }
328
329 fn into_bytes(self) -> Vec<u8> {
330 bincode::serialize(&self).unwrap()
331 }
332
333 fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
334 bincode::deserialize(bytes.as_ref()).unwrap()
335 }
336}
337
338impl<S> Pool<S>
339where
340 S: StateView,
341{
342 pub fn new(metadata: Metadata) -> Self {
344 Self {
345 metadata,
346 states: Vec::new(),
347 }
348 }
349
350 pub fn metadata(&self) -> &Metadata {
352 &self.metadata
353 }
354
355 pub fn last_state(&self) -> Option<&S> {
357 self.states.last()
358 }
359
360 pub fn states(&self) -> &Vec<S> {
362 &self.states
363 }
364
365 pub fn get(&self, txid: Txid) -> Option<&S> {
367 self.states
368 .iter()
369 .find(|state| state.inspect_state().txid == txid)
370 }
371
372 pub fn states_mut(&mut self) -> &mut Vec<S> {
374 &mut self.states
375 }
376}
377
378#[doc(hidden)]
379pub trait ReePool<S> {
380 fn get_pool_info(&self) -> PoolInfo;
381
382 fn get_pool_basic(&self) -> PoolBasic;
383
384 fn truncate(&mut self, nonce: u64) -> Result<(), String>;
385
386 fn rollback(&mut self, txid: Txid) -> Result<Vec<S>, String>;
387
388 fn finalize(&mut self, txid: Txid) -> Result<(), String>;
389}
390
391#[doc(hidden)]
392impl<S> ReePool<S> for Pool<S>
393where
394 S: StateView,
395{
396 fn get_pool_basic(&self) -> PoolBasic {
397 PoolBasic {
398 name: self.metadata.name.clone(),
399 address: self.metadata.address.clone(),
400 }
401 }
402
403 fn get_pool_info(&self) -> PoolInfo {
404 let metadata: Metadata = self.metadata.clone();
405 let Metadata {
406 key,
407 key_derivation_path,
408 name,
409 address,
410 } = metadata;
411 let state = self
412 .states
413 .last()
414 .map(|s| s.inspect_state())
415 .unwrap_or_default();
416 let StateInfo {
417 txid: _,
418 nonce,
419 coin_reserved,
420 btc_reserved,
421 utxos,
422 attributes,
423 } = state;
424 PoolInfo {
425 key,
426 key_derivation_path,
427 name,
428 address,
429 nonce,
430 coin_reserved,
431 btc_reserved,
432 utxos,
433 attributes,
434 }
435 }
436
437 fn truncate(&mut self, nonce: u64) -> Result<(), String> {
438 while let Some(state) = self.states.last() {
439 if state.inspect_state().nonce >= nonce {
440 self.states.pop();
441 } else {
442 break;
443 }
444 }
445 Ok(())
446 }
447
448 fn rollback(&mut self, txid: Txid) -> Result<Vec<S>, String> {
449 let idx = self
450 .states
451 .iter()
452 .position(|state| state.inspect_state().txid == txid)
453 .ok_or("txid not found".to_string())?;
454
455 let mut rollbacked_states = vec![];
456 while self.states.len() > idx {
457 rollbacked_states.push(self.states.pop().unwrap());
458 }
459
460 Ok(rollbacked_states)
461 }
462
463 fn finalize(&mut self, txid: Txid) -> Result<(), String> {
464 let idx = self
465 .states
466 .iter()
467 .position(|state| state.inspect_state().txid == txid)
468 .ok_or("txid not found".to_string())?;
469 if idx == 0 {
470 return Ok(());
471 }
472 self.states.rotate_left(idx);
473 self.states.truncate(self.states.len() - idx);
474 Ok(())
475 }
476}
477
478pub trait Pools {
480 type PoolState: StateView + Serialize + for<'de> Deserialize<'de>;
482
483 type BlockState: Serialize + for<'de> Deserialize<'de>;
485
486 const BLOCK_STATE_MEMORY: u8;
488
489 const POOL_STATE_MEMORY: u8;
491
492 fn network() -> Network;
494
495 fn finalize_threshold() -> u32 {
498 60
499 }
500}
501
502pub trait Hook: Pools {
505 fn on_tx_rollbacked(
507 _address: String,
508 _txid: Txid,
509 _reason: String,
510 _rollbacked_states: Vec<Self::PoolState>,
511 ) {
512 }
513
514 fn on_tx_confirmed(_address: String, _txid: Txid, _block: Block) {}
516
517 fn on_block_confirmed(_block: Block) {}
519
520 fn pre_block_confirmed(_height: u32) {}
522}
523
524pub trait PoolStorageAccess<P: Pools> {
527 fn block_state() -> Option<P::BlockState>;
528
529 fn commit(height: u32, block_state: P::BlockState) -> Result<(), String>;
530
531 fn get(address: &String) -> Option<Pool<P::PoolState>>;
532
533 fn insert(pool: Pool<P::PoolState>);
534
535 fn remove(address: &String) -> Option<Pool<P::PoolState>>;
536
537 fn iter() -> iter::PoolIterator<P>;
538}
539
540#[doc(hidden)]
541#[derive(Clone, Debug, Deserialize, Serialize)]
542pub struct GlobalStateWrapper<S> {
543 pub inner: S,
544}
545
546#[doc(hidden)]
547impl<S> GlobalStateWrapper<S> {
548 pub fn new(s: S) -> Self {
549 Self { inner: s }
550 }
551}
552
553#[doc(hidden)]
554impl<S> Storable for GlobalStateWrapper<S>
555where
556 S: Serialize + for<'de> Deserialize<'de>,
557{
558 const BOUND: Bound = Bound::Unbounded;
559
560 fn to_bytes(&self) -> std::borrow::Cow<'_, [u8]> {
561 let bytes = bincode::serialize(self).unwrap();
562 std::borrow::Cow::Owned(bytes)
563 }
564
565 fn into_bytes(self) -> Vec<u8> {
566 bincode::serialize(&self).unwrap()
567 }
568
569 fn from_bytes(bytes: std::borrow::Cow<'_, [u8]>) -> Self {
570 bincode::deserialize(bytes.as_ref()).unwrap()
571 }
572}
573
574pub trait Upgrade<P: Pools> {
661 type PoolState: Into<P::PoolState> + for<'de> Deserialize<'de> + Clone;
663
664 type BlockState: Into<P::BlockState> + for<'de> Deserialize<'de> + Clone;
666
667 const POOL_STATE_MEMORY: u8;
669
670 const BLOCK_STATE_MEMORY: u8;
672}
673
674#[doc(hidden)]
675pub fn iterator<P>(memory: Memory) -> iter::PoolIterator<P>
676where
677 P: Pools,
678{
679 let inner = PoolStorage::<P::PoolState>::init(memory);
680 let keys = inner.keys().collect::<Vec<_>>();
681 iter::PoolIterator {
682 inner,
683 cursor: 0,
684 keys,
685 }
686}
687
688#[doc(hidden)]
689pub mod iter {
690 pub struct PoolIterator<P: super::Pools> {
691 pub(crate) inner: super::PoolStorage<P::PoolState>,
692 pub(crate) cursor: usize,
693 pub(crate) keys: Vec<String>,
694 }
695
696 impl<P> std::iter::Iterator for PoolIterator<P>
697 where
698 P: super::Pools,
699 {
700 type Item = (String, super::Pool<P::PoolState>);
701
702 fn next(&mut self) -> Option<Self::Item> {
703 if self.cursor < self.keys.len() {
704 let key = self.keys[self.cursor].clone();
705 self.cursor += 1;
706 self.inner.get(&key).map(|v| (key.clone(), v))
707 } else {
708 None
709 }
710 }
711 }
712}
713
714#[cfg(test)]
715pub mod test {
716 use std::str::FromStr;
717
718 use super::*;
719
720 #[derive(CandidType, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
721 struct DummyPoolState {
722 nonce: u64,
723 txid: Txid,
724 coin_reserved: Vec<CoinBalance>,
725 btc_reserved: u64,
726 utxos: Vec<Utxo>,
727 attributes: String,
728 }
729
730 impl StateView for DummyPoolState {
731 fn inspect_state(&self) -> StateInfo {
732 StateInfo {
733 txid: self.txid.clone(),
734 nonce: self.nonce,
735 coin_reserved: self.coin_reserved.clone(),
736 btc_reserved: self.btc_reserved,
737 utxos: self.utxos.clone(),
738 attributes: self.attributes.clone(),
739 }
740 }
741
742 fn set_nonce(&mut self, nonce: u64) {
743 self.nonce = nonce;
744 }
745 }
746
747 #[test]
748 pub fn test_candid_and_bincode_serialize() {
749 let state = DummyPoolState {
750 nonce: 1,
751 txid: Txid::default(),
752 coin_reserved: vec![],
753 btc_reserved: 0,
754 utxos: vec![],
755 attributes: "{}".to_string(),
756 };
757 let pool = Pool::<DummyPoolState> {
758 metadata: Metadata {
759 key: Pubkey::from_raw(vec![2u8; 33]).unwrap(),
760 key_derivation_path: vec![vec![0; 32]],
761 name: "Test Pool".to_string(),
762 address: "test-address".to_string(),
763 },
764 states: vec![state.clone()],
765 };
766 let bincode_serialized = pool.to_bytes();
767 Pool::<DummyPoolState>::from_bytes(bincode_serialized);
768 assert_eq!(pool.metadata.name, "Test Pool");
769
770 let mut candid_ser = candid::ser::IDLBuilder::new();
771 candid_ser.arg(&state).unwrap();
772 let candid_serialized = candid_ser.serialize_to_vec();
773 assert!(candid_serialized.is_ok());
774 let candid_serialized = candid_serialized.unwrap();
775 let mut candid_de = candid::de::IDLDeserialize::new(&candid_serialized).unwrap();
776 let candid_deserialized = candid_de.get_value::<DummyPoolState>();
777 assert!(candid_deserialized.is_ok());
778 }
779
780 #[test]
781 fn test_pool_rollback() {
782 let mut pool = Pool::<DummyPoolState> {
783 metadata: Metadata {
784 key: Pubkey::from_raw(vec![2u8; 33]).unwrap(),
785 key_derivation_path: vec![vec![0; 32]],
786 name: "Test Pool".to_string(),
787 address: "test-address".to_string(),
788 },
789 states: vec![],
790 };
791 let push_random_state_by_txid = |txid: &str, pool: &mut Pool<DummyPoolState>| {
792 let txid = Txid::from_str(txid).unwrap();
793 let nonce = pool.states.len() as u64;
794 let state = DummyPoolState {
795 nonce,
796 txid,
797 coin_reserved: vec![],
798 btc_reserved: 0,
799 utxos: vec![],
800 attributes: "{}".to_string(),
801 };
802 pool.states.push(state);
803 };
804
805 let txs = [
806 "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2a",
807 "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2b",
808 "51230fe70deae44a92f8f44a600585e3e57b8c8720a0b67c4c422f579d9ace2c",
809 ];
810
811 let init_pool_state = |pool: &mut Pool<DummyPoolState>| {
812 pool.states.clear();
813 for txid in txs.iter() {
814 push_random_state_by_txid(txid, pool);
815 }
816 };
817
818 init_pool_state(&mut pool);
820 assert_eq!(pool.states.len(), 3);
821 let before_rollback_states = pool.states.clone();
822 let rollbacked_states = pool.rollback(Txid::from_str(txs[0]).unwrap()).unwrap();
823 assert_eq!(rollbacked_states.len(), 3);
824 assert_eq!(pool.states.len(), 0);
825 assert_eq!(rollbacked_states[0], before_rollback_states[2]);
826 assert_eq!(rollbacked_states[1], before_rollback_states[1]);
827 assert_eq!(rollbacked_states[2], before_rollback_states[0]);
828
829 init_pool_state(&mut pool);
831 assert_eq!(pool.states.len(), 3);
832 let before_rollback_states = pool.states.clone();
833 let rollbacked_states = pool.rollback(Txid::from_str(txs[1]).unwrap()).unwrap();
834 assert_eq!(rollbacked_states.len(), 2);
835 assert_eq!(pool.states.len(), 1);
836 assert_eq!(pool.states[0], before_rollback_states[0]);
837 assert_eq!(rollbacked_states[0], before_rollback_states[2]);
838 assert_eq!(rollbacked_states[1], before_rollback_states[1]);
839
840 init_pool_state(&mut pool);
842 assert_eq!(pool.states.len(), 3);
843 let before_rollback_states = pool.states.clone();
844 let rollbacked_states = pool.rollback(Txid::from_str(txs[2]).unwrap()).unwrap();
845 assert_eq!(rollbacked_states.len(), 1);
846 assert_eq!(pool.states.len(), 2);
847 assert_eq!(pool.states[0], before_rollback_states[0]);
848 assert_eq!(pool.states[1], before_rollback_states[1]);
849 assert_eq!(rollbacked_states[0], before_rollback_states[2]);
850 }
851}