Repay
The repay instruction is the second half of our flash loan system. Thanks to the design of the loan instruction, the logic for repay is quite simple, as it only needs to:
Check that all balances have been correctly repaid using the
loanaccount.Close the
loanaccount, since it's no longer needed.
Required Accounts
borrower: The user who requested the flash loan. They provide the lamports for creating theloanaccount. Must be mutable.loan: The temporary account used to store theprotocol_token_accountand the finalbalancerequired. Must be mutable as it will be closed at the end of the instruction.
Here's the implementation:
pub struct RepayAccounts<'a> {
pub borrower: &'a AccountInfo,
pub loan: &'a AccountInfo,
pub token_accounts: &'a [AccountInfo],
}
impl<'a> TryFrom<&'a [AccountInfo]> for RepayAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let [borrower, loan, token_accounts @ ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Ok(Self {
borrower,
loan,
token_accounts,
})
}
}The token_accounts field is a dynamic array of accounts representing the protocol token accounts associated with the borrower's loan.
Instruction Data
No instruction data is needed because we use the balance field in the loan account to verify if the loan has been repaid.
Instruction Logic
We begin by parsing the accounts into the RepayAccounts struct.
pub struct Repay<'a> {
pub accounts: RepayAccounts<'a>,
}
impl<'a> TryFrom<&'a [AccountInfo]> for Repay<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
let accounts = RepayAccounts::try_from(accounts)?;
Ok(Self { accounts })
}
}
Next, we check if all loans have been repaid. We do this by retrieving the number of loans from the loan account and iterating through them. For each loan, we verify that the protocol_token_account is correct and that its balance is greater than or equal to the amount loaned.
impl<'a> Repay<'a> {
pub const DISCRIMINATOR: &'a u8 = &1;
pub fn process(&mut self) -> ProgramResult {
let loan_data = self.accounts.loan.try_borrow_data()?;
let loan_num = loan_data.len() / size_of::<LoanData>();
if loan_num.ne(&self.accounts.token_accounts.len()) {
return Err(ProgramError::InvalidAccountData);
}
// Process each pair of token accounts (protocol, borrower) with corresponding amounts
for i in 0..loan_num {
// Validate that protocol_ata is the same as the one in the loan account
let protocol_token_account = &self.accounts.token_accounts[i];
if unsafe { *(loan_data.as_ptr().add(i * size_of::<LoanData>()) as *const [u8; 32]) } != *protocol_token_account.key() {
return Err(ProgramError::InvalidAccountData);
}
// Check if the loan is already repaid
let balance = get_token_amount(&protocol_token_account.try_borrow_data()?)?;
let loan_balance = unsafe { *(loan_data.as_ptr().add(i * size_of::<LoanData>() + size_of::<[u8; 32]>()) as *const u64) };
if balance < loan_balance {
return Err(ProgramError::InvalidAccountData);
}
}
//..
}
}We can then proceed to close the loan account and reclaim the rent, as it's no longer needed:
drop(loan_data);
// Close the loan account and give back the lamports to the borrower
unsafe {
*self.accounts.borrower.borrow_mut_lamports_unchecked() += *self.accounts.loan.borrow_lamports_unchecked();
// There is no need to manually zero out lamports in the loan account because it is done in the close_unchecked function
self.accounts.loan.close_unchecked();
}