Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vest program #5987

Merged
merged 17 commits into from
Oct 4, 2019
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ members = [
"programs/stake_tests",
"programs/storage_api",
"programs/storage_program",
"programs/vest_api",
"programs/vote_api",
"programs/vote_program",
"replicator",
Expand Down
27 changes: 27 additions & 0 deletions programs/vest_api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "solana-vest-api"
version = "0.20.0-pre0"
description = "Solana Vest program API"
authors = ["Solana Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"

[dependencies]
bincode = "1.1.4"
chrono = { version = "0.4.9", features = ["serde"] }
log = "0.4.8"
num-derive = "0.2"
num-traits = "0.2"
serde = "1.0.100"
serde_derive = "1.0.100"
solana-sdk = { path = "../../sdk", version = "0.20.0-pre0" }
solana-config-api = { path = "../config_api", version = "0.20.0-pre0" }

[dev-dependencies]
solana-runtime = { path = "../../runtime", version = "0.20.0-pre0" }

[lib]
crate-type = ["lib"]
name = "solana_budget_api"
58 changes: 58 additions & 0 deletions programs/vest_api/src/date_instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
///
/// A library for creating a trusted date oracle.
///
use bincode::{deserialize, serialized_size};
use chrono::{
prelude::{Date, DateTime, TimeZone, Utc},
serde::ts_seconds,
};
use serde_derive::{Deserialize, Serialize};
use solana_config_api::{config_instruction, ConfigState};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct DateConfig {
#[serde(with = "ts_seconds")]
pub dt: DateTime<Utc>,
}

impl Default for DateConfig {
fn default() -> Self {
Self {
dt: Utc.timestamp(0, 0),
}
}
}
impl DateConfig {
pub fn new(dt: Date<Utc>) -> Self {
Self {
dt: dt.and_hms(0, 0, 0),
}
}

pub fn deserialize(input: &[u8]) -> Option<Self> {
deserialize(input).ok()
}
}

impl ConfigState for DateConfig {
fn max_space() -> u64 {
serialized_size(&Self::default()).unwrap()
}
}

/// Create a date account. The date is set to the Unix epoch.
pub fn create_account(
payer_pubkey: &Pubkey,
date_pubkey: &Pubkey,
lamports: u64,
) -> Vec<Instruction> {
config_instruction::create_account::<DateConfig>(payer_pubkey, date_pubkey, lamports, vec![])
}

/// Set the date in the date account. The account pubkey must be signed in the
/// transaction containing this instruction.
pub fn store(date_pubkey: &Pubkey, dt: Date<Utc>) -> Instruction {
let date_config = DateConfig::new(dt);
config_instruction::store(&date_pubkey, true, vec![], &date_config)
}
15 changes: 15 additions & 0 deletions programs/vest_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub mod date_instruction;
pub mod vest_instruction;
pub mod vest_processor;
pub mod vest_schedule;
pub mod vest_state;

const VEST_PROGRAM_ID: [u8; 32] = [
7, 87, 23, 47, 219, 236, 238, 33, 137, 188, 215, 141, 32, 229, 155, 195, 133, 124, 23, 232,
113, 153, 252, 252, 111, 5, 187, 128, 0, 0, 0, 0,
];

solana_sdk::solana_name_id!(
VEST_PROGRAM_ID,
"Vest111111111111111111111111111111111111111"
);
144 changes: 144 additions & 0 deletions programs/vest_api/src/vest_instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use crate::{id, vest_state::VestState};
use bincode::serialized_size;
use chrono::prelude::{Date, DateTime, Utc};
use num_derive::FromPrimitive;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{
instruction::{AccountMeta, Instruction, InstructionError},
instruction_processor_utils::DecodeError,
pubkey::Pubkey,
system_instruction,
};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, FromPrimitive)]
pub enum VestError {
DestinationMissing,
Unauthorized,
}

impl From<VestError> for InstructionError {
fn from(e: VestError) -> Self {
InstructionError::CustomError(e as u32)
}
}

impl<T> DecodeError<T> for VestError {
fn type_of() -> &'static str {
"VestError"
}
}

impl std::fmt::Display for VestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
VestError::DestinationMissing => "destination missing",
VestError::Unauthorized => "unauthorized",
}
)
}
}
impl std::error::Error for VestError {}

/// An instruction to progress the smart contract.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum VestInstruction {
/// Declare and instantiate a vesting schedule
InitializeAccount {
terminator_pubkey: Pubkey, // The address authorized to terminate this contract with a signed Terminate instruction
payee_pubkey: Pubkey, // The address authorized to redeem vested tokens
start_date_time: DateTime<Utc>, // The day from which the vesting contract begins
date_pubkey: Pubkey, // Address of an account containing a trusted date, used to drive the vesting schedule
total_lamports: u64, // The number of lamports to send the payee if the schedule completes
},

/// Change the payee pubkey
SetPayee(Pubkey),

/// Load an account and pass its data to the contract for inspection.
RedeemTokens,

/// Tell the contract that the `InitializeAccount` with `Signature` has been
/// signed by the containing transaction's `Pubkey`.
Terminate,
}

fn initialize_account(
terminator_pubkey: &Pubkey,
payee_pubkey: &Pubkey,
contract_pubkey: &Pubkey,
start_date: Date<Utc>,
date_pubkey: &Pubkey,
total_lamports: u64,
) -> Instruction {
let keys = vec![AccountMeta::new(*contract_pubkey, false)];
Instruction::new(
id(),
&VestInstruction::InitializeAccount {
terminator_pubkey: *terminator_pubkey,
payee_pubkey: *payee_pubkey,
start_date_time: start_date.and_hms(0, 0, 0),
date_pubkey: *date_pubkey,
total_lamports,
},
keys,
)
}

pub fn create_account(
terminator_pubkey: &Pubkey,
payee_pubkey: &Pubkey,
contract_pubkey: &Pubkey,
start_date: Date<Utc>,
date_pubkey: &Pubkey,
lamports: u64,
) -> Vec<Instruction> {
let space = serialized_size(&VestState::default()).unwrap();
vec![
system_instruction::create_account(
&terminator_pubkey,
contract_pubkey,
lamports,
space,
&id(),
),
initialize_account(
terminator_pubkey,
payee_pubkey,
contract_pubkey,
start_date,
date_pubkey,
lamports,
),
]
}

pub fn set_payee(old_payee: &Pubkey, contract: &Pubkey, new_payee: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*old_payee, true),
AccountMeta::new(*contract, false),
];
Instruction::new(id(), &VestInstruction::SetPayee(*new_payee), account_metas)
}

pub fn terminate(from: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruction {
let mut account_metas = vec![
AccountMeta::new(*from, true),
AccountMeta::new(*contract, false),
];
if from != to {
account_metas.push(AccountMeta::new_credit_only(*to, false));
}
Instruction::new(id(), &VestInstruction::Terminate, account_metas)
}

pub fn redeem_tokens(date_pubkey: &Pubkey, contract: &Pubkey, to: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new_credit_only(*date_pubkey, false),
AccountMeta::new(*contract, false),
AccountMeta::new_credit_only(*to, false),
];
Instruction::new(id(), &VestInstruction::RedeemTokens, account_metas)
}
Loading