1use core::str::FromStr;
26
27use rgb::popls::bp::Coinselect;
28use rgb::{CellAddr, Outpoint, OwnedState, StateCalc};
29use strict_types::StrictVal;
30
31#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display, Default)]
32#[display(lowercase)]
33pub enum CoinselectStrategy {
34 #[default]
36 Aggregate,
37
38 SmallSize,
41}
42
43impl FromStr for CoinselectStrategy {
44 type Err = String;
45
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 match s.to_lowercase().as_str() {
48 "aggregate" => Ok(CoinselectStrategy::Aggregate),
49 "smallsize" => Ok(CoinselectStrategy::SmallSize),
50 s => Err(s.to_string()),
51 }
52 }
53}
54
55impl Coinselect for CoinselectStrategy {
56 fn coinselect<'a>(
57 &mut self,
58 invoiced_state: &StrictVal,
59 calc: &mut StateCalc,
60 owned_state: impl IntoIterator<
61 Item = &'a OwnedState<Outpoint>,
62 IntoIter: DoubleEndedIterator<Item = &'a OwnedState<Outpoint>>,
63 >,
64 ) -> Option<Vec<(CellAddr, Outpoint)>> {
65 let res = match self {
66 CoinselectStrategy::Aggregate => owned_state
67 .into_iter()
68 .take_while(|owned| {
69 if calc.is_satisfied(invoiced_state) {
70 return false;
71 }
72 calc.accumulate(&owned.assignment.data).is_ok()
73 })
74 .map(|owned| (owned.addr, owned.assignment.seal))
75 .collect(),
76 CoinselectStrategy::SmallSize => owned_state
77 .into_iter()
78 .rev()
79 .take_while(|owned| {
80 if calc.is_satisfied(invoiced_state) {
81 return false;
82 }
83 calc.accumulate(&owned.assignment.data).is_ok()
84 })
85 .map(|owned| (owned.addr, owned.assignment.seal))
86 .collect(),
87 };
88 if !calc.is_satisfied(invoiced_state) {
89 return None;
90 };
91 Some(res)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn display_from_str() {
101 assert_eq!(CoinselectStrategy::Aggregate.to_string(), "aggregate");
102 assert_eq!(CoinselectStrategy::SmallSize.to_string(), "smallsize");
103 assert_eq!(CoinselectStrategy::Aggregate, "aggregate".parse().unwrap());
104 assert_eq!(CoinselectStrategy::SmallSize, "smallsize".parse().unwrap());
105 }
106}