Skip to content

Commit ec4f7af

Browse files
committed
Adding also reward views into the CLI
1 parent 75e453d commit ec4f7af

10 files changed

+504
-12
lines changed
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
//! CLI commands related to liquidity mining.
22
33
mod add_pool_reward;
4-
mod claim_pool_reward;
54
mod close_pool_reward;
65
mod crank_pool_rewards;
76
mod edit_pool_reward;
87
mod find_obligations_to_fund;
98
mod migrate_all_reserves;
9+
mod view_obligation_rewards;
10+
mod view_reserve_rewards;
1011

1112
pub(crate) use add_pool_reward::command as command_add_pool_reward;
13+
pub(crate) use close_pool_reward::command as command_close_pool_reward;
1214
pub(crate) use crank_pool_rewards::command as command_crank_pool_rewards;
15+
pub(crate) use edit_pool_reward::command as command_edit_pool_reward;
1316
pub(crate) use find_obligations_to_fund::command as command_find_obligations_to_fund_for_liquidity_mining;
1417
pub(crate) use migrate_all_reserves::command as command_migrate_all_reserves_for_liquidity_mining;
18+
pub(crate) use view_obligation_rewards::command as command_view_obligation_rewards;
19+
pub(crate) use view_reserve_rewards::command as command_view_reserve_rewards;

token-lending/cli/src/liquidity_mining/add_pool_reward.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ pub(crate) fn command(
4242
.any(|pr| matches!(pr, PoolRewardEntry::Vacant { .. }));
4343

4444
if !has_free_slot {
45-
return Err(format!(
46-
"There are no vacant slots to add the pool reward. Please crank it first"
47-
)
48-
.into());
45+
return Err(
46+
"There are no vacant slots to add the pool reward. Please crank it first".into(),
47+
);
4948
}
5049

5150
let Some(source_reward_token_account) = config
@@ -55,7 +54,8 @@ pub(crate) fn command(
5554
return Err(format!(
5655
"Failed to fetch source token account '{}'",
5756
source_reward_token_account_pubkey
58-
))?;
57+
)
58+
.into());
5959
};
6060

6161
let reward_mint = Pubkey::from_str(&source_reward_token_account.mint)?;

token-lending/cli/src/liquidity_mining/claim_pool_reward.rs

Whitespace-only changes.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! A pool reward can only be closed if it has no more active user reward managers.
2+
3+
use std::{borrow::Borrow, str::FromStr};
4+
5+
use solana_program::program_pack::Pack;
6+
use solana_sdk::pubkey::Pubkey;
7+
use solend_sdk::{
8+
instruction::{close_pool_reward, find_reward_vault_authority},
9+
state::{LendingMarket, PoolRewardEntry, PositionKind, Reserve},
10+
};
11+
12+
use crate::{send_transaction, CommandResult, Config};
13+
14+
pub(crate) fn command(
15+
config: &mut Config,
16+
reserve_pubkey: Pubkey,
17+
position_kind: PositionKind,
18+
pool_reward_index: usize,
19+
destination_reward_token_account_pubkey: Pubkey,
20+
) -> CommandResult {
21+
let reserve_info = config.rpc_client.get_account(&reserve_pubkey)?;
22+
let reserve = Reserve::unpack_from_slice(reserve_info.data.borrow())?;
23+
let lending_market_info = config.rpc_client.get_account(&reserve.lending_market)?;
24+
let lending_market = LendingMarket::unpack(lending_market_info.data.borrow())?;
25+
26+
if config.fee_payer.pubkey() != lending_market.owner {
27+
return Err(format!(
28+
"The fee payer must be the owner of the lending market '{}'",
29+
reserve.lending_market
30+
)
31+
.into());
32+
}
33+
34+
let PoolRewardEntry::Occupied(pool_reward) = reserve
35+
.pool_reward_manager(position_kind)
36+
.pool_rewards
37+
.get(pool_reward_index)
38+
.ok_or_else(|| {
39+
format!(
40+
"Pool reward index {} does not exist for position kind {:?}",
41+
pool_reward_index, position_kind
42+
)
43+
})?
44+
else {
45+
return Err("Pool reward index is not occupied".into());
46+
};
47+
48+
if pool_reward.num_user_reward_managers > 0 {
49+
return Err(format!(
50+
"Pool reward still has {} user reward managers. Crank it first.",
51+
pool_reward.num_user_reward_managers
52+
)
53+
.into());
54+
}
55+
56+
let Some(reward_vault_token_account) =
57+
config.rpc_client.get_token_account(&pool_reward.vault)?
58+
else {
59+
return Err(format!(
60+
"Failed to fetch pool reward vault '{}'",
61+
pool_reward.vault
62+
))?;
63+
};
64+
65+
let reward_mint = Pubkey::from_str(&reward_vault_token_account.mint)?;
66+
67+
let (reward_vault_authority, reward_authority_bump) = find_reward_vault_authority(
68+
&config.lending_program_id,
69+
&reserve.lending_market,
70+
&pool_reward.vault,
71+
);
72+
73+
let close_reward_ix = close_pool_reward(
74+
config.lending_program_id,
75+
reward_authority_bump,
76+
position_kind,
77+
pool_reward_index as _,
78+
reserve_pubkey,
79+
reward_mint,
80+
destination_reward_token_account_pubkey,
81+
reward_vault_authority,
82+
pool_reward.vault,
83+
reserve.lending_market,
84+
lending_market.owner,
85+
);
86+
87+
let recent_blockhash = config.rpc_client.get_latest_blockhash()?;
88+
89+
let message = solana_sdk::message::Message::new_with_blockhash(
90+
&[close_reward_ix],
91+
Some(&config.fee_payer.pubkey()),
92+
&recent_blockhash,
93+
);
94+
95+
let transaction = solana_sdk::transaction::Transaction::new(
96+
&vec![config.fee_payer.as_ref()],
97+
message,
98+
recent_blockhash,
99+
);
100+
101+
send_transaction(config, transaction)?;
102+
103+
Ok(())
104+
}

