diff --git a/token-lending/LIQUIDITY_MINING.md b/token-lending/LIQUIDITY_MINING.md index cce28cfa425..bc2af6f25f4 100644 --- a/token-lending/LIQUIDITY_MINING.md +++ b/token-lending/LIQUIDITY_MINING.md @@ -50,14 +50,10 @@ A reward vault authority is a PDA that is used to sign CPIs into the token progr [ b"RewardVaultAuthority", lending_market_key, - reserve_key, - reward_mint_key, + vault_token_account_key, ] ``` -> TBD: Should we use the reward vault token account pubkey instead to create a 1-1 relationship between the authority and the vault? -> What will be easier for the clients to use? - ### `add_pool_reward` Admin only ix that adds a new pool reward to a reserve's reward manager, either a deposit or a borrow one. @@ -224,11 +220,13 @@ A lending market will be automatically upgraded on the first mutable ix. ## Outstanding work -- [ ] Review feature parity with Suilend -- [ ] Consider changing the reward vault authority seed +- [x] Review feature parity with Suilend + - Looped rewards are not implemented but that's ok +- [x] Consider changing the reward vault authority seed - [ ] Consider having another admin account to manage the rewards -- [ ] Consider spending some rent to the obligations from the reclaimed merkle-tree reward distributor -- [ ] Discuss CU limits with the Save client team +- [x] Consider spending some rent to the obligations from the reclaimed merkle-tree reward distributor + - We will fund the obligations to support some of the extra rent +- [x] Discuss CU limits with the Save client team diff --git a/token-lending/program/src/processor/liquidity_mining.rs b/token-lending/program/src/processor/liquidity_mining.rs index 1a906ca4306..14c395f79be 100644 --- a/token-lending/program/src/processor/liquidity_mining.rs +++ b/token-lending/program/src/processor/liquidity_mining.rs @@ -48,6 +48,7 @@ struct CheckAndUnpackPoolRewardAccounts<'a, 'info> { reward_authority_info: &'a AccountInfo<'info>, lending_market_info: &'a AccountInfo<'info>, token_program_info: &'a AccountInfo<'info>, + reward_token_vault_info: &'a AccountInfo<'info>, } /// Does all the checks of [check_and_unpack_pool_reward_accounts] and additionally: @@ -83,7 +84,7 @@ fn check_and_unpack_pool_reward_accounts_for_admin_ixs<'a, 'info>( /// * ✅ `lending_market_info` unpacks /// * ✅ `token_program_info` matches `lending_market_info` /// * ✅ `reward_mint_info` belongs to the token program -/// * ✅ `reward_authority_info` is seed of `lending_market_info`, `reserve_info`, `reward_mint_info` +/// * ✅ `reward_authority_info` is seed of `lending_market_info`, `reward_token_vault_info` fn check_and_unpack_pool_reward_accounts<'a, 'info>( program_id: &Pubkey, bumps: Bumps, @@ -93,6 +94,7 @@ fn check_and_unpack_pool_reward_accounts<'a, 'info>( reward_authority_info, lending_market_info, token_program_info, + reward_token_vault_info, }: CheckAndUnpackPoolRewardAccounts<'a, 'info>, ) -> Result<(LendingMarket, ReserveBorrow<'a, 'info>), ProgramError> { let reserve = ReserveBorrow::new_mut(program_id, reserve_info)?; @@ -121,8 +123,7 @@ fn check_and_unpack_pool_reward_accounts<'a, 'info>( let expected_reward_vault_authority = create_reward_vault_authority( program_id, lending_market_info.key, - reserve_info.key, - reward_mint_info.key, + reward_token_vault_info.key, bumps.reward_authority, )?; if expected_reward_vault_authority != *reward_authority_info.key { @@ -244,7 +245,7 @@ mod tests { .expect_err("Should fail"); } - /// ❌ `reward_authority_info` is seed of `lending_market_info`, `reserve_info`, `reward_mint_info` + /// ❌ `reward_authority_info` is seed of `lending_market_info`, `reward_token_vault_info` #[test] fn test_fails_if_reward_authority_info_is_not_seed() { let (mut account_info_builders, og_bumps) = @@ -256,8 +257,7 @@ mod tests { let (new_reward_authority, new_reward_authority_bump) = find_reward_vault_authority( &crate::id(), &Pubkey::new_unique(), - &account_info_builders.reserve.key, - &account_info_builders.mint.key, + &account_info_builders.reward_token_vault.key, ); account_info_builders.reward_authority.key = new_reward_authority; account_info_builders @@ -270,32 +270,12 @@ mod tests { ) .expect_err("Should fail"); - // wrong reserve + // wrong vault let (new_reward_authority, new_reward_authority_bump) = find_reward_vault_authority( &crate::id(), &account_info_builders.lending_market.key, &Pubkey::new_unique(), - &account_info_builders.mint.key, - ); - account_info_builders.reward_authority.key = new_reward_authority; - account_info_builders - .clone() - .check_and_unpack_pool_reward_accounts( - crate::id(), - Bumps { - reward_authority: new_reward_authority_bump, - }, - ) - .expect_err("Should fail"); - - // wrong mint - - let (new_reward_authority, new_reward_authority_bump) = find_reward_vault_authority( - &crate::id(), - &account_info_builders.lending_market.key, - &account_info_builders.reserve.key, - &Pubkey::new_unique(), ); account_info_builders.reward_authority.key = new_reward_authority; account_info_builders @@ -361,6 +341,7 @@ mod tests { reserve: AccountInfoBuilder, reward_authority: AccountInfoBuilder, token_program: AccountInfoBuilder, + reward_token_vault: AccountInfoBuilder, } #[derive(Clone)] @@ -394,10 +375,10 @@ mod tests { lending_market: lending_market.key, ..Default::default() }); + let reward_token_vault = AccountInfoBuilder::new_reward_token_vault(); let (reward_authority, bumps) = AccountInfoBuilder::new_reward_authority( &lending_market.key, - &reserve.key, - &mint.key, + &reward_token_vault.key, ); ( @@ -408,6 +389,7 @@ mod tests { reserve, reward_authority, token_program, + reward_token_vault, }, bumps, ) @@ -423,6 +405,7 @@ mod tests { let reserve_info = self.reserve.as_account_info(); let reward_authority_info = self.reward_authority.as_account_info(); let token_program_info = self.token_program.as_account_info(); + let reward_token_vault_info = self.reward_token_vault.as_account_info(); check_and_unpack_pool_reward_accounts( &program_id, @@ -433,6 +416,7 @@ mod tests { reward_authority_info: &reward_authority_info, reward_mint_info: &mint_info, token_program_info: &token_program_info, + reward_token_vault_info: &reward_token_vault_info, }, ) .map(drop) @@ -449,6 +433,7 @@ mod tests { let reward_authority_info = self.reward_authority.as_account_info(); let token_program_info = self.token_program.as_account_info(); let lending_market_owner_info = self.lending_market_owner.as_account_info(); + let reward_token_vault_info = self.reward_token_vault.as_account_info(); check_and_unpack_pool_reward_accounts_for_admin_ixs( &program_id, @@ -459,6 +444,7 @@ mod tests { reward_authority_info: &reward_authority_info, reward_mint_info: &mint_info, token_program_info: &token_program_info, + reward_token_vault_info: &reward_token_vault_info, }, &lending_market_owner_info, ) @@ -549,14 +535,12 @@ mod tests { fn new_reward_authority( lending_market_key: &Pubkey, - reserve_key: &Pubkey, - reward_mint_key: &Pubkey, + reward_token_vault_key: &Pubkey, ) -> (Self, Bumps) { let (key, bump) = find_reward_vault_authority( &crate::id(), lending_market_key, - reserve_key, - reward_mint_key, + reward_token_vault_key, ); let s = Self { @@ -590,5 +574,18 @@ mod tests { is_executable: false, } } + + fn new_reward_token_vault() -> Self { + Self { + key: Pubkey::new_unique(), + lamports: 0, + data: vec![], + owner: spl_token::id(), + rent_epoch: 0, + is_signer: false, + is_writable: true, + is_executable: false, + } + } } } diff --git a/token-lending/program/src/processor/liquidity_mining/add_pool_reward.rs b/token-lending/program/src/processor/liquidity_mining/add_pool_reward.rs index abcb03d6e45..cd63db66c65 100644 --- a/token-lending/program/src/processor/liquidity_mining/add_pool_reward.rs +++ b/token-lending/program/src/processor/liquidity_mining/add_pool_reward.rs @@ -44,7 +44,7 @@ struct AddPoolRewardAccounts<'a, 'info> { /// ✅ matches `reward_mint_info` /// ✅ is writable reward_token_source_info: &'a AccountInfo<'info>, - /// ✅ seed of `lending_market_info`, `reserve_info`, `reward_mint_info` + /// ✅ seed of `lending_market_info`, `reward_token_vault_info` reward_authority_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program /// ✅ has no data @@ -157,6 +157,7 @@ impl<'a, 'info> AddPoolRewardAccounts<'a, 'info> { reward_authority_info, lending_market_info, token_program_info, + reward_token_vault_info, }, lending_market_owner_info, )?; diff --git a/token-lending/program/src/processor/liquidity_mining/claim_user_reward.rs b/token-lending/program/src/processor/liquidity_mining/claim_user_reward.rs index 45dd377e619..d53d6b7cdc0 100644 --- a/token-lending/program/src/processor/liquidity_mining/claim_user_reward.rs +++ b/token-lending/program/src/processor/liquidity_mining/claim_user_reward.rs @@ -45,8 +45,8 @@ struct ClaimUserReward<'a, 'info> { /// ✅ is writable _reserve_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program - reward_mint_info: &'a AccountInfo<'info>, - /// ✅ seed of `lending_market_info`, `reserve_info`, `reward_mint_info` + _reward_mint_info: &'a AccountInfo<'info>, + /// ✅ seed of `lending_market_info`, `reward_token_vault_info` reward_authority_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program /// ✅ unpacks to a [TokenAccount] @@ -123,8 +123,7 @@ pub(crate) fn process( authority_signer_seeds: &[ reward_vault_authority_seeds( accounts.lending_market_info.key, - &accounts.reserve.key(), - accounts.reward_mint_info.key, + accounts.reward_token_vault_info.key, ) .as_slice(), &[&[reward_authority_bump]], @@ -215,6 +214,7 @@ impl<'a, 'info> ClaimUserReward<'a, 'info> { reward_authority_info, lending_market_info, token_program_info, + reward_token_vault_info, }, )?; @@ -286,7 +286,7 @@ impl<'a, 'info> ClaimUserReward<'a, 'info> { obligation_info, obligation_owner_token_account_info, _reserve_info: reserve_info, - reward_mint_info, + _reward_mint_info: reward_mint_info, reward_authority_info, reward_token_vault_info, lending_market_info, diff --git a/token-lending/program/src/processor/liquidity_mining/close_pool_reward.rs b/token-lending/program/src/processor/liquidity_mining/close_pool_reward.rs index cb0309e58ff..31a445a3d91 100644 --- a/token-lending/program/src/processor/liquidity_mining/close_pool_reward.rs +++ b/token-lending/program/src/processor/liquidity_mining/close_pool_reward.rs @@ -32,15 +32,15 @@ struct ClosePoolRewardAccounts<'a, 'info> { /// ✅ unpacks /// ✅ belongs to `lending_market_info` /// ✅ is writable - reserve_info: &'a AccountInfo<'info>, + _reserve_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program - reward_mint_info: &'a AccountInfo<'info>, + _reward_mint_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program /// ✅ owned by `lending_market_owner_info` /// ✅ matches `reward_mint_info` /// ✅ is writable reward_token_destination_info: &'a AccountInfo<'info>, - /// ✅ seed of `lending_market_info`, `reserve_info`, `reward_mint_info` + /// ✅ seed of `lending_market_info`, `reward_token_vault_info` reward_authority_info: &'a AccountInfo<'info>, /// ❓ we don't know whether it matches vault in the [Reserve] /// ✅ is writable @@ -96,8 +96,7 @@ pub(crate) fn process( let signer_seeds = [ reward_vault_authority_seeds( accounts.lending_market_info.key, - accounts.reserve_info.key, - accounts.reward_mint_info.key, + accounts.reward_token_vault_info.key, ) .as_slice(), &[&bump_seed], @@ -159,6 +158,7 @@ impl<'a, 'info> ClosePoolRewardAccounts<'a, 'info> { reward_authority_info, lending_market_info, token_program_info, + reward_token_vault_info, }, lending_market_owner_info, )?; @@ -196,8 +196,8 @@ impl<'a, 'info> ClosePoolRewardAccounts<'a, 'info> { } Ok(Self { - reserve_info, - reward_mint_info, + _reserve_info: reserve_info, + _reward_mint_info: reward_mint_info, reward_token_destination_info, reward_authority_info, reward_token_vault_info, diff --git a/token-lending/program/src/processor/liquidity_mining/edit_pool_reward.rs b/token-lending/program/src/processor/liquidity_mining/edit_pool_reward.rs index 495616afc0f..c4bb8fb4e82 100644 --- a/token-lending/program/src/processor/liquidity_mining/edit_pool_reward.rs +++ b/token-lending/program/src/processor/liquidity_mining/edit_pool_reward.rs @@ -45,12 +45,12 @@ struct EditPoolRewardAccounts<'a, 'info> { /// ✅ is writable _reserve_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program - reward_mint_info: &'a AccountInfo<'info>, + _reward_mint_info: &'a AccountInfo<'info>, /// ✅ belongs to the token program /// ✅ matches `reward_mint_info` /// ✅ is writable lending_market_reward_token_account_info: &'a AccountInfo<'info>, - /// ✅ seed of `lending_market_info`, `reserve_info`, `reward_mint_info` + /// ✅ seed of `lending_market_info`, `reward_token_vault_info` reward_authority_info: &'a AccountInfo<'info>, /// ❓ we don't know whether it matches the reward vault pubkey stored in [Reserve] /// ✅ is writable @@ -124,8 +124,7 @@ pub(crate) fn process( authority_signer_seeds: &[ reward_vault_authority_seeds( accounts.lending_market_info.key, - &accounts.reserve.key(), - accounts.reward_mint_info.key, + accounts.reward_token_vault_info.key, ) .as_slice(), &[&[reward_authority_bump]], @@ -160,6 +159,7 @@ impl<'a, 'info> EditPoolRewardAccounts<'a, 'info> { reward_authority_info, lending_market_info, token_program_info, + reward_token_vault_info, }, lending_market_owner_info, )?; @@ -192,7 +192,7 @@ impl<'a, 'info> EditPoolRewardAccounts<'a, 'info> { Ok(Self { _reserve_info: reserve_info, - reward_mint_info, + _reward_mint_info: reward_mint_info, lending_market_reward_token_account_info: reward_token_destination_info, reward_authority_info, reward_token_vault_info, diff --git a/token-lending/program/tests/helpers/solend_program_test.rs b/token-lending/program/tests/helpers/solend_program_test.rs index 00f44e34f1d..9956e13f529 100644 --- a/token-lending/program/tests/helpers/solend_program_test.rs +++ b/token-lending/program/tests/helpers/solend_program_test.rs @@ -948,8 +948,7 @@ impl Info { let (reward_authority_pda, reward_authority_bump) = find_reward_vault_authority( &solend_program::id(), &self.pubkey, - &reserve.pubkey, - &reward.mint, + &reward.vault.pubkey(), ); let instructions = [ @@ -998,8 +997,7 @@ impl Info { let (reward_authority_pda, reward_authority_bump) = find_reward_vault_authority( &solend_program::id(), &self.pubkey, - &reserve.pubkey, - &reward.mint, + &reward.vault.pubkey(), ); let instructions = [ @@ -1036,8 +1034,7 @@ impl Info { let (reward_authority_pda, reward_authority_bump) = find_reward_vault_authority( &solend_program::id(), &self.pubkey, - &reserve.pubkey, - &reward.mint, + &reward.vault.pubkey(), ); let instructions = [ @@ -1073,8 +1070,7 @@ impl Info { let (reward_authority_pda, reward_authority_bump) = find_reward_vault_authority( &solend_program::id(), &self.pubkey, - &reserve.pubkey, - &reward.mint, + &reward.vault.pubkey(), ); let instructions = [ diff --git a/token-lending/sdk/src/instruction.rs b/token-lending/sdk/src/instruction.rs index 11adbf39cca..9b203aa5975 100644 --- a/token-lending/sdk/src/instruction.rs +++ b/token-lending/sdk/src/instruction.rs @@ -542,8 +542,7 @@ pub enum LendingInstruction { /// `[]` Derived reserve pool reward authority. Seed: /// * b"RewardVaultAuthority" /// * Lending market account pubkey - /// * Reserve account pubkey - /// * Reward mint pubkey + /// * Vault token account pubkey /// `[writable]` Uninitialized rent-exempt account that will hold reward tokens. /// `[]` Lending market account. /// `[signer]` Lending market owner. @@ -575,8 +574,7 @@ pub enum LendingInstruction { /// `[]` Derived reserve pool reward authority. Seed: /// * b"RewardVaultAuthority" /// * Lending market account pubkey - /// * Reserve account pubkey - /// * Reward mint pubkey + /// * Vault token account pubkey /// `[writable]` Reward vault token account. /// `[]` Lending market account. /// `[signer]` Lending market owner. @@ -606,8 +604,7 @@ pub enum LendingInstruction { /// `[]` Derived reserve pool reward authority. Seed: /// * b"RewardVaultAuthority" /// * Lending market account pubkey - /// * Reserve account pubkey - /// * Reward mint pubkey + /// * Vault token account pubkey /// `[writable]` Reward vault token account. /// `[]` Lending market account. /// `[signer]` Lending market owner. @@ -637,8 +634,7 @@ pub enum LendingInstruction { /// `[]` Derived reserve pool reward authority. Seed: /// * b"RewardVaultAuthority" /// * Lending market account pubkey - /// * Reserve account pubkey - /// * Reward mint pubkey + /// * Vault token account pubkey /// `[writable]` Reward vault token account. /// `[]` Lending market account. /// `[]` Token program. @@ -2272,8 +2268,7 @@ pub fn close_pool_reward( /// `[]` Derived reserve pool reward authority. Seed: /// * b"RewardVaultAuthority" /// * Lending market account pubkey -/// * Reserve account pubkey -/// * Reward mint pubkey +/// * Vault token account pubkey /// `[writable]` Reward vault token account. /// `[]` Lending market account. /// `[]` Token program. @@ -2314,11 +2309,10 @@ pub fn claim_pool_reward( pub fn find_reward_vault_authority( program_id: &Pubkey, lending_market_key: &Pubkey, - reserve_key: &Pubkey, - reward_mint_key: &Pubkey, + reward_token_vault_key: &Pubkey, ) -> (Pubkey, u8) { Pubkey::find_program_address( - &reward_vault_authority_seeds(lending_market_key, reserve_key, reward_mint_key), + &reward_vault_authority_seeds(lending_market_key, reward_token_vault_key), program_id, ) } @@ -2327,14 +2321,12 @@ pub fn find_reward_vault_authority( pub fn create_reward_vault_authority( program_id: &Pubkey, lending_market_key: &Pubkey, - reserve_key: &Pubkey, - reward_mint_key: &Pubkey, + reward_token_vault_key: &Pubkey, bump: u8, ) -> Result { Pubkey::create_program_address( &[ - reward_vault_authority_seeds(lending_market_key, reserve_key, reward_mint_key) - .as_slice(), + reward_vault_authority_seeds(lending_market_key, reward_token_vault_key).as_slice(), &[&[bump]], ] .concat(), @@ -2345,14 +2337,12 @@ pub fn create_reward_vault_authority( /// Returns seeds to derive the reward vault authority PDA address. pub fn reward_vault_authority_seeds<'keys>( lending_market_key: &'keys Pubkey, - reserve_key: &'keys Pubkey, - reward_mint_key: &'keys Pubkey, -) -> [&'keys [u8]; 4] { + reward_token_vault_key: &'keys Pubkey, +) -> [&'keys [u8]; 3] { [ b"RewardVaultAuthority", lending_market_key.as_ref(), - reserve_key.as_ref(), - reward_mint_key.as_ref(), + reward_token_vault_key.as_ref(), ] }