This commit is contained in:
czl1378 2020-12-01 01:41:09 +08:00
parent 95d86e8c0e
commit 3f0b330113
6 changed files with 309 additions and 39 deletions

View File

@ -8,6 +8,7 @@ edition = "2018"
[dependencies]
solana-program = "1.4.8"
spl-token = { version = "3.0.0", features = [ "no-entrypoint" ] }
byteorder = "1.3"
thiserror = "1.0"
num-derive = "0.3"

View File

@ -0,0 +1 @@
[230,199,10,234,118,135,80,179,41,207,6,148,213,115,124,131,231,98,111,94,230,60,157,233,181,137,107,192,74,90,146,137,195,104,18,142,51,221,206,67,34,125,1,169,105,243,210,182,239,4,85,132,189,79,155,159,50,7,195,53,144,151,128,232]

View File

@ -37,6 +37,9 @@ pub enum Error {
/// Submit cooling
#[error("Submission cooling")]
SubmissonCooling,
/// InsufficientWithdrawable
#[error("Insufficient withdrawable")]
InsufficientWithdrawable,
}
impl From<Error> for ProgramError {

View File

@ -9,10 +9,14 @@ use solana_program::{
use std::convert::TryInto;
/// Maximum number of aggregators in this program
pub const MAX_AGGREGATORS: usize = 32;
/// Maximum number of oracles
pub const MAX_ORACLES: usize = 18;
/// The interval(seconds) of an oracle's each submission
pub const SUBMIT_INTERVAL: i64 = 6;
/// The amount paid of TOKEN paid to each oracle per submission, in lamports (10e-10 SOL)
pub const PAYMENT_AMOUNT: u64 = 10;
/// Instructions supported by the program.
#[repr(C)]
@ -20,14 +24,14 @@ pub const SUBMIT_INTERVAL: i64 = 6;
pub enum Instruction {
/// Initializes a new Aggregator
Initialize {
/// The aggregator authority
authority: Pubkey,
/// A short description of what is being reported
description: [u8; 32],
/// min submission value
min_submission_value: u64,
/// max submission value
max_submission_value: u64,
/// The payment token program
payment_token: Pubkey,
},
/// Add an oracle
@ -38,6 +42,8 @@ pub enum Instruction {
AddOracle {
/// The oracle authority
authority: Pubkey,
/// Is usually the oracle name
description: [u8; 32],
/// The oracle's index
seat: u8,
},
@ -53,6 +59,12 @@ pub enum Instruction {
/// submission is the updated data that the oracle is submitting
submission: u64,
},
/// Oracle withdraw token
Withdraw {
/// withdraw amount
amount: u64,
}
}
@ -64,8 +76,7 @@ impl Instruction {
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
Ok(match tag {
0 => {
let (authority, rest) = Self::unpack_pubkey(rest)?;
let (description, rest) = rest.split_at(32);
let description = description
.try_into()
@ -79,25 +90,34 @@ impl Instruction {
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let (max_submission_value, _rest) = rest.split_at(8);
let (max_submission_value, rest) = rest.split_at(8);
let max_submission_value = max_submission_value
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let (payment_token, _rest) = Self::unpack_pubkey(rest)?;
Self::Initialize {
authority,
description,
min_submission_value,
max_submission_value,
payment_token,
}
},
1 => {
let (authority, rest) = Self::unpack_pubkey(rest)?;
let (seat, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
let (seat, rest) = rest.split_first().ok_or(InvalidInstruction)?;
let (description, _rest) = rest.split_at(32);
let description = description
.try_into()
.ok()
.ok_or(InvalidInstruction)?;
Self::AddOracle {
authority,
description,
seat: *seat,
}
},
@ -118,7 +138,19 @@ impl Instruction {
Self::Submit {
submission,
}
}
},
4 => {
let (amount, _rest) = rest.split_at(8);
let amount = amount
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
Self::Withdraw {
amount,
}
},
_ => return Err(Error::InvalidInstruction.into()),
})
}

View File

@ -2,7 +2,7 @@
use crate::{
error::Error,
instruction::{Instruction, SUBMIT_INTERVAL},
instruction::{Instruction, SUBMIT_INTERVAL, PAYMENT_AMOUNT},
state::{Aggregator, Oracle},
};
@ -14,6 +14,7 @@ use solana_program::{
entrypoint::ProgramResult,
info,
program_pack::{Pack},
program::{invoke_signed},
program_error::{PrintProgramError, ProgramError},
pubkey::Pubkey,
sysvar::{rent::Rent, Sysvar},
@ -29,23 +30,25 @@ impl Processor {
match instruction {
Instruction::Initialize {
authority,
description,
min_submission_value,
max_submission_value,
payment_token,
} => {
info!("Instruction: Initialize");
Self::process_initialize(
accounts, authority, description, min_submission_value, max_submission_value
accounts, description, min_submission_value,
max_submission_value, payment_token,
)
},
Instruction::AddOracle {
authority,
description,
seat,
} => {
info!("Instruction: AddOracle");
Self::process_add_oracle(
accounts, authority, seat,
accounts, authority, description, seat,
)
},
Instruction::RemoveOracle {
@ -64,42 +67,83 @@ impl Processor {
accounts, submission,
)
},
Instruction::Withdraw {
amount,
} => {
info!("Instruction: Withdraw");
Self::process_withdraw(
accounts, amount,
)
},
}
}
/// Processes an [Initialize](enum.Instruction.html) instruction.
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The aggregator(key).
/// 1. `[writable]` The program id
/// 1. `[]` Sysvar rent
/// 2. `[signer]` The aggregator's authority.
pub fn process_initialize(
accounts: &[AccountInfo],
authority: Pubkey,
description: [u8; 32],
min_submission_value: u64,
max_submission_value: u64,
payment_token: Pubkey,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let aggregator_info = next_account_info(account_info_iter)?;
let _aggregator_data_len = aggregator_info.data_len();
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
let program_info = next_account_info(account_info_iter)?;
let rent_info = next_account_info(account_info_iter)?;
let owner_info = next_account_info(account_info_iter)?;
// check signer
if !owner_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let rent = &Rent::from_account_info(rent_info)?;
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
if aggregator.is_initialized {
return Err(Error::AlreadyInUse.into());
}
if !rent.is_exempt(aggregator_info.lamports(), _aggregator_data_len) {
if !rent.is_exempt(aggregator_info.lamports(), aggregator_info.data_len()) {
return Err(Error::NotRentExempt.into());
}
let (faucet_owner, faucet_bump_seed) = find_authority_bump_seed(
program_info.key,
aggregator_info.key,
b"faucet",
);
aggregator.min_submission_value = min_submission_value;
aggregator.max_submission_value = max_submission_value;
aggregator.description = description;
aggregator.is_initialized = true;
aggregator.authority = authority;
aggregator.payment_token = payment_token;
aggregator.faucet_owner = faucet_owner;
aggregator.faucet_bump_seed = faucet_bump_seed;
aggregator.authority = *owner_info.key;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
// let mut program = Program::unpack_unchecked(&program_info.data.borrow())?;
// for p in program.aggregators.iter_mut() {
// if p == &Pubkey::default() {
// *p = *aggregator_info.key;
// }
// }
Ok(())
}
@ -116,6 +160,7 @@ impl Processor {
pub fn process_add_oracle(
accounts: &[AccountInfo],
authority: Pubkey,
description: [u8; 32],
seat: u8,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -125,7 +170,7 @@ impl Processor {
// Check aggregator authority
let owner_info = next_account_info(account_info_iter)?;
Self::validate_owner(&aggregator.authority, owner_info)?;
validate_owner(&aggregator.authority, owner_info)?;
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
@ -143,6 +188,7 @@ impl Processor {
submission: 0,
next_submit_time: clock.unix_timestamp,
authority,
description,
withdrawable: 0,
};
@ -171,7 +217,7 @@ impl Processor {
// Check aggregator authority
let owner_info = next_account_info(account_info_iter)?;
Self::validate_owner(&aggregator.authority, owner_info)?;
validate_owner(&aggregator.authority, owner_info)?;
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
@ -227,6 +273,9 @@ impl Processor {
}
found_oracle = true;
oracle.submission = submission;
// pay oracle
oracle.withdrawable += PAYMENT_AMOUNT;
oracle.next_submit_time = clock.unix_timestamp + SUBMIT_INTERVAL;
}
@ -242,21 +291,125 @@ impl Processor {
Ok(())
}
/// Validate aggregator owner
pub fn validate_owner(
expected_owner: &Pubkey,
owner_account_info: &AccountInfo,
/// Processes an [Withdraw](enum.Instruction.html) instruction
/// Can only be called by the oracle admin
///
/// @to: the address to send the token to
/// @amount: the amount of token to send
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The aggregator(key).
/// 1. `[writable]` The token transfer from
/// 2. `[writable]` The token withdraw to
/// 3. `[]` SPL Token program id
/// 4. `[]` The faucet owner
/// 5. `[signer]` The oracle's authority.
pub fn process_withdraw(
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
if expected_owner != owner_account_info.key {
return Err(Error::OwnerMismatch.into());
let account_info_iter = &mut accounts.iter();
let aggregator_info = next_account_info(account_info_iter)?;
let token_account_info = next_account_info(account_info_iter)?;
let receiver_info = next_account_info(account_info_iter)?;
let token_program_info = next_account_info(account_info_iter)?;
let faucet_owner_info = next_account_info(account_info_iter)?;
let oracle_owner_info = next_account_info(account_info_iter)?;
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
}
if !owner_account_info.is_signer {
let mut oracles = aggregator.oracles;
let mut oracle_idx: i8 = -1;
// find oracle
for (idx, oracle) in oracles.iter().enumerate() {
if &oracle.authority == oracle_owner_info.key {
oracle_idx = idx as i8;
}
}
if oracle_idx < 0 {
return Err(Error::NotFoundOracle.into());
}
// must be signer
if !oracle_owner_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if oracles[oracle_idx as usize].withdrawable < amount {
return Err(Error::InsufficientWithdrawable.into());
}
let authority_signature_seeds = [
&aggregator_info.key.to_bytes()[..32],
b"faucet",
&[aggregator.faucet_bump_seed]
];
let signers = &[&authority_signature_seeds[..]];
info!("Create transfer transaction...");
let instruction = spl_token::instruction::transfer(
token_program_info.key,
token_account_info.key,
receiver_info.key,
faucet_owner_info.key,
&[],
amount,
)?;
info!("Invoke signed...");
invoke_signed(
&instruction,
&[
token_account_info.clone(),
token_program_info.clone(),
receiver_info.clone(),
faucet_owner_info.clone(),
],
signers
)?;
// update oracle
oracles[oracle_idx as usize].withdrawable -= amount;
aggregator.oracles = oracles;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
Ok(())
}
}
// Helpers
/// Validate aggregator owner
fn validate_owner(
expected_owner: &Pubkey,
owner_account_info: &AccountInfo,
) -> ProgramResult {
if expected_owner != owner_account_info.key {
return Err(Error::OwnerMismatch.into());
}
if !owner_account_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
Ok(())
}
/// Generates seed bump for stake pool authorities
pub fn find_authority_bump_seed(
program_id: &Pubkey,
my_info: &Pubkey,
authority_type: &[u8],
) -> (Pubkey, u8) {
Pubkey::find_program_address(&[&my_info.to_bytes()[..32], authority_type], program_id)
}
impl PrintProgramError for Error {
fn print<E>(&self)
@ -274,6 +427,7 @@ impl PrintProgramError for Error {
Error::NotFoundOracle => info!("Error: Not found oracle"),
Error::SubmissonValueOutOfRange => info!("Error: Submisson value out of range"),
Error::SubmissonCooling => info!("Submission cooling"),
Error::InsufficientWithdrawable => info!("Insufficient withdrawable"),
}
}
}

View File

@ -1,6 +1,6 @@
//! State transition types
use crate::instruction::MAX_ORACLES;
use crate::instruction::{MAX_ORACLES, MAX_AGGREGATORS};
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{
@ -10,6 +10,37 @@ use solana_program::{
clock::{UnixTimestamp}
};
/// Program data
#[repr(C)]
#[derive(Clone, Debug, Copy, PartialEq)]
pub struct Program {
/// All aggregators
pub aggregators: [Pubkey; MAX_AGGREGATORS],
}
impl Sealed for Program {}
impl Pack for Program {
const LEN: usize = MAX_AGGREGATORS*32;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, MAX_AGGREGATORS*32];
Ok(Program {
aggregators: unpack_aggregators(src),
})
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, MAX_AGGREGATORS*32];
let (aggregators_dst, _) = mut_array_refs![dst, 0;..;];
let &Program {
ref aggregators,
} = self;
pack_aggregators(aggregators, aggregators_dst);
}
}
/// Aggregator data.
#[repr(C)]
#[derive(Clone, Debug, Copy, Default, PartialEq)]
@ -24,6 +55,12 @@ pub struct Aggregator {
pub is_initialized: bool,
/// authority
pub authority: Pubkey,
/// the payment token program
pub payment_token: Pubkey,
/// faucet owner (program derived address)
pub faucet_owner: Pubkey,
/// faucet bump seed
pub faucet_bump_seed: u8,
/// submissions
pub oracles: [Oracle; MAX_ORACLES],
}
@ -36,13 +73,13 @@ impl IsInitialized for Aggregator {
impl Sealed for Aggregator {}
impl Pack for Aggregator {
const LEN: usize = 81 + MAX_ORACLES*56;
const LEN: usize = 146 + MAX_ORACLES*88;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, 81 + MAX_ORACLES*56];
let src = array_ref![src, 0, 146 + MAX_ORACLES*88];
let (
min_submission_value, max_submission_value,
description, is_initialized, authority, rem,
) = array_refs![src, 8, 8, 32, 1, 32; ..;];
min_submission_value, max_submission_value, description, is_initialized,
authority, payment_token, faucet_owner, faucet_bump_seed, rem,
) = array_refs![src, 8, 8, 32, 1, 32, 32, 32, 1; ..;];
let is_initialized = match is_initialized {
[0] => false,
@ -57,19 +94,25 @@ impl Pack for Aggregator {
is_initialized,
authority: Pubkey::new_from_array(*authority),
oracles: unpack_oracles(rem),
payment_token: Pubkey::new_from_array(*payment_token),
faucet_owner: Pubkey::new_from_array(*faucet_owner),
faucet_bump_seed: faucet_bump_seed[0],
})
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, 81 + MAX_ORACLES*56];
let dst = array_mut_ref![dst, 0, 146 + MAX_ORACLES*88];
let (
min_submission_value_dst,
max_submission_value_dst,
description_dst,
is_initialized_dst,
authority_dst,
payment_token_dst,
faucet_owner_dst,
faucet_bump_seed_dst,
rem,
) = mut_array_refs![dst, 8, 8, 32, 1, 32; ..;];
) = mut_array_refs![dst, 8, 8, 32, 1, 32, 32, 32, 1; ..;];
let &Aggregator {
min_submission_value,
@ -77,6 +120,9 @@ impl Pack for Aggregator {
description,
is_initialized,
ref authority,
ref payment_token,
ref faucet_owner,
faucet_bump_seed,
ref oracles,
} = self;
@ -85,6 +131,9 @@ impl Pack for Aggregator {
*description_dst = description;
is_initialized_dst[0] = is_initialized as u8;
authority_dst.copy_from_slice(authority.as_ref());
payment_token_dst.copy_from_slice(payment_token.as_ref());
faucet_owner_dst.copy_from_slice(faucet_owner.as_ref());
faucet_bump_seed_dst[0] = faucet_bump_seed as u8;
pack_oracles(oracles, rem);
}
@ -100,6 +149,8 @@ pub struct Oracle {
pub next_submit_time: UnixTimestamp,
/// oracle authority
pub authority: Pubkey,
/// is usually the oracle name
pub description: [u8; 32],
/// withdrawable
pub withdrawable: u64,
}
@ -112,14 +163,16 @@ fn unpack_oracles(mut dst: &[u8]) -> [Oracle; MAX_ORACLES] {
submission,
next_submit_time,
authority,
description,
withdrawable,
rem,
) = array_refs![dst, 8, 8, 32, 8; ..;];
) = array_refs![dst, 8, 8, 32, 32, 8; ..;];
arr[i] = Oracle {
submission: u64::from_le_bytes(*submission),
next_submit_time: i64::from_le_bytes(*next_submit_time),
authority: Pubkey::new_from_array(*authority),
description: *description,
withdrawable: u64::from_le_bytes(*withdrawable),
};
@ -130,27 +183,53 @@ fn unpack_oracles(mut dst: &[u8]) -> [Oracle; MAX_ORACLES] {
fn pack_oracles(src: &[Oracle; MAX_ORACLES], mut dst: &mut [u8]) {
for i in 0 .. MAX_ORACLES {
let (s, rem) = mut_array_refs![dst, 56; ..;];
let (s, rem) = mut_array_refs![dst, 88; ..;];
let (
submission_dst,
next_submit_time_dst,
authority_dst,
description_dst,
withdrawable_dst,
) = mut_array_refs![&mut *s, 8, 8, 32, 8];
) = mut_array_refs![&mut *s, 8, 8, 32, 32, 8];
let &Oracle {
submission,
next_submit_time,
authority,
description,
withdrawable,
} = &src[i];
*submission_dst = submission.to_le_bytes();
*next_submit_time_dst = next_submit_time.to_le_bytes();
*description_dst = description;
authority_dst.copy_from_slice(authority.as_ref());
*withdrawable_dst = withdrawable.to_le_bytes();
dst = rem;
}
}
fn unpack_aggregators(mut dst: &[u8]) -> [Pubkey; MAX_AGGREGATORS] {
let mut arr = [Pubkey::default(); MAX_AGGREGATORS];
for i in 0 .. MAX_AGGREGATORS {
let ( pubkey, rem ) = array_refs![dst, 32; ..;];
arr[i] = Pubkey::new_from_array(*pubkey);
dst = rem;
}
arr
}
fn pack_aggregators(src: &[Pubkey; MAX_AGGREGATORS], mut dst: &mut [u8]) {
for i in 0 .. MAX_AGGREGATORS {
let (s, rem) = mut_array_refs![dst, 32; ..;];
let &pubkey = &src[i];
s.copy_from_slice(pubkey.as_ref());
dst = rem;
}
}