token-lending/cli/src/liquidity_mining/crank_pool_rewards.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//! Each reserve has a limited number of slots that are used to declare pool rewards.
2-
//! When all slots are occupied, the admin can no longer start new pool rewards.
1+
//! Each reserve has a limited number of entries that are used to declare pool rewards.
2+
//! When all entries are occupied, the admin can no longer start new pool rewards.
33
//!
44
//! This is where cranking comes in.
55
//! Given a reserve, this command estimates the cheapest pool reward to crank out.
@@ -130,7 +130,7 @@ pub(crate) fn command(
130130
})
131131
})
132132
.map(|(obligation_pubkey, obligation)| (obligation_pubkey, obligation.owner))
133-
.map(|(obligation_pubkey, obligation_owner)| {
133+
.flat_map(|(obligation_pubkey, obligation_owner)| {
134134
let ata = get_associated_token_address(&obligation_owner, &reward_mint);
135135

136136
let create_ata_ix = create_associated_token_account_idempotent(
@@ -155,7 +155,6 @@ pub(crate) fn command(
155155

156156
std::iter::once(create_ata_ix).chain(std::iter::once(claim_ix))
157157
})
158-
.flatten()
159158
.collect();
160159

161160
for ixs in ixs.chunks(CLAIM_IXS_BATCH_SIZE).progress() {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::{borrow::Borrow, str::FromStr};
2+
3+
use solana_program::program_pack::Pack;
4+
use solana_sdk::pubkey::Pubkey;
5+
use solend_sdk::{
6+
instruction::{edit_pool_reward, find_reward_vault_authority},
7+
state::{LendingMarket, PoolRewardEntry, PositionKind, Reserve},
8+
};
9+
10+
use crate::{send_transaction, CommandResult, Config};
11+
12+
pub(crate) fn command(
13+
config: &mut Config,
14+
reserve_pubkey: Pubkey,
15+
position_kind: PositionKind,
16+
pool_reward_index: usize,
17+
new_end_time_secs: u64,
18+
reward_token_account_pubkey: Pubkey,
19+
) -> CommandResult {
20+
let reserve_info = config.rpc_client.get_account(&reserve_pubkey)?;
21+
let reserve = Reserve::unpack_from_slice(reserve_info.data.borrow())?;
22+
let lending_market_info = config.rpc_client.get_account(&reserve.lending_market)?;
23+
let lending_market = LendingMarket::unpack(lending_market_info.data.borrow())?;
24+
25+
if config.fee_payer.pubkey() != lending_market.owner {
26+
return Err(format!(
27+
"The fee payer must be the owner of the lending market '{}'",
28+
reserve.lending_market
29+
)
30+
.into());
31+
}
32+
33+
let PoolRewardEntry::Occupied(pool_reward) = reserve
34+
.pool_reward_manager(position_kind)
35+
.pool_rewards
36+
.get(pool_reward_index)
37+
.ok_or_else(|| {
38+
format!(
39+
"Pool reward index {} does not exist for position kind {:?}",
40+
pool_reward_index, position_kind
41+
)
42+
})?
43+
else {
44+
return Err("Pool reward index is not occupied".into());
45+
};
46+
47+
let Some(reward_vault_token_account) =
48+
config.rpc_client.get_token_account(&pool_reward.vault)?
49+
else {
50+
return Err(format!(
51+
"Failed to fetch pool reward vault '{}'",
52+
pool_reward.vault
53+
))?;
54+
};
55+
56+
let reward_mint = Pubkey::from_str(&reward_vault_token_account.mint)?;
57+
58+
let (reward_vault_authority, reward_authority_bump) = find_reward_vault_authority(
59+
&config.lending_program_id,
60+
&reserve.lending_market,
61+
&pool_reward.vault,
62+
);
63+
64+
let edit_reward_ix = edit_pool_reward(
65+
config.lending_program_id,
66+
reward_authority_bump,
67+
position_kind,
68+
pool_reward_index as _,
69+
new_end_time_secs,
70+
reserve_pubkey,
71+
reward_mint,
72+
reward_token_account_pubkey,
73+
reward_vault_authority,
74+
pool_reward.vault,
75+
reserve.lending_market,
76+
lending_market.owner,
77+
);
78+
79+
let recent_blockhash = config.rpc_client.get_latest_blockhash()?;
80+
81+
let message = solana_sdk::message::Message::new_with_blockhash(
82+
&[edit_reward_ix],
83+
Some(&config.fee_payer.pubkey()),
84+
&recent_blockhash,
85+
);
86+
87+
let transaction = solana_sdk::transaction::Transaction::new(
88+
&vec![config.fee_payer.as_ref()],
89+
message,
90+
recent_blockhash,
91+
);
92+
93+
send_transaction(config, transaction)?;
94+
95+
Ok(())
96+
}

token-lending/cli/src/liquidity_mining/find_obligations_to_fund.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ pub(crate) fn command(config: &mut Config, output_csv: impl AsRef<Path>) -> Comm
3737
encoding: Some(UiAccountEncoding::Base64),
3838
..Default::default()
3939
},
40-
..Default::default()
4140
};
4241
let all_obligations = config
4342
.rpc_client
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::{borrow::Borrow, time::SystemTime};
2+
3+
use solana_program::program_pack::Pack;
4+
use solana_sdk::pubkey::Pubkey;
5+
use solend_sdk::state::{Obligation, Reserve};
6+
7+
use crate::{CommandResult, Config};
8+
9+
pub(crate) fn command(config: &mut Config, obligation_pubkey: Pubkey) -> CommandResult {
10+
let obligation_info = config.rpc_client.get_account(&obligation_pubkey)?;
11+
let obligation = Obligation::unpack_from_slice(obligation_info.data.borrow())?;
12+
13+
let now_secs = SystemTime::now()
14+
.duration_since(SystemTime::UNIX_EPOCH)?
15+
.as_secs();
16+
17+
for user_manager in obligation.user_reward_managers.iter() {
18+
let reserve_info = config.rpc_client.get_account(&user_manager.reserve)?;
19+
let reserve = Reserve::unpack_from_slice(reserve_info.data.borrow())?;
20+
println!(
21+
"Rewards for reserve {} {:?} last updated {}s ago",
22+
user_manager.reserve,
23+
user_manager.position_kind,
24+
now_secs.saturating_sub(user_manager.last_update_time_secs)
25+
);
26+
27+
let pool_reward_manager = reserve.pool_reward_manager(user_manager.position_kind);
28+
29+
let share = user_manager.share as f64 / pool_reward_manager.total_shares as f64;
30+
println!(
31+
" Mines {}% in {} rewards",
32+
share * 100.0,
33+
user_manager.rewards.len()
34+
);
35+
}
36+
37+
Ok(())
38+
}

0 commit comments

Comments
 (0)