Skip to content

Commit

Permalink
Add processing vesting into realm #5 (#6)
Browse files Browse the repository at this point in the history
* Add processing vesting into realm #5

* Add checks for realm accounts #5

* Got rid of warnings; simplify account list (remove Rent & Clock) #5

Co-authored-by: Semen Medvedev <[email protected]>
  • Loading branch information
s-medvedev and Semen Medvedev authored Mar 23, 2022
1 parent dfd04ba commit 71cad7a
Show file tree
Hide file tree
Showing 9 changed files with 831 additions and 46 deletions.
1 change: 1 addition & 0 deletions addin-vesting/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ spl-associated-token-account = { version = "1.0.2", features = ["no-entrypoint"]
#spl-governance-tools = { version = "0.1.2" }
spl-governance = { path="/mnt/working/solana/solana-program-library.git/governance/program", features = ["no-entrypoint"] }
spl-governance-tools = { path="/mnt/working/solana/solana-program-library.git/governance/tools", version = "0.1.2" }
spl-governance-addin-api = { path="/mnt/working/solana/solana-program-library.git/governance/addin-api", version = "0.1.1" }
arbitrary = { version = "0.4", features = ["derive"], optional = true }
honggfuzz = { version = "0.5", optional = true }

Expand Down
34 changes: 31 additions & 3 deletions addin-vesting/program/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
use num_derive::FromPrimitive;
use solana_program::{decode_error::DecodeError, program_error::ProgramError};
use solana_program::{
decode_error::DecodeError,
program_error::{ProgramError, PrintProgramError},
msg,
};
use thiserror::Error;

/// Errors that may be returned by the Token vesting program.
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum VestingError {
// Invalid instruction
#[error("Invalid Instruction")]
InvalidInstruction
InvalidInstruction,

#[error("Missing realm accounts")]
MissingRealmAccounts,

#[error("Invalid Realm account")]
InvalidRealmAccount,

#[error("Invalid VoterWeightRecord account address")]
InvalidVoterWeightRecordAccountAddress,

#[error("Invalid MaxVoterWeightRecord account address")]
InvalidMaxVoterWeightRecordAccountAddress,

#[error("Invalid VoterWeightRecord linkage")]
InvalidVoterWeightRecordLinkage,

#[error("Invalid MaxVoterWeightRecord linkage")]
InvalidMaxVoterWeightRecordLinkage,
}

impl From<VestingError> for ProgramError {
Expand All @@ -21,3 +42,10 @@ impl<T> DecodeError<T> for VestingError {
"VestingError"
}
}

impl PrintProgramError for VestingError {
fn print<E>(&self)
{
msg!("VESTING-ERROR: {}", &self.to_string());
}
}
149 changes: 133 additions & 16 deletions addin-vesting/program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use crate::state::VestingSchedule;
use crate::{
state::VestingSchedule,
voter_weight::get_voter_weight_record_address,
max_voter_weight::get_max_voter_weight_record_address,
};

use solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
system_program,
sysvar,
};

use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use spl_governance::state::token_owner_record::get_token_owner_record_address;

