Skip to content

Commit

Permalink
Add account lot collect subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
mvines committed Jun 18, 2024
1 parent bd6ac2f commit e449b2b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/bin/sys-lend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.takes_value(true)
.global(true)
.validator(is_url)
.help("Helium JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"),
.help("Helius JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"),
)
.arg(
Arg::with_name("priority_fee_exact")
Expand Down
47 changes: 27 additions & 20 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ impl LotAcquistion {
}
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, EnumString, IntoStaticStr)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, EnumString, IntoStaticStr)]
pub enum LotSelectionMethod {
#[strum(serialize = "fifo")]
FirstInFirstOut,
Expand Down Expand Up @@ -331,6 +331,31 @@ impl Lot {
}
}

pub fn sort_lots_by_selection_method(
lots: &mut Vec<Lot>,
lot_selection_method: LotSelectionMethod,
) {
match lot_selection_method {
LotSelectionMethod::FirstInFirstOut => {
lots.sort_by(|a, b| a.acquisition.when.cmp(&b.acquisition.when));
if !lots.is_empty() {
// Assume the oldest lot is the rent-reserve. Extract it as the last resort
let first_lot = lots.remove(0);
lots.push(first_lot);
}
}
LotSelectionMethod::LastInFirstOut => {
lots.sort_by(|a, b| b.acquisition.when.cmp(&a.acquisition.when))
}
LotSelectionMethod::LowestBasis => {
lots.sort_by(|a, b| a.acquisition.price().cmp(&b.acquisition.price()))
}
LotSelectionMethod::HighestBasis => {
lots.sort_by(|a, b| b.acquisition.price().cmp(&a.acquisition.price()))
}
}
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum LotDisposalKind {
Usd {
Expand Down Expand Up @@ -455,25 +480,7 @@ fn split_lots(
let mut extracted_lots = vec![];
let mut remaining_lots = vec![];

match lot_selection_method {
LotSelectionMethod::FirstInFirstOut => {
lots.sort_by(|a, b| a.acquisition.when.cmp(&b.acquisition.when));
if !lots.is_empty() {
// Assume the oldest lot is the rent-reserve. Extract it as the last resort
let first_lot = lots.remove(0);
lots.push(first_lot);
}
}
LotSelectionMethod::LastInFirstOut => {
lots.sort_by(|a, b| b.acquisition.when.cmp(&a.acquisition.when))
}
LotSelectionMethod::LowestBasis => {
lots.sort_by(|a, b| a.acquisition.price().cmp(&b.acquisition.price()))
}
LotSelectionMethod::HighestBasis => {
lots.sort_by(|a, b| b.acquisition.price().cmp(&a.acquisition.price()))
}
}
sort_lots_by_selection_method(&mut lots, lot_selection_method);

let mut amount_remaining = amount;
for mut lot in lots {
Expand Down
94 changes: 91 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4415,7 +4415,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.takes_value(true)
.global(true)
.validator(is_url)
.help("Helium JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"),
.help("Helius JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"),
)
.arg(
Arg::with_name("verbose")
Expand Down Expand Up @@ -5129,7 +5129,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.setting(AppSettings::InferSubcommands)
.subcommand(
SubCommand::with_name("swap")
.about("Swap lots in the local database only")
.about("Swap lots")
.arg(
Arg::with_name("lot_number1")
.value_name("LOT NUMBER")
Expand All @@ -5147,6 +5147,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.help("Second lot number"),
)
)
.subcommand(
SubCommand::with_name("collect")
.about("Collect non-disposed lots of a desired type into an address")
.arg(
Arg::with_name("token")
.value_name("SOL or SPL Token")
.takes_value(true)
.required(true)
.validator(is_valid_token_or_sol)
.help("Token type"),
)
.arg(
Arg::with_name("address")
.value_name("ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("Account address"),
)
.arg(lot_selection_arg())
)
.subcommand(
SubCommand::with_name("delete")
.about("Delete a lot from the local database only. \
Expand All @@ -5169,7 +5190,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
)
.subcommand(
SubCommand::with_name("move")
.about("Move a lot to a new addresses in the local database only. \
.about("Move a lot to a new address. \
Useful if the on-chain state is out of sync with the database")
.arg(
Arg::with_name("lot_number")
Expand Down Expand Up @@ -6145,6 +6166,73 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Swapping lots {lot_number1} and {lot_number2}");
db.swap_lots(lot_number1, lot_number2)?;
}
("collect", Some(arg_matches)) => {
let address = pubkey_of(arg_matches, "address").unwrap();
let token = MaybeToken::from(value_t!(arg_matches, "token", Token).ok());
let lot_selection_method =
value_t_or_exit!(arg_matches, "lot_selection", LotSelectionMethod);

println!(
"Collecting {lot_selection_method:?} lots for {address} ({})",
token.name()
);
loop {
let mut current_lots = vec![];
let mut candidate_lots = vec![];
for account in db.get_accounts() {
if (account.token == token)
|| (token.is_sol_or_wsol() && account.token.is_sol_or_wsol())
{
if account.address == address && account.token == token {
assert!(current_lots.is_empty());
current_lots = account.lots;
} else {
candidate_lots.extend(account.lots);
}
}
}

sort_lots_by_selection_method(&mut current_lots, lot_selection_method);

candidate_lots.extend(current_lots.clone());
sort_lots_by_selection_method(&mut candidate_lots, lot_selection_method);

while !current_lots.is_empty() {
match lot_selection_method {
LotSelectionMethod::LowestBasis
| LotSelectionMethod::HighestBasis => {
if current_lots[0].acquisition.price()
!= candidate_lots[0].acquisition.price()
{
break;
}
}
LotSelectionMethod::FirstInFirstOut
| LotSelectionMethod::LastInFirstOut => {
if current_lots[0].acquisition.when
!= candidate_lots[0].acquisition.when
{
break;
}
}
}

current_lots.pop();
candidate_lots.pop();
}

if current_lots.is_empty() {
println!("Done");
break;
}

println!(
"Swapping lots {} and {}",
current_lots[0].lot_number, candidate_lots[0].lot_number
);
db.swap_lots(current_lots[0].lot_number, candidate_lots[0].lot_number)?;
}
}
("move", Some(arg_matches)) => {
let lot_number = value_t_or_exit!(arg_matches, "lot_number", usize);
let to_address =
Expand Down
4 changes: 4 additions & 0 deletions src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ impl MaybeToken {
!self.is_token()
}

pub fn is_sol_or_wsol(&self) -> bool {
self.is_sol() || self.token() == Some(Token::wSOL)
}

pub fn ui_amount(&self, amount: u64) -> f64 {
match self.0 {
None => lamports_to_sol(amount),
Expand Down

0 comments on commit e449b2b

Please sign in to comment.