diff --git a/Anchor.toml b/Anchor.toml index bd1b269975a..99fe81ddd78 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -29,3 +29,104 @@ url = "https://api.mainnet-beta.solana.com" [[test.validator.clone]] # Solend Main Pool - (USDC) Reserve State address = "BgxfHJDzm44T7XG68MYKx7YisTjZu73tVovyZSjJMpmw" +# What follows is a list of some more reserves to clone to test batch upgrade +[[test.validator.clone]] +address = "46t9bCbiBwiVsjQPz2CLYcMULCtsZTBbwqLdAz7s2xXy" +[[test.validator.clone]] +address = "6RLEnWjEUR8MTTn8MtFXmw1Fz1JWjUVcC1bQFPqDXbgy" +[[test.validator.clone]] +address = "4o8bqVMVrjwbUEU69axoQB7LFDHHt58d2XMtPXFJ8tK1" +[[test.validator.clone]] +address = "3xLmLkoKSKddqg7ejPNq679ApQNnm2dn3VVvtV7isSDo" +[[test.validator.clone]] +address = "44JpnzauMCjmwHBN1VJe4rFVjidozsAeGMR5srzuWp55" +[[test.validator.clone]] +address = "4YA39gkfuskkp3ir1NZ9jySkHYocSnZoPY2oT64BRmb5" +[[test.validator.clone]] +address = "3NdKfP3qLSzxqQTkJpYL2gGBza1esdyakRrAyMLt64R2" +[[test.validator.clone]] +address = "59KjpUPKXsoYZUNdc8g2Yi32uAw9Brm6g8bQCfFVomyJ" +[[test.validator.clone]] +address = "5noExw6LxoDaoAbcaFj5YZbBhpXoMqazzZuBrAUeFiUj" +[[test.validator.clone]] +address = "5eLCVf61tRmv4V6WASMfR7X4skqXpEakqyaLMFrQETSB" +[[test.validator.clone]] +address = "rKBpHeyPyn9YU4VNtxaX6Tu618Y2R4sWybxUpea5ph4" +[[test.validator.clone]] +address = "5yq5AJTJMoRQvGFbA2h6wKRQMV7Z6CjfbZCa3cAJS2r5" +[[test.validator.clone]] +address = "gY2sQUkxEQPDcn2s72KBKmw9q343QpVhxU5WLu2sxjr" +[[test.validator.clone]] +address = "6TsJpaAbwJMTFZdAEHcVb86zQaLoNmbabYX1kkW1NAj7" +[[test.validator.clone]] +address = "6JLJ3eq8sHDjUBLy4zjvLjvyMjrqnUDizQEdcZqgaYrG" +[[test.validator.clone]] +address = "4kgzahtogzibeopWQBrKcCYPiavEAaV9sJjuZy9dUuic" +[[test.validator.clone]] +address = "6uhoHHFPQbRpssDphCjxU6hMmXp3GLS2qAZmCExZkwap" +[[test.validator.clone]] +address = "13Ts1ERfwAM11MVQAU3zCz49fGkWmdZbfXuTGyKz6ENy" +[[test.validator.clone]] +address = "14aRZgQAQtGRES3mNTFvEVEfEqtnKJa73ryENDAvJFaQ" +[[test.validator.clone]] +address = "6e8zg2Y9skA5AMm7J62GJQ8Ui4reuzEytBS74zHV8S7A" +[[test.validator.clone]] +address = "6QNF4ovs4vWqjjvNUNoJLhXCW34iEm2QGipRKJdk725n" +[[test.validator.clone]] +address = "yjuWAA6XXhEwxWuzbPfDPnYiohCLFqAfgCDA4EdHHDm" +[[test.validator.clone]] +address = "XK8FEMEziMX9W46ivFmjddjvW3aY8dkVvucEGLmrt5D" +[[test.validator.clone]] +address = "2WBEmjZbUMXZbs9ucG3B7254y2C34d72uv2qHjvR1T4n" +[[test.validator.clone]] +address = "yxW7QwpJzKxfNo2QkbcmgjpgFxEL4UGbUivkFWtkmd3" +[[test.validator.clone]] +address = "66RtjhW1bMXTJ2ZL8TMowUTAtraHiksKGGQSArGRZKAZ" +[[test.validator.clone]] +address = "3Cv8evqFV1MWirL1ohv2VsdTAmpDvNWy4veGjYDFrWn2" +[[test.validator.clone]] +address = "5rDqwn1GMMrSkjvZS1G7BZ2tR5Q4JS13wnSHn11La9a9" +[[test.validator.clone]] +address = "4ERjFetPd5DQDK8N9wL4i36ozs4zeW1MeGbPbKw9QMsy" +[[test.validator.clone]] +address = "5hevPuvhqmXdQcuiB2mqekxQcqL9kVix8D5ckRGyA8yk" +[[test.validator.clone]] +address = "62M3oYeJ2agvuHsgHfzJuiWsTbEewZCFJbMHXKDkKMqL" +[[test.validator.clone]] +address = "6DF8vRKZKdAK2rdirJTwG3TWogjgByqcEi5WbnwXb3YZ" +[[test.validator.clone]] +address = "3mSMHPvNewL8RTwAcA6GTCLjS19J3NK2huJxgA553oHy" +[[test.validator.clone]] +address = "75EgKN1rrVssMQr6KjvR5w6Gnth8FkqECgZ6Q4mDDCx6" +[[test.validator.clone]] +address = "2tSNdecgEHEidEemg9vCbgFPzg6nyok97xJc1Lse3mG8" +[[test.validator.clone]] +address = "3Hr5qshXDQgbL1za6Sayug61Hwt5rjnV7dbyE9NUQDaZ" +[[test.validator.clone]] +address = "2v3Y19ahC3dtV6CnrmT5vZfpJy7HkyFoxkRgTeuk6cC4" +[[test.validator.clone]] +address = "53oro4QCCqqtDgfs1qfeH5LyvfdSexjXXaT14drTJ2Xj" +[[test.validator.clone]] +address = "7kL41rV8tgRBticXUyp3LfCV7eBGKUrAwL2e7GJB5ooP" +[[test.validator.clone]] +address = "7t3QnGAqse8zvAohJJX7robsBviyP5Bg7xNBxi7HSPNy" +[[test.validator.clone]] +address = "7vcWs9Gut1HE8o24cfuYkjuFCdBMEkBALqqvLDnQsBQT" +[[test.validator.clone]] +address = "2j8XVnUFk6Hxm75QdJn6MDyxVE9QCbCe5BauGg82ANZU" +[[test.validator.clone]] +address = "81EGVb5RD8yft1N3SGxitn2QnvJg7Pbq5k4CiXkf3f5A" +[[test.validator.clone]] +address = "7UgZy6RzhfSG66qrdD7Q5LHrVJRm7bUqhRHPh9siBqQQ" +[[test.validator.clone]] +address = "3kWqRMVepJ5HSmXF16bBWQYQ5C7YNGvJJqCPt3A7zKWH" +[[test.validator.clone]] +address = "8NVfgFqWPiy7B4o4yQQ8XSTwSihidtdftA1wzeePnCeJ" +[[test.validator.clone]] +address = "3738W1f4ygKayow8TrFGuDbhFovQ3QhM2MPY8AF375ki" +[[test.validator.clone]] +address = "7ssc2gVnucKKwsh6DS4HYHUWAigJomW6v37T65SXJVEr" +[[test.validator.clone]] +address = "2wDArvF5bAdLmmm4QZNVFJDrML6CCBbfbCeCLEb7c6QN" +[[test.validator.clone]] +address = "41SrrxMb1yzivSbUNjLShRV5yZf7S7YdQ1Emg2AxCviu" diff --git a/token-lending/LIQUIDITY_MINING.md b/token-lending/LIQUIDITY_MINING.md index bc2af6f25f4..1ea8eb83000 100644 --- a/token-lending/LIQUIDITY_MINING.md +++ b/token-lending/LIQUIDITY_MINING.md @@ -73,7 +73,7 @@ Users will still be able to claim rewards they accrued until this point. #### Cancel Cancelling a pool reward can be done by setting the end time to 0. -Note that only rewards longer than [solend_sdk::MIN_REWARD_PERIOD_SECS] can be cancelled. +Note that only rewards longer than `solend_sdk::MIN_REWARD_PERIOD_SECS` can be cancelled. In this case we transfer tokens from the reward vault to the lending market reward token account. #### Shorten diff --git a/token-lending/cli/Cargo.toml b/token-lending/cli/Cargo.toml index a59aa534750..214e43835a8 100644 --- a/token-lending/cli/Cargo.toml +++ b/token-lending/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solend-program-cli" -version.workspace = true +version = "2.1.0" authors = ["Solend Maintainers "] description = "Solend Program CLI" edition = "2018" diff --git a/token-lending/cli/src/liquidity_mining.rs b/token-lending/cli/src/liquidity_mining.rs new file mode 100644 index 00000000000..dd837b521c6 --- /dev/null +++ b/token-lending/cli/src/liquidity_mining.rs @@ -0,0 +1,5 @@ +//! CLI commands related to liquidity mining. + +mod migrate_all_reserves; + +pub(crate) use migrate_all_reserves::command_upgrade_reserves_to_v2_1_0; diff --git a/token-lending/cli/src/liquidity_mining/migrate_all_reserves.rs b/token-lending/cli/src/liquidity_mining/migrate_all_reserves.rs new file mode 100644 index 00000000000..1d95e949154 --- /dev/null +++ b/token-lending/cli/src/liquidity_mining/migrate_all_reserves.rs @@ -0,0 +1,94 @@ +//! Temporary command that migrates all reserves to their new version. +//! +//! Delete once @v2.1.0 is fully deployed. +//! +//! Running this command before the upgrade: +//! > Found 1621 reserves to upgrade +//! > We'll spend ~54.46 $SOL on rent +//! > There are 87 reserves that were not used in the last 7 days as of 2025-05-08. + +use solana_account_decoder::UiAccountEncoding; +use solana_account_decoder::UiDataSliceConfig; +use solana_client::rpc_config::RpcAccountInfoConfig; +use solana_client::rpc_config::RpcProgramAccountsConfig; +use solana_client::rpc_filter::RpcFilterType; +use solana_sdk::compute_budget::ComputeBudgetInstruction; +use solana_sdk::message::Message; +use solana_sdk::native_token::LAMPORTS_PER_SOL; +use solana_sdk::program_pack::Pack; +use solana_sdk::transaction::Transaction; +use solend_sdk::instruction::upgrade_reserve_to_v2_1_0; +use solend_sdk::state::Reserve; +use solend_sdk::state::RESERVE_LEN_V2_0_2; + +use crate::send_transaction; +use crate::CommandResult; +use crate::Config; + +/// How many reserves to upgrade in a single transaction. +/// +/// We found the right value empirically. +const BATCH_SIZE: usize = 25; +/// How much to pay for compute units. +/// Helps lending txs. +const CU_PRICE: u64 = 3000; + +/// Upgrades all reserves to the new version. +pub(crate) fn command_upgrade_reserves_to_v2_1_0(config: &mut Config) -> CommandResult { + let reserve_new_rent = config + .rpc_client + .get_minimum_balance_for_rent_exemption(Reserve::LEN)?; + + // reserves before migration were sized to RESERVE_LEN_V2_0_2 and we're only interested in those + let filter = RpcProgramAccountsConfig { + filters: Some(vec![RpcFilterType::DataSize(RESERVE_LEN_V2_0_2 as _)]), + // with_context: Some(false), + account_config: RpcAccountInfoConfig { + data_slice: Some(UiDataSliceConfig { + offset: 1, + length: 8, // we don't need the data + }), + encoding: Some(UiAccountEncoding::Base64), + ..Default::default() + }, + ..Default::default() + }; + let reserves_to_upgrade = config + .rpc_client + .get_program_accounts_with_config(&config.lending_program_id, filter)?; + + println!("Found {} reserves to upgrade", reserves_to_upgrade.len()); + + let missing_rent: u64 = reserves_to_upgrade + .iter() + .map(|(_, acc)| reserve_new_rent.saturating_sub(acc.lamports)) + .sum(); + + println!( + "We'll spend ~{:.2} $SOL on rent", + missing_rent as f64 / LAMPORTS_PER_SOL as f64 + ); + + for reserves in reserves_to_upgrade.chunks(BATCH_SIZE) { + let mut ixs = vec![ComputeBudgetInstruction::set_compute_unit_price(CU_PRICE)]; + ixs.extend(reserves.iter().map(|(reserve_pubkey, _)| { + upgrade_reserve_to_v2_1_0( + config.lending_program_id, + *reserve_pubkey, + config.fee_payer.pubkey(), + ) + })); + + let recent_blockhash = config.rpc_client.get_latest_blockhash()?; + + let message = + Message::new_with_blockhash(&ixs, Some(&config.fee_payer.pubkey()), &recent_blockhash); + + let transaction = + Transaction::new(&vec![config.fee_payer.as_ref()], message, recent_blockhash); + + send_transaction(config, transaction)?; + } + + Ok(()) +} diff --git a/token-lending/cli/src/main.rs b/token-lending/cli/src/main.rs index afa365e7325..90b77ce694a 100644 --- a/token-lending/cli/src/main.rs +++ b/token-lending/cli/src/main.rs @@ -1,5 +1,9 @@ +mod lending_state; +mod liquidity_mining; + use lending_state::SolendState; +use liquidity_mining::command_upgrade_reserves_to_v2_1_0; use serde_json::Value; use solana_account_decoder::UiAccountEncoding; use solana_client::rpc_config::{RpcProgramAccountsConfig, RpcSendTransactionConfig}; @@ -11,7 +15,6 @@ use solend_program::{ instruction::set_lending_market_owner_and_config, state::{validate_reserve_config, RateLimiterConfig}, }; -use solend_sdk::instruction::upgrade_reserve_to_v2_1_0; use solend_sdk::{ instruction::{ liquidate_obligation_and_redeem_reserve_collateral, redeem_reserve_collateral, @@ -21,8 +24,6 @@ use solend_sdk::{ state::ReserveType, }; -mod lending_state; - use { clap::{ crate_description, crate_name, crate_version, value_t, App, AppSettings, Arg, ArgMatches, @@ -770,17 +771,8 @@ fn main() { ) ) .subcommand( - SubCommand::with_name("upgrade-reserve") - .about("Migrate reserve to version 2.1.0") - .arg( - Arg::with_name("reserve") - .long("reserve") - .validator(is_pubkey) - .value_name("PUBKEY") - .takes_value(true) - .required(true) - .help("Reserve address"), - ) + SubCommand::with_name("upgrade-all-reserves") + .about("Upgrade all reserves to version 2.1.0") ) .subcommand( SubCommand::with_name("update-reserve") @@ -1338,11 +1330,7 @@ fn main() { risk_authority_pubkey, ) } - ("upgrade-reserve", Some(arg_matches)) => { - let reserve_pubkey = pubkey_of(arg_matches, "reserve").unwrap(); - - command_upgrade_reserve_to_v2_1_0(&mut config, reserve_pubkey) - } + ("upgrade-all-reserves", _) => command_upgrade_reserves_to_v2_1_0(&mut config), ("update-reserve", Some(arg_matches)) => { let reserve_pubkey = pubkey_of(arg_matches, "reserve").unwrap(); let lending_market_owner_keypair = @@ -1992,29 +1980,6 @@ fn command_set_lending_market_owner_and_config( Ok(()) } -fn command_upgrade_reserve_to_v2_1_0(config: &mut Config, reserve_pubkey: Pubkey) -> CommandResult { - let recent_blockhash = config.rpc_client.get_latest_blockhash()?; - - let message = Message::new_with_blockhash( - &[ - ComputeBudgetInstruction::set_compute_unit_price(30101), - upgrade_reserve_to_v2_1_0( - config.lending_program_id, - reserve_pubkey, - config.fee_payer.pubkey(), - ), - ], - Some(&config.fee_payer.pubkey()), - &recent_blockhash, - ); - - let transaction = Transaction::new(&vec![config.fee_payer.as_ref()], message, recent_blockhash); - - send_transaction(config, transaction)?; - - Ok(()) -} - #[allow(clippy::too_many_arguments, clippy::unnecessary_unwrap)] fn command_update_reserve( config: &mut Config, diff --git a/token-lending/program/Cargo.toml b/token-lending/program/Cargo.toml index ae8a379dd44..e491762dd11 100644 --- a/token-lending/program/Cargo.toml +++ b/token-lending/program/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solend-program" -version.workspace = true +version = "2.1.0" description = "Solend Program" authors = ["Solend Maintainers "] repository = "https://github.com/solendprotocol/solana-program-library" diff --git a/token-lending/sdk/Cargo.toml b/token-lending/sdk/Cargo.toml index 26ddb0536e7..4002f16f5a1 100644 --- a/token-lending/sdk/Cargo.toml +++ b/token-lending/sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solend-sdk" -version.workspace = true +version = "2.1.0" description = "Solend Sdk" authors = ["Solend Maintainers "] repository = "https://github.com/solendprotocol/solana-program-library" diff --git a/token-lending/tests/liquidity-mining.ts b/token-lending/tests/liquidity-mining.ts index d8cd3aa2c8c..d1df90362e0 100644 --- a/token-lending/tests/liquidity-mining.ts +++ b/token-lending/tests/liquidity-mining.ts @@ -14,20 +14,21 @@ describe("liquidity mining", () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); - const TEST_RESERVE_FOR_UPGRADE = - "BgxfHJDzm44T7XG68MYKx7YisTjZu73tVovyZSjJMpmw"; - - it("Upgrades reserve to 2.1.0 via CLI", async () => { - // There's an ix that upgrades a reserve to 2.1.0. + it("Upgrades reserves to 2.1.0 via CLI", async () => { + // There's an ix that upgrades all program reserves to 2.1.0. // This ix is invocable via our CLI. // In this test case for comfort and more test coverage we invoke the CLI // command rather than crafting the ix ourselves. + // We check this reserve before & after the upgrade. + const SOME_TEST_RESERVE_TO_CHECK = + "BgxfHJDzm44T7XG68MYKx7YisTjZu73tVovyZSjJMpmw"; + const rpcUrl = anchor.getProvider().connection.rpcEndpoint; const reserveBefore = await anchor .getProvider() - .connection.getAccountInfo(new PublicKey(TEST_RESERVE_FOR_UPGRADE)); + .connection.getAccountInfo(new PublicKey(SOME_TEST_RESERVE_TO_CHECK)); expect(reserveBefore.data.length).to.eq(619); // old version data length const expectedRentBefore = await anchor @@ -36,7 +37,7 @@ describe("liquidity mining", () => { // some reserves have more rent expect(reserveBefore.lamports).to.be.greaterThanOrEqual(expectedRentBefore); - const command = `cargo run --quiet --bin solend-cli -- --url ${rpcUrl} upgrade-reserve --reserve ${TEST_RESERVE_FOR_UPGRADE}`; + const command = `cargo run --quiet --bin solend-cli -- --url ${rpcUrl} upgrade-all-reserves`; console.log(`\$ ${command}`); const cliProcess = exec(command); @@ -58,7 +59,7 @@ describe("liquidity mining", () => { const reserveAfter = await anchor .getProvider() - .connection.getAccountInfo(new PublicKey(TEST_RESERVE_FOR_UPGRADE)); + .connection.getAccountInfo(new PublicKey(SOME_TEST_RESERVE_TO_CHECK)); expect(reserveAfter.data.length).to.eq(5451); // new version data length const expectedRentAfter = await anchor