# Instructions
This document contains a list of all instructions related to the Parrot PAI stablecoin.
# Accounts Graph Visualization
For every instruction, we provide an accompanying graph for every accounts that must be passed in, and the relationships (if any) that these accounts must satisfy.
- Solid arrows are relationships that must be checked explicitly by our own program.
- Dotted arrows are relationships that exist, but checked by subprogram calls.
- The state struct of an account is given in between bracket.
- notation:
[<type> <mutability>?]
- For example:
[spl-token:Account mut]
denotes that this account should be an spl-tokenAccount
, and that it should be mutable.
- notation:
- A round circle is an user controlled account.
- If the user account needs to provide a signature (e.g. token owner for transfer), then it is marked as
signer
.
- If the user account needs to provide a signature (e.g. token owner for transfer), then it is marked as
PDA
denotes program derived account.
# InitDebtType (Admin)
Initializes a ProgramAccount<DebtType>
. This is done by the debt owner. There is no sanity checking right now, so the admin should take care to use the correct inputs otherwise the system would not work.
debt_originator
andinterests_holder
should be spl token accounts ofdebt_token
mint.debt_originator.owner
should be the debt_type PDA, withseeds = [debt_type.pubkey, nonce]
- This is not checked by the program
debt_type.owner
is checked whether it's signed.- This allows admin to throw away the key.
- This makes multisig owner easier to configure.
pub fn init_debt_type(ctx: Context<InitDebtType>, nonce: u8) -> Result<()>;
#[derive(Accounts)]
pub struct InitDebtType<'info> {
#[account(init)]
debt_type: ProgramAccount<'info, DebtType>,
debt_token: AccountInfo<'info>,
#[account("&debt_originator.mint == debt_token.key")]
debt_originator: CpiAccount<'info, TokenAccount>,
#[account("&interests_holder.mint == debt_token.key")]
interests_holder: CpiAccount<'info, TokenAccount>,
owner: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
# InitVaultType (Admin)
Initializes a ProgramAccount<VaultType>
. This is done by the debt owner, to create a new vault type that accepts a particular collateral token.
- Only the
debt_type.owner
should be able to initialize a new vault for thatdebt_type
. interest_accum_updated
should be set to current slotcollateral_token_holder.owner
should be the vault_type PDA, withseeds = [vault_type.pubkey, nonce]
pub fn init_vault_type(
ctx: Context<InitVaultType>,
nonce: u8,
minimum_collateral_ratio: u64,
liquidation_collateral_ratio: u64,
liquidation_penalty: u64,
interest_rate: u128,
debt_ceiling: u64,
) -> Result<()>;
#[derive(Accounts)]
pub struct InitVaultType<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(init)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(signer, "&debt_type.owner == owner.key")]
owner: AccountInfo<'info>,
collateral_token: AccountInfo<'info>,
#[account("&collateral_token_holder.mint == collateral_token.key")]
collateral_token_holder: CpiAccount<'info, TokenAccount>,
price_oracle: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
clock: Sysvar<'info, Clock>,
}
# InitVault (Admin)
Initializes a ProgramAccount<Vault>
.
vault
should be an associated account of the owner address.- Extra space should be reserved for the vault account for future upgrades.
vault_type.interest_accum
should update to the latest clock, thenvault.interest_accum
should be set tovault_type.interest_accum
- Should owner be signed?
pub fn init_vault(ctx: Context<InitVault>) -> Result<()>;
#[derive(Accounts)]
pub struct InitVault<'info> {
#[account(mut)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(init, associated = owner, with = vault_type, space = "240")]
vault: ProgramAccount<'info, Vault>,
#[account(signer)]
owner: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
system_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
# UpdateDebtType (Admin)
Admin can reconfigure the debt type.
- Changing
debt_type.owner
.- Program doesn't check if the new owner key is signed.
- It can be PDA controlled account, or a burn address.
- Program doesn't check if the new owner key is signed.
- Changing
debt_type.interests_holder
.- It should be a SPL token account that holds the debt token.
- Program doesn't check this.
pub fn update_debt_type(ctx: Context<UpdateDebtType>, update: DebtTypeUpdate) -> Result<()>;
#[derive(Accounts)]
pub struct UpdateDebtType<'info> {
#[account(mut)]
debt_type: ProgramAccount<'info, DebtType>,
#[account(signer, "&debt_type.owner == owner.key")]
owner: AccountInfo<'info>,
}
#[derive(Clone, Copy, AnchorDeserialize, AnchorSerialize)]
pub struct DebtTypeUpdate {
owner: Option<Pubkey>,
interests_holder: Option<Pubkey>,
}
# UpdateVaultType (Admin)
Admin can reconfigure a debt type.
- If updating
interest_rate
, will first accumulate interests up to the current slot using the older interest rate. - No sanity checks.
pub fn update_vault_type(ctx: Context<UpdateVaultType>, update: VaultTypeUpdate) -> Result<()>;
#[derive(Accounts)]
pub struct UpdateVaultType<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(mut, has_one = debt_type)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(signer, "&debt_type.owner == owner.key")]
owner: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
#[derive(Clone, Copy, AnchorDeserialize, AnchorSerialize)]
pub struct VaultTypeUpdate {
minimum_collateral_ratio: Option<u64>,
liquidation_collateral_ratio: Option<u64>,
liquidation_penalty: Option<u64>,
interest_rate: Option<u128>,
debt_ceiling: Option<u64>,
price_oracle: Option<Pubkey>,
}
# CollectVaultInterests (Admin)
Collect accrued interests. This should transfer vault_type.accrued_interests
amount of debt tokens from debt_type.debt_originator
to debt_type.interests_holder
.
#[derive(Accounts)]
pub struct CollectVaultInterests<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(mut, has_one = debt_type)]
vault_type: ProgramAccount<'info, VaultType>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,
#[account(mut, "&debt_type.debt_originator == debt_originator.key")]
debt_originator: AccountInfo<'info>,
debt_originator_authority: AccountInfo<'info>,
#[account(mut, "&debt_type.interests_holder == interests_holder.key")]
interests_holder: AccountInfo<'info>,
#[account(signer, "&debt_type.owner == owner.key")]
owner: AccountInfo<'info>,
}
# Borrow
Increase vault.debt_amount
, and transfer debt tokens from debt_type.debt_originator
to receiver
.
- After borrow, the collateral ratio must still be above
vault_type.minimum_collateral_ratio
. vault_type.total_debt
must not exceedvault_type.debt_ceiling
- Only the
vault.owner
can borrow. - If
debt_type.debt_originator
no longer has enough debt balance, this transaction should fail.- This acts as a global debt ceiling for the whole protocol.
- Our user-facing app will use the associated token address of
vault.owner
asreceiver
.- THis is not enforced by the program.
pub fn borrow(ctx: Context<Borrow>, amount: u64) -> Result<()>;
#[derive(Accounts)]
pub struct Borrow<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(mut, has_one = debt_type)]
vault_type: ProgramAccount<'info, VaultType>,
#[account("&vault_type.collateral_token == collateral_token_mint.to_account_info().key")]
collateral_token_mint: CpiAccount<'info, Mint>, //to get token decimal
#[account(mut, has_one = vault_type)]
vault: ProgramAccount<'info, Vault>,
#[account(signer, "&vault.owner == vault_owner.key")]
vault_owner: AccountInfo<'info>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,
#[account("&debt_type.debt_token == debt_token.to_account_info().key")]
debt_token: CpiAccount<'info, Mint>, //use Mint to get token decimal
#[account(mut, "&debt_type.debt_originator == debt_originator.key")]
debt_originator: AccountInfo<'info>,
#[account()] // PDA
debt_originator_authority: AccountInfo<'info>,
#[account(mut)]
receiver: AccountInfo<'info>,
#[account("&vault_type.price_oracle == oracle.key")]
oracle: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
# Stake
Increase vault.collateral_amount
by transferring collateral tokens from user into vault_type.collateral_token_holder
.
- Anyone can increase the collateral_amount, not just the vault owner.
pub fn stake(ctx: Context<Stake>, amount: u64) -> Result<()>;
#[derive(Accounts)]
pub struct Stake<'info> {
#[account(mut)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(mut, has_one = vault_type)]
vault: ProgramAccount<'info, Vault>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,
#[account(mut)]
collateral_from: AccountInfo<'info>,
#[account(signer)]
collateral_from_authority: AccountInfo<'info>,
#[account(mut, "&vault_type.collateral_token_holder == collateral_to.key")]
collateral_to: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
# Repay
Decrease vault.debt_amount
by transferring debt tokens from user back to the debt_type.debt_originator
.
- Anyone can pay down the debt of a vault, not necessarily the vault owner.
- If the repay
amount
is larger thanvault.debt_amount
, the excess should not be transferred. - If after repay, the remaining
vault.debt_amount
is less than 1 (the smallest unit of debt), then consider it dust and zerovault.debt_amount
.
pub fn repay(ctx: Context<Repay>, amount: u64) -> Result<()>;
#[derive(Accounts)]
pub struct Repay<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(mut, has_one = debt_type)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(mut, has_one = vault_type)]
vault: ProgramAccount<'info, Vault>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,
#[account(mut, "&debt_type.debt_token == debt_token.key")]
debt_token: AccountInfo<'info>,
#[account(mut)]
debt_from: AccountInfo<'info>,
#[account(signer)]
debt_from_authority: AccountInfo<'info>,
#[account(mut, "&debt_type.debt_originator == debt_originator.key")]
debt_originator: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
# Unstake
Decrease vault.collateral_amount
by transferring collateral tokens from vault_type.collateral_token_holder
to receiver
.
- Only
vault.owner
can unstake. - After unstake, the collateral ratio must still be above
vault_type.minimum_collateral_ratio
.
pub fn unstake(ctx: Context<Unstake>, amount: u64) -> Result<()>
#[derive(Accounts)]
pub struct Unstake<'info> {
debt_type: ProgramAccount<'info, DebtType>,
#[account(mut, has_one = debt_type)]
vault_type: ProgramAccount<'info, VaultType>,
#[account(mut, "&debt_type.debt_token == debt_token.to_account_info().key")]
debt_token: CpiAccount<'info, Mint>, //to get token decimal
#[account(mut, has_one = vault_type)]
vault: ProgramAccount<'info, Vault>,
#[account("&vault_type.price_oracle == oracle.key")]
oracle: AccountInfo<'info>,
#[account(mut, signer, "&vault.owner == vault_owner.key")]
vault_owner: AccountInfo<'info>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,
#[account(
mut,
"&vault_type.collateral_token == collateral_token.to_account_info().key"
)]
collateral_token: CpiAccount<'info, Mint>,
#[account(
mut,
"&vault_type.collateral_token_holder == collateral_token_holder.key"
)]
collateral_token_holder: AccountInfo<'info>,
// PDA of vault_type
collateral_token_holder_authority: AccountInfo<'info>,
// spl-token account to receive unstaked collateral tokens
#[account(mut)]
receiver: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
# Liquidate
TODO
- if
vault_type.priceOracle
is set toSTABLEQRACLE1111111111111111111111111111111
, it is considered a stable vault, and liquidation is disabled.