#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;
Expand Down Expand Up @@ -71,7 +75,6 @@ pub enum VestingInstruction {
/// 5. `[writable]` The source spl-token account (from account)
/// 6. `[]` The Vesting Owner account
/// 7. `[signer]` Payer
/// 8. `[]` Rent sysvar
///
/// Optional part (vesting for Realm)
/// 8. `[]` The Governance program account
Expand All @@ -87,24 +90,22 @@ pub enum VestingInstruction {
},



/// Unlocks a simple vesting contract (SVC) - can only be invoked by the program itself
/// Accounts expected by this instruction:
///
/// * Single owner
/// 0. `[]` The spl-token program account
/// 1. `[]` The clock sysvar account
/// 2. `[writable]` The vesting account (vesting owner account: PDA [seeds])
/// 3. `[writable]` The vesting spl-token account (vesting balance account)
/// 4. `[writable]` The destination spl-token account
/// 5. `[signer]` The Vesting Owner account
/// 1. `[writable]` The vesting account (vesting owner account: PDA [seeds])
/// 2. `[writable]` The vesting spl-token account (vesting balance account)
/// 3. `[writable]` The destination spl-token account
/// 4. `[signer]` The Vesting Owner account
///
/// Optional part (vesting for Realm)
/// 6. `[]` The Governance program account
/// 7. `[]` The Realm account
/// 8. `[]` Governing Owner Record. PDA seeds (governance program): ['governance', realm, token_mint, vesting_owner]
/// 9. `[writable]` The VoterWeightRecord. PDA seeds: ['voter_weight', realm, token_mint, vesting_owner]
/// 10. `[writable]` The MaxVoterWeightRecord. PDA seeds: ['max_voter_weight', realm, token_mint]
/// 5. `[]` The Governance program account
/// 6. `[]` The Realm account
/// 7. `[]` Governing Owner Record. PDA seeds (governance program): ['governance', realm, token_mint, vesting_owner]
/// 8. `[writable]` The VoterWeightRecord. PDA seeds: ['voter_weight', realm, token_mint, vesting_owner]
/// 9. `[writable]` The MaxVoterWeightRecord. PDA seeds: ['max_voter_weight', realm, token_mint]
///
Withdraw {
#[allow(dead_code)]
Expand Down Expand Up @@ -157,7 +158,50 @@ pub fn deposit(
AccountMeta::new(*source_token_account, false),
AccountMeta::new_readonly(*vesting_owner, false),
AccountMeta::new_readonly(*payer, true),
AccountMeta::new_readonly(sysvar::rent::id(), false),
];

let instruction = VestingInstruction::Deposit { seeds, schedules };

Ok(Instruction {
program_id: *program_id,
accounts,
data: instruction.try_to_vec().unwrap(),
})
}

/// Creates a `Deposit` instruction to create and initialize the vesting token account
/// inside the Realm
pub fn deposit_with_realm(
program_id: &Pubkey,
token_program_id: &Pubkey,
seeds: [u8; 32],
vesting_token_account: &Pubkey,
source_token_owner: &Pubkey,
source_token_account: &Pubkey,
vesting_owner: &Pubkey,
payer: &Pubkey,
schedules: Vec<VestingSchedule>,
governance_id: &Pubkey,
realm: &Pubkey,
mint: &Pubkey,
) -> Result<Instruction, ProgramError> {
let vesting_account = Pubkey::create_program_address(&[&seeds], program_id)?;
let voting_weight_record_account = get_voter_weight_record_address(program_id, realm, mint, vesting_owner);
let max_voting_weight_record_account = get_max_voter_weight_record_address(program_id, realm, mint);
let accounts = vec![
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(*token_program_id, false),
AccountMeta::new(vesting_account, false),
AccountMeta::new(*vesting_token_account, false),
AccountMeta::new_readonly(*source_token_owner, true),
AccountMeta::new(*source_token_account, false),
AccountMeta::new_readonly(*vesting_owner, false),
AccountMeta::new_readonly(*payer, true),

AccountMeta::new_readonly(*governance_id, false),
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(voting_weight_record_account, false),
AccountMeta::new(max_voting_weight_record_account, false),
];

let instruction = VestingInstruction::Deposit { seeds, schedules };
Expand All @@ -181,7 +225,6 @@ pub fn withdraw(
let vesting_account = Pubkey::create_program_address(&[&seeds], program_id)?;
let accounts = vec![
AccountMeta::new_readonly(*token_program_id, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new(vesting_account, false),
AccountMeta::new(*vesting_token_account, false),
AccountMeta::new(*destination_token_account, false),
Expand All @@ -197,6 +240,45 @@ pub fn withdraw(
})
}

/// Creates a `Withdraw` instruction with realm
pub fn withdraw_with_realm(
program_id: &Pubkey,
token_program_id: &Pubkey,
seeds: [u8; 32],
vesting_token_account: &Pubkey,
destination_token_account: &Pubkey,
vesting_owner: &Pubkey,
governance_id: &Pubkey,
realm: &Pubkey,
mint: &Pubkey,
) -> Result<Instruction, ProgramError> {
let vesting_account = Pubkey::create_program_address(&[&seeds], program_id)?;
let owner_record_account = get_token_owner_record_address(governance_id, realm, mint, vesting_owner);
let voting_weight_record_account = get_voter_weight_record_address(program_id, realm, mint, vesting_owner);
let max_voting_weight_record_account = get_max_voter_weight_record_address(program_id, realm, mint);
let accounts = vec![
AccountMeta::new_readonly(*token_program_id, false),
AccountMeta::new(vesting_account, false),
AccountMeta::new(*vesting_token_account, false),
AccountMeta::new(*destination_token_account, false),
AccountMeta::new_readonly(*vesting_owner, true),

AccountMeta::new_readonly(*governance_id, false),
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(owner_record_account, false),
AccountMeta::new(voting_weight_record_account, false),
AccountMeta::new(max_voting_weight_record_account, false),
];

let instruction = VestingInstruction::Withdraw { seeds };

Ok(Instruction {
program_id: *program_id,
accounts,
data: instruction.try_to_vec().unwrap(),
})
}

/// Creates a `Withdraw` instruction
pub fn change_owner(
program_id: &Pubkey,
Expand All @@ -220,6 +302,41 @@ pub fn change_owner(
})
}

/// Creates a `Withdraw` instruction
pub fn change_owner_with_realm(
program_id: &Pubkey,
seeds: [u8; 32],
vesting_owner: &Pubkey,
new_vesting_owner: &Pubkey,
governance_id: &Pubkey,
realm: &Pubkey,
mint: &Pubkey,
) -> Result<Instruction, ProgramError> {
let vesting_account = Pubkey::create_program_address(&[&seeds], program_id)?;
let current_owner_record_account = get_token_owner_record_address(governance_id, realm, mint, vesting_owner);
let current_voting_weight_record_account = get_voter_weight_record_address(program_id, realm, mint, vesting_owner);
let new_voting_weight_record_account = get_voter_weight_record_address(program_id, realm, mint, new_vesting_owner);
let accounts = vec![
AccountMeta::new(vesting_account, false),
AccountMeta::new(*vesting_owner, true),
AccountMeta::new(*new_vesting_owner, false),

AccountMeta::new_readonly(*governance_id, false),
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(current_owner_record_account, false),
AccountMeta::new(current_voting_weight_record_account, false),
AccountMeta::new(new_voting_weight_record_account, false),
];

let instruction = VestingInstruction::ChangeOwner { seeds };

Ok(Instruction {
program_id: *program_id,
accounts,
data: instruction.try_to_vec().unwrap(),
})
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions addin-vesting/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ pub mod entrypoint;
pub mod error;
pub mod instruction;
pub mod state;
pub mod voter_weight;
pub mod max_voter_weight;

pub mod processor;
91 changes: 91 additions & 0 deletions addin-vesting/program/src/max_voter_weight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::error::VestingError;
use solana_program::{
pubkey::Pubkey,
program_error::ProgramError,
account_info::AccountInfo,
rent::Rent,
sysvar::Sysvar,
};
use spl_governance::addins::max_voter_weight::get_max_voter_weight_record_data;
use spl_governance_tools::account::create_and_serialize_account_signed;

pub use spl_governance_addin_api::max_voter_weight::MaxVoterWeightRecord;

/// Returns MaxVoterWeightRecord PDA seeds
pub fn get_max_voter_weight_record_seeds<'a>(
realm: &'a Pubkey,
mint: &'a Pubkey,
) -> [&'a [u8]; 3] {
[b"max_voter_weight", realm.as_ref(), mint.as_ref()]
}

/// Returns MaxVoterWeightRecord PDA address
pub fn get_max_voter_weight_record_address(program_id: &Pubkey, realm: &Pubkey, mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(&get_max_voter_weight_record_seeds(realm, mint), program_id).0
}

/// Deserializes MaxVoterWeightRecord account and checks owner program
pub fn get_max_voter_weight_record_data_for_seeds(
program_id: &Pubkey,
max_voter_weight_record_info: &AccountInfo,
max_voter_weight_record_seeds: &[&[u8]],
) -> Result<MaxVoterWeightRecord, ProgramError> {
let (max_voter_weight_record_address, _) =
Pubkey::find_program_address(max_voter_weight_record_seeds, program_id);

if max_voter_weight_record_address != *max_voter_weight_record_info.key {
return Err(VestingError::InvalidMaxVoterWeightRecordAccountAddress.into());
}

get_max_voter_weight_record_data(program_id, max_voter_weight_record_info)
}

/// Deserializes MaxVoterWeightRecord account and checks owner program and linkage
pub fn get_max_voter_weight_record_data_checked(
program_id: &Pubkey,
record_info: &AccountInfo,
realm: &Pubkey,
mint: &Pubkey,
) -> Result<MaxVoterWeightRecord, ProgramError> {
let seeds = get_max_voter_weight_record_seeds(realm, mint);
let record = get_max_voter_weight_record_data_for_seeds(program_id, record_info, &seeds)?;
if record.realm != *realm ||
record.governing_token_mint != *mint {
return Err(VestingError::InvalidMaxVoterWeightRecordLinkage.into())
}
Ok(record)
}

/// Create Voter Weight Record
pub fn create_max_voter_weight_record<'a, I>(
program_id: &Pubkey,
realm: &Pubkey,
mint: &Pubkey,
payer_account: &AccountInfo<'a>,
record_account: &AccountInfo<'a>,
system_program_account: &AccountInfo<'a>,
initialize_func: I
) -> Result<(), ProgramError>
where I: FnOnce(&mut MaxVoterWeightRecord) -> Result<(), ProgramError>
{
let mut record_data = MaxVoterWeightRecord {
account_discriminator: MaxVoterWeightRecord::ACCOUNT_DISCRIMINATOR,
realm: *realm,
governing_token_mint: *mint,
max_voter_weight: 0,
max_voter_weight_expiry: None,
reserved: [0u8; 8],
};
initialize_func(&mut record_data)?;
create_and_serialize_account_signed::<MaxVoterWeightRecord>(
payer_account,
record_account,
&record_data,
&get_max_voter_weight_record_seeds(realm, mint),
program_id,
system_program_account,
&Rent::get()?
)?;
Ok(())
}

Loading

0 comments on commit 71cad7a

Please sign in to comment.