use 'borsh' for serialization; add some tests

This commit is contained in:
czl1378 2020-12-18 14:38:28 +08:00
parent 64ac2ead2f
commit 1f32a52df7
10 changed files with 2554 additions and 375 deletions

6
.gitignore vendored
View File

@ -4,4 +4,8 @@ target
yarn.lock
build
.yarn
dist
dist
.yarnrc.yml
.pnp.js
.env
deploy.json

Binary file not shown.

1659
program/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,15 +7,19 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
borsh = "0.7.1"
borsh-derive = "0.7.1"
solana-program = "1.4.8"
spl-token = { version = "3.0.0", features = [ "no-entrypoint" ] }
byteorder = "1.3"
thiserror = "1.0"
num-derive = "0.3"
num-traits = "0.2"
arrayref = "0.3.6"
num_enum = "0.5.1"
[dev-dependencies]
solana-sdk = "1.4.8"
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -0,0 +1,56 @@
//! Borsh utils
use borsh::schema::{BorshSchema, Declaration, Definition, Fields};
use std::collections::HashMap;
/// Get packed length for the given BorchSchema Declaration
fn get_declaration_packed_len(
declaration: &str,
definitions: &HashMap<Declaration, Definition>,
) -> usize {
match definitions.get(declaration) {
Some(Definition::Array { length, elements }) => {
*length as usize * get_declaration_packed_len(elements, definitions)
}
Some(Definition::Enum { variants }) => {
1 + variants
.iter()
.map(|(_, declaration)| get_declaration_packed_len(declaration, definitions))
.max()
.unwrap_or(0)
}
Some(Definition::Struct { fields }) => match fields {
Fields::NamedFields(named_fields) => named_fields
.iter()
.map(|(_, declaration)| get_declaration_packed_len(declaration, definitions))
.sum(),
Fields::UnnamedFields(declarations) => declarations
.iter()
.map(|declaration| get_declaration_packed_len(declaration, definitions))
.sum(),
Fields::Empty => 0,
},
Some(Definition::Sequence {
elements: _elements,
}) => panic!("Missing support for Definition::Sequence"),
Some(Definition::Tuple { elements }) => elements
.iter()
.map(|element| get_declaration_packed_len(element, definitions))
.sum(),
None => match declaration {
"u8" | "i8" => 1,
"u16" | "i16" => 2,
"u32" | "i32" => 4,
"u64" | "i64" => 8,
"u128" | "i128" => 16,
"bool" => 1,
"nil" => 0,
_ => panic!("Missing primitive type: {}", declaration),
},
}
}
/// Get the worst-case packed length for the given BorshSchema
pub fn get_packed_len<S: BorshSchema>() -> usize {
let schema_container = S::schema_container();
get_declaration_packed_len(&schema_container.declaration, &schema_container.definitions)
}

View File

@ -1,25 +1,35 @@
//! Instruction types
use crate::error::Error;
// use crate::error::Error;
#![allow(dead_code)]
use solana_program::{
program_error::ProgramError,
sysvar,
pubkey::Pubkey,
info,
program_error::ProgramError,
program_pack::{Pack, Sealed},
instruction::{AccountMeta, Instruction as SolInstruction},
};
use std::convert::TryInto;
// use std::convert::TryInto;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
/// Maximum number of oracles
pub const MAX_ORACLES: usize = 21;
pub const MAX_ORACLES: usize = 12;
/// 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)]
#[derive(Clone, Debug, PartialEq)]
/// Instructions supported by the program
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
pub enum Instruction {
/// Initializes a new Aggregator
///
/// Accounts expected by this instruction:
///
/// 0. `[]` Rent sysvar
/// 1. `[writable]` The aggregator.
/// 2. `[signer]` The aggregator's authority.
Initialize {
/// The interval(seconds) of an oracle's each submission
submit_interval: u32,
@ -32,128 +42,259 @@ pub enum Instruction {
},
/// Add an oracle
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The aggregator.
/// 1. `[signer]` The aggregator's authority.
/// Accounts expected by this instruction:
/// 0. `[writable]` The oracle
/// 1. `[]` The oracle owner
/// 2. `[]` Clock sysvar
/// 3. `[writable]` The aggregator
/// 4. `[signer]` The aggregator owner
AddOracle {
/// add to index
/// the oracle's index of the aggregator
index: u8,
/// Is usually the oracle name
/// Is usually the oracle name
description: [u8; 32],
},
/// Remove an oracle
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The aggregator.
/// 1. `[signer]` The aggregator onwer.
RemoveOracle {
/// index
/// the oracle's index of the aggregator
index: u8,
},
/// Called by oracles when they have witnessed a need to update
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The aggregator(key).
/// 1. `[]` Clock sysvar
/// 2. `[writable]` The oracle key.
/// 3. `[signer]` The oracle owner.
Submit {
/// submission is the updated data that the oracle is submitting
/// the updated data that the oracle is submitting
submission: u64,
},
/// Oracle withdraw token
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The aggregator (key).
/// 1. `[writable]` The faucet (which token transfer from)
/// 2. `[writable]` The recevier (which token withdraw to)
/// 3. `[]` SPL Token program id
/// 4. `[]` The faucet owner
/// 5. `[signer, writable]` The oracle's authority.
Withdraw {
/// withdraw amount
amount: u64,
///
seed: Vec<u8>,
/// program account nonced seed
seed: [u8; 32],
},
}
impl Instruction {
/// Unpacks a byte buffer into a [Instruction](enum.Instruction.html).
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
use Error::InvalidInstruction;
impl Sealed for Instruction {}
impl Pack for Instruction {
const LEN: usize = 53;
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
Ok(match tag {
0 => {
let (submit_interval, rest) = rest.split_at(4);
let submit_interval = submit_interval
.try_into()
.ok()
.map(u32::from_le_bytes)
.ok_or(InvalidInstruction)?;
let (min_submission_value, rest) = rest.split_at(8);
let min_submission_value = min_submission_value
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
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 (description, _rest) = rest.split_at(32);
let description = description
.try_into()
.ok()
.ok_or(InvalidInstruction)?;
Self::Initialize {
submit_interval,
min_submission_value,
max_submission_value,
description,
}
},
1 => {
let (&index, 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 {
index, description,
}
},
2 => {
let (&index, _rest) = rest.split_first().ok_or(InvalidInstruction)?;
Self::RemoveOracle {
index,
}
},
3 => {
let (submission, _rest) = rest.split_at(8);
let submission = submission
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
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, seed: rest.to_vec(),
}
},
_ => return Err(Error::InvalidInstruction.into()),
})
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = self.pack_into_vec();
dst[..data.len()].copy_from_slice(&data);
}
}
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut mut_src: &[u8] = src;
Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidInstructionData)
}
}
impl Instruction {
fn pack_into_vec(&self) -> Vec<u8> {
self.try_to_vec().expect("try_to_vec")
}
}
/// Below is for test
/// Creates a `intialize` instruction.
pub fn initialize(
program_id: &Pubkey,
aggregator_pubkey: &Pubkey,
aggregator_owner_pubkey: &Pubkey,
submit_interval: u32,
min_submission_value: u64,
max_submission_value: u64,
description: [u8; 32],
) -> Result<SolInstruction, ProgramError> {
let data = Instruction::Initialize {
submit_interval,
min_submission_value,
max_submission_value,
description,
}
.try_to_vec().unwrap();
let accounts = vec![
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new(*aggregator_pubkey, false),
AccountMeta::new_readonly(*aggregator_owner_pubkey, true),
];
Ok(SolInstruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a `add_oralce` instruction
pub fn add_oracle(
program_id: &Pubkey,
oracle_pubkey: &Pubkey,
oracle_owner_pubkey: &Pubkey,
aggregator_pubkey: &Pubkey,
aggregator_owner_pubkey: &Pubkey,
index: u8,
description: [u8; 32]
) -> Result<SolInstruction, ProgramError> {
let data = Instruction::AddOracle {
index,
description,
}
.try_to_vec().unwrap();
let accounts = vec![
AccountMeta::new(*oracle_pubkey, false),
AccountMeta::new(*oracle_owner_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new(*aggregator_pubkey, false),
AccountMeta::new_readonly(*aggregator_owner_pubkey, true),
];
Ok(SolInstruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a `remove_oralce` instruction
pub fn remove_oracle(
program_id: &Pubkey,
aggregator_pubkey: &Pubkey,
aggregator_owner_pubkey: &Pubkey,
index: u8,
) -> Result<SolInstruction, ProgramError> {
let data = Instruction::RemoveOracle {
index,
}
.try_to_vec().unwrap();
let accounts = vec![
AccountMeta::new(*aggregator_pubkey, false),
AccountMeta::new_readonly(*aggregator_owner_pubkey, true),
];
Ok(SolInstruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a `submit` instruction
pub fn submit(
program_id: &Pubkey,
aggregator_pubkey: &Pubkey,
oracle_pubkey: &Pubkey,
oracle_owner_pubkey: &Pubkey,
submission: u64,
) -> Result<SolInstruction, ProgramError> {
let data = Instruction::Submit {
submission,
}
.try_to_vec().unwrap();
let accounts = vec![
AccountMeta::new(*aggregator_pubkey, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new(*oracle_pubkey, false),
AccountMeta::new_readonly(*oracle_owner_pubkey, true),
];
Ok(SolInstruction {
program_id: *program_id,
accounts,
data,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::borsh_utils;
#[test]
fn test_get_packed_len() {
assert_eq!(
Instruction::get_packed_len(),
borsh_utils::get_packed_len::<Instruction>()
)
}
#[test]
fn test_serialize_bytes() {
let test_instruction = Instruction::Initialize {
submit_interval: 6u32,
min_submission_value: 0u64,
max_submission_value: 9999u64,
description: [1u8; 32],
};
assert_eq!(
test_instruction.try_to_vec().unwrap(),
vec![
0,
6, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
15, 39, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
],
);
}
#[test]
fn state_deserialize_invalid() {
assert_eq!(
Instruction::unpack_from_slice(&[
0,
6, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
15, 39, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]),
Ok(Instruction::Initialize {
submit_interval: 6u32,
min_submission_value: 0u64,
max_submission_value: 9999u64,
description: [1u8; 32],
}),
);
assert_eq!(
Instruction::unpack_from_slice(&[
4,
15, 39, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]),
Ok(Instruction::Withdraw {
amount: 9999u64,
seed: [1u8; 32],
}),
);
}
}

View File

@ -13,6 +13,7 @@ pub mod instruction;
pub mod processor;
pub mod error;
pub mod state;
pub mod borsh_utils;
#[cfg(not(feature = "no-entrypoint"))]
pub mod entrypoint;

View File

@ -2,7 +2,7 @@
use crate::{
error::Error,
instruction::{Instruction, PAYMENT_AMOUNT},
instruction::{Instruction, PAYMENT_AMOUNT, MAX_ORACLES},
state::{Aggregator, Oracle, Submission},
};
@ -12,7 +12,7 @@ use solana_program::{
clock::{Clock},
decode_error::DecodeError,
entrypoint::ProgramResult,
info,
msg,
program_pack::{Pack},
program::{invoke_signed},
program_error::{PrintProgramError, ProgramError},
@ -24,9 +24,10 @@ use solana_program::{
pub struct Processor {}
impl Processor {
/// Processes an [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = Instruction::unpack(input)?;
let instruction = Instruction::unpack_from_slice(input)?;
match instruction {
Instruction::Initialize {
@ -35,7 +36,7 @@ impl Processor {
max_submission_value,
description,
} => {
info!("Instruction: Initialize");
msg!("Instruction: Initialize");
Self::process_initialize(
program_id, accounts, submit_interval, min_submission_value,
max_submission_value, description,
@ -45,7 +46,7 @@ impl Processor {
index,
description,
} => {
info!("Instruction: AddOracle");
msg!("Instruction: AddOracle");
Self::process_add_oracle(
accounts, index, description,
)
@ -53,7 +54,7 @@ impl Processor {
Instruction::RemoveOracle {
index,
} => {
info!("Instruction: RemoveOracle");
msg!("Instruction: RemoveOracle");
Self::process_remove_oracle(
accounts, index,
)
@ -61,7 +62,7 @@ impl Processor {
Instruction::Submit {
submission,
} => {
info!("Instruction: Submit");
msg!("Instruction: Submit");
Self::process_submit(
accounts, submission,
)
@ -70,21 +71,15 @@ impl Processor {
amount,
seed,
} => {
info!("Instruction: Withdraw");
msg!("Instruction: Withdraw");
Self::process_withdraw(
accounts, amount, seed.as_slice(),
accounts, amount, seed,
)
},
}
}
/// Processes an [Initialize](enum.Instruction.html) instruction.
///
/// Accounts expected by this instruction:
///
/// 1. `[]` Sysvar rent
/// 2. `[writable]` The aggregator
/// 3. `[signer]` The aggregator owner
pub fn process_initialize(
_program_id: &Pubkey,
accounts: &[AccountInfo],
@ -121,7 +116,7 @@ impl Processor {
aggregator.max_submission_value = max_submission_value;
aggregator.description = description;
aggregator.is_initialized = true;
aggregator.owner = *owner_info.key;
aggregator.owner = owner_info.key.to_bytes();
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
@ -129,16 +124,6 @@ impl Processor {
}
/// Processes an [AddOracle](enum.Instruction.html) instruction.
///
/// @description: the oracle name
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The oracle
/// 1. `[]` The oracle owner
/// 2. `[]` Clock sysvar
/// 3. `[writable]` The aggregator
/// 4. `[signer]` The aggregator owner
pub fn process_add_oracle(
accounts: &[AccountInfo],
index: u8,
@ -158,14 +143,14 @@ impl Processor {
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
if &aggregator.owner != aggregator_owner_info.key {
return Err(Error::OwnerMismatch.into());
}
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
}
if &Pubkey::new_from_array(aggregator.owner) != aggregator_owner_info.key {
return Err(Error::OwnerMismatch.into());
}
let mut oracle = Oracle::unpack_unchecked(&oracle_info.data.borrow())?;
if oracle.is_initialized {
return Err(Error::AlreadyInUse.into());
@ -176,25 +161,15 @@ impl Processor {
let mut submissions = aggregator.submissions;
let submission = &mut submissions[index as usize];
if submission.oracle == Pubkey::default() {
submission.oracle = *oracle_info.key;
let index = MAX_ORACLES.min(0.max(index as usize));
let submission = &mut submissions[index];
if Pubkey::new_from_array(submission.oracle) == Pubkey::default() {
submission.oracle = oracle_info.key.to_bytes();
} else {
return Err(Error::IndexHaveBeenUsed.into());
}
// // default submission
// for s in submissions.iter_mut() {
// if s.oracle == Pubkey::default() {
// *s = Submission {
// time: clock.unix_timestamp,
// value: 0,
// oracle: *oracle_info.key,
// };
// break;
// }
// }
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
@ -203,8 +178,8 @@ impl Processor {
oracle.description = description;
oracle.is_initialized = true;
oracle.withdrawable = 0;
oracle.aggregator = *aggregator_info.key;
oracle.owner = *oracle_owner_info.key;
oracle.aggregator = aggregator_info.key.to_bytes();
oracle.owner = oracle_owner_info.key.to_bytes();
Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
@ -212,13 +187,6 @@ impl Processor {
}
/// Processes an [RemoveOracle](enum.Instruction.html) instruction.
///
/// @seat: the oracle's index of the aggregator
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The aggregator.
/// 1. `[signer]` The aggregator onwer.
pub fn process_remove_oracle(
accounts: &[AccountInfo],
index: u8,
@ -237,33 +205,21 @@ impl Processor {
return Err(Error::NotFoundAggregator.into());
}
if &aggregator.owner != owner_info.key {
if &Pubkey::new_from_array(aggregator.owner) != owner_info.key {
return Err(Error::OwnerMismatch.into());
}
// remove submission
let mut submissions = aggregator.submissions;
let index = MAX_ORACLES.min(0.max(index as usize));
let submission = &mut submissions[index as usize];
if submission.oracle != Pubkey::default() {
let submission = &mut submissions[index];
if Pubkey::new_from_array(submission.oracle) != Pubkey::default() {
*submission = Submission::default();
} else {
return Err(Error::NotFoundOracle.into());
}
// let mut found = false;
// for s in submissions.iter_mut() {
// if s.oracle == oracle {
// *s = Submission::default();
// found = true;
// break;
// }
// }
// if !found {
// return Err(Error::NotFoundOracle.into());
// }
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
@ -271,14 +227,6 @@ impl Processor {
}
/// Processes an [Submit](enum.Instruction.html) instruction.
/// @submission: the updated data that the oracle is submitting
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` The aggregator(key).
/// 1. `[]` Clock sysvar
/// 2. `[writable]` The oracle key.
/// 3. `[signer]` The oracle owner.
pub fn process_submit(
accounts: &[AccountInfo],
submission: u64,
@ -307,11 +255,11 @@ impl Processor {
return Err(Error::NotFoundOracle.into());
}
if &oracle.owner != oracle_owner_info.key {
if &Pubkey::new_from_array(oracle.owner) != oracle_owner_info.key {
return Err(Error::OwnerMismatch.into());
}
if &oracle.aggregator != aggregator_info.key {
if &Pubkey::new_from_array(oracle.aggregator) != aggregator_info.key {
return Err(Error::AggregatorKeyNotMatch.into());
}
@ -321,7 +269,7 @@ impl Processor {
let mut found = false;
let mut submissions = aggregator.submissions;
for s in submissions.iter_mut() {
if &s.oracle == oracle_info.key {
if &Pubkey::new_from_array(s.oracle) == oracle_info.key {
s.value = submission;
s.time = clock.unix_timestamp;
found = true;
@ -334,7 +282,6 @@ impl Processor {
}
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
if oracle.next_submit_time > clock.unix_timestamp {
return Err(Error::SubmissonCooling.into());
@ -344,29 +291,20 @@ impl Processor {
oracle.withdrawable += PAYMENT_AMOUNT;
oracle.next_submit_time = clock.unix_timestamp + aggregator.submit_interval as i64;
// update aggregator
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
// update oracle
Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
Ok(())
}
/// 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 faucet (which token transfer from)
/// 2. `[writable]` The recevier (which token withdraw to)
/// 3. `[]` SPL Token program id
/// 4. `[]` The faucet owner
/// 5. `[signer, writable]` The oracle's authority.
pub fn process_withdraw(
accounts: &[AccountInfo],
amount: u64,
seed: &[u8],
seed: [u8; 32],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let aggregator_info = next_account_info(account_info_iter)?;
@ -396,7 +334,7 @@ impl Processor {
return Err(Error::InsufficientWithdrawable.into());
}
info!("Create transfer instruction...");
msg!("Create transfer instruction...");
let instruction = spl_token::instruction::transfer(
token_program_info.key,
faucet_info.key,
@ -406,7 +344,7 @@ impl Processor {
amount,
)?;
info!("Invoke signed...");
msg!("Invoke signed...");
invoke_signed(
&instruction,
&[
@ -415,7 +353,7 @@ impl Processor {
receiver_info.clone(),
faucet_owner_info.clone(),
],
&[&[seed]]
&[&[seed.as_ref()]]
)?;
// update oracle
@ -433,18 +371,476 @@ impl PrintProgramError for Error {
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
Error::InvalidInstruction => info!("Error: Invalid instruction"),
Error::AlreadyInUse => info!("Error: Already in use"),
Error::NotRentExempt => info!("Error: No rent exempt"),
Error::NotFoundAggregator => info!("Error: no found aggregator"),
Error::OracleAdded => info!("Error: Oracle added"),
Error::OwnerMismatch => info!("Error: Owner mismatch"),
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"),
Error::AggregatorKeyNotMatch => info!("Aggregator key not match"),
Error::IndexHaveBeenUsed => info!("Index have been used"),
Error::InvalidInstruction => msg!("Error: Invalid instruction"),
Error::AlreadyInUse => msg!("Error: Already in use"),
Error::NotRentExempt => msg!("Error: No rent exempt"),
Error::NotFoundAggregator => msg!("Error: no found aggregator"),
Error::OracleAdded => msg!("Error: Oracle added"),
Error::OwnerMismatch => msg!("Error: Owner mismatch"),
Error::NotFoundOracle => msg!("Error: Not found oracle"),
Error::SubmissonValueOutOfRange => msg!("Error: Submisson value out of range"),
Error::SubmissonCooling => msg!("Submission cooling"),
Error::InsufficientWithdrawable => msg!("Insufficient withdrawable"),
Error::AggregatorKeyNotMatch => msg!("Aggregator key not match"),
Error::IndexHaveBeenUsed => msg!("Index have been used"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instruction::*;
use solana_program::{instruction::Instruction};
use solana_sdk::account::{
create_account, create_is_signer_account_infos, Account as SolanaAccount,
};
fn do_process_instruction(
instruction: Instruction,
accounts: Vec<&mut SolanaAccount>,
) -> ProgramResult {
let mut meta = instruction
.accounts
.iter()
.zip(accounts)
.map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
.collect::<Vec<_>>();
let account_infos = create_is_signer_account_infos(&mut meta);
Processor::process(&instruction.program_id, &account_infos, &instruction.data)
}
fn return_error_as_program_error() -> ProgramError {
Error::NotFoundAggregator.into()
}
fn rent_sysvar() -> SolanaAccount {
create_account(&Rent::default(), 42)
}
fn clock_sysvar() -> SolanaAccount {
create_account(&Clock::default(), 42)
}
fn aggregator_minimum_balance() -> u64 {
Rent::default().minimum_balance(Aggregator::get_packed_len())
}
fn oracle_minimum_balance() -> u64 {
Rent::default().minimum_balance(Oracle::get_packed_len())
}
#[test]
fn test_print_error() {
let error = return_error_as_program_error();
error.print::<Error>();
}
#[test]
#[should_panic(expected = "Custom(3)")]
fn test_error_unwrap() {
Err::<(), ProgramError>(return_error_as_program_error()).unwrap();
}
#[test]
fn test_pack_unpack() {
let check = Submission {
time: 1,
value: 1,
oracle: [1; 32],
};
let mut packed = vec![0; Submission::get_packed_len() + 1];
assert_eq!(
Err(ProgramError::InvalidAccountData),
Submission::pack(check, &mut packed)
);
}
#[test]
fn test_intialize() {
let program_id = Pubkey::new_unique();
let aggregator_key = Pubkey::new_unique();
let owner_key = Pubkey::new_unique();
let mut rent_sysvar = rent_sysvar();
let mut aggregator_account = SolanaAccount::new(42, Aggregator::get_packed_len(), &program_id);
let mut owner_account = SolanaAccount::default();
// aggregator is not rent exempt
assert_eq!(
Err(Error::NotRentExempt.into()),
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut owner_account,
]
)
);
aggregator_account.lamports = aggregator_minimum_balance();
// initialize will be successful
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut owner_account,
]
).unwrap();
// duplicate initialize will get failed
assert_eq!(
Err(Error::AlreadyInUse.into()),
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut owner_account,
]
)
);
}
#[test]
fn test_add_oracle() {
let program_id = Pubkey::new_unique();
let oracle_key = Pubkey::new_unique();
let oracle_owner_key = Pubkey::new_unique();
let aggregator_key = Pubkey::new_unique();
let aggregator_owner_key = Pubkey::new_unique();
let mut rent_sysvar = rent_sysvar();
let mut clock_sysvar = clock_sysvar();
let mut oracle_account = SolanaAccount::new(
oracle_minimum_balance(),
Oracle::get_packed_len(),
&program_id
);
let mut aggregator_account = SolanaAccount::new(
aggregator_minimum_balance(),
Aggregator::get_packed_len(),
&program_id
);
let mut oracle_owner_account = SolanaAccount::default();
let mut aggregator_owner_account = SolanaAccount::default();
// add oracle to unexist aggregator
assert_eq!(
Err(Error::NotFoundAggregator.into()),
do_process_instruction(
add_oracle(
&program_id,
&oracle_key,
&oracle_owner_key,
&aggregator_key,
&aggregator_owner_key,
0,
[1; 32]
).unwrap(),
vec![
&mut oracle_account,
&mut oracle_owner_account,
&mut clock_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
)
);
// initialize aggregator
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&aggregator_owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// will be successful
do_process_instruction(
add_oracle(
&program_id,
&oracle_key,
&oracle_owner_key,
&aggregator_key,
&aggregator_owner_key,
0,
[1; 32]
).unwrap(),
vec![
&mut oracle_account,
&mut oracle_owner_account,
&mut clock_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// duplicate oracle
assert_eq!(
Err(Error::AlreadyInUse.into()),
do_process_instruction(
add_oracle(
&program_id,
&oracle_key,
&oracle_owner_key,
&aggregator_key,
&aggregator_owner_key,
0,
[1; 32]
).unwrap(),
vec![
&mut oracle_account,
&mut oracle_owner_account,
&mut clock_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
)
);
}
#[test]
fn test_remove_oracle() {
let program_id = Pubkey::new_unique();
let oracle_key = Pubkey::new_unique();
let oracle_owner_key = Pubkey::new_unique();
let aggregator_key = Pubkey::new_unique();
let aggregator_owner_key = Pubkey::new_unique();
let mut rent_sysvar = rent_sysvar();
let mut clock_sysvar = clock_sysvar();
let mut oracle_account = SolanaAccount::new(
oracle_minimum_balance(),
Oracle::get_packed_len(),
&program_id
);
let mut aggregator_account = SolanaAccount::new(
aggregator_minimum_balance(),
Aggregator::get_packed_len(),
&program_id
);
let mut oracle_owner_account = SolanaAccount::default();
let mut aggregator_owner_account = SolanaAccount::default();
// initialize aggregator
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&aggregator_owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// add oracle (index 0)
do_process_instruction(
add_oracle(
&program_id,
&oracle_key,
&oracle_owner_key,
&aggregator_key,
&aggregator_owner_key,
0,
[1; 32]
).unwrap(),
vec![
&mut oracle_account,
&mut oracle_owner_account,
&mut clock_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// remove an unexist oracle (index 1)
assert_eq!(
Err(Error::NotFoundOracle.into()),
do_process_instruction(
remove_oracle(
&program_id,
&aggregator_key,
&aggregator_owner_key,
1
).unwrap(),
vec![
&mut aggregator_account,
&mut aggregator_owner_account,
]
)
);
// will be successful
do_process_instruction(
remove_oracle(
&program_id,
&aggregator_key,
&aggregator_owner_key,
0
).unwrap(),
vec![
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
}
#[test]
fn test_submit() {
let program_id = Pubkey::new_unique();
let oracle_key = Pubkey::new_unique();
let oracle_owner_key = Pubkey::new_unique();
let aggregator_key = Pubkey::new_unique();
let aggregator_owner_key = Pubkey::new_unique();
let mut rent_sysvar = rent_sysvar();
let mut clock_sysvar = clock_sysvar();
let mut oracle_account = SolanaAccount::new(
oracle_minimum_balance(),
Oracle::get_packed_len(),
&program_id
);
let mut aggregator_account = SolanaAccount::new(
aggregator_minimum_balance(),
Aggregator::get_packed_len(),
&program_id
);
let mut oracle_owner_account = SolanaAccount::default();
let mut aggregator_owner_account = SolanaAccount::default();
// initialize aggregator
do_process_instruction(
initialize(
&program_id,
&aggregator_key,
&aggregator_owner_key,
6,
1,
9999,
[1; 32]
).unwrap(),
vec![
&mut rent_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// add oracle (index 0)
do_process_instruction(
add_oracle(
&program_id,
&oracle_key,
&oracle_owner_key,
&aggregator_key,
&aggregator_owner_key,
0,
[1; 32]
).unwrap(),
vec![
&mut oracle_account,
&mut oracle_owner_account,
&mut clock_sysvar,
&mut aggregator_account,
&mut aggregator_owner_account,
]
).unwrap();
// oracle submit
do_process_instruction(
submit(
&program_id,
&aggregator_key,
&oracle_key,
&oracle_owner_key,
1
).unwrap(),
vec![
&mut aggregator_account,
&mut clock_sysvar,
&mut oracle_account,
&mut oracle_owner_account,
]
).unwrap();
// submission cooling
assert_eq!(
Err(Error::SubmissonCooling.into()),
do_process_instruction(
submit(
&program_id,
&aggregator_key,
&oracle_key,
&oracle_owner_key,
1
).unwrap(),
vec![
&mut aggregator_account,
&mut clock_sysvar,
&mut oracle_account,
&mut oracle_owner_account,
]
)
);
}
}

View File

@ -1,18 +1,16 @@
//! State transition types
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use crate::instruction::{MAX_ORACLES};
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
pubkey::Pubkey,
clock::{UnixTimestamp},
};
/// Aggregator data.
#[repr(C)]
#[derive(Clone, Debug, Copy, Default, PartialEq)]
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Aggregator {
/// The interval(seconds) of an oracle's each submission
pub submit_interval: u32,
@ -25,10 +23,9 @@ pub struct Aggregator {
/// is initialized
pub is_initialized: bool,
/// authority
pub owner: Pubkey,
pub owner: [u8; 32],
/// submissions
pub submissions: [Submission; MAX_ORACLES],
}
impl IsInitialized for Aggregator {
@ -39,73 +36,22 @@ impl IsInitialized for Aggregator {
impl Sealed for Aggregator {}
impl Pack for Aggregator {
const LEN: usize = 85 + MAX_ORACLES*48;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, 85 + MAX_ORACLES*48];
let (
submit_interval,
min_submission_value,
max_submission_value,
description,
is_initialized,
owner,
submissions,
) = array_refs![src, 4, 8, 8, 32, 1, 32, MAX_ORACLES*48];
let is_initialized = match is_initialized {
[0] => false,
[1] => true,
_ => return Err(ProgramError::InvalidAccountData),
};
Ok(Aggregator {
submit_interval: u32::from_le_bytes(*submit_interval),
min_submission_value: u64::from_le_bytes(*min_submission_value),
max_submission_value: u64::from_le_bytes(*max_submission_value),
description: *description,
is_initialized,
owner: Pubkey::new_from_array(*owner),
submissions: unpack_submissions(submissions),
})
}
// 48 is submission packed length
const LEN: usize = 85 + MAX_ORACLES * 48;
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, 85 + MAX_ORACLES*48];
let (
submit_interval_dst,
min_submission_value_dst,
max_submission_value_dst,
description_dst,
is_initialized_dst,
owner_dst,
submissions_dst,
) = mut_array_refs![dst, 4, 8, 8, 32, 1, 32, MAX_ORACLES*48];
let data = self.try_to_vec().unwrap();
dst[..data.len()].copy_from_slice(&data);
}
let &Aggregator {
submit_interval,
min_submission_value,
max_submission_value,
description,
is_initialized,
owner,
ref submissions,
} = self;
*submit_interval_dst = submit_interval.to_le_bytes();
*min_submission_value_dst = min_submission_value.to_le_bytes();
*max_submission_value_dst = max_submission_value.to_le_bytes();
*description_dst = description;
owner_dst.copy_from_slice(owner.as_ref());
is_initialized_dst[0] = is_initialized as u8;
pack_submissions(submissions, submissions_dst);
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut mut_src: &[u8] = src;
Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
}
}
/// Oracle data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Oracle {
/// submission
pub submission: u64,
@ -118,9 +64,9 @@ pub struct Oracle {
/// withdrawable
pub withdrawable: u64,
/// aggregator
pub aggregator: Pubkey,
pub aggregator: [u8; 32],
/// owner
pub owner: Pubkey,
pub owner: [u8; 32],
}
impl IsInitialized for Oracle {
@ -132,110 +78,82 @@ impl IsInitialized for Oracle {
impl Sealed for Oracle {}
impl Pack for Oracle {
const LEN: usize = 121;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, 121];
let (
submission, next_submit_time, description, is_initialized,
withdrawable, aggregator, owner,
) = array_refs![src, 8, 8, 32, 1, 8, 32, 32];
let is_initialized = match is_initialized {
[0] => false,
[1] => true,
_ => return Err(ProgramError::InvalidAccountData),
};
Ok(Oracle {
submission: u64::from_le_bytes(*submission),
next_submit_time: i64::from_le_bytes(*next_submit_time),
description: *description,
is_initialized,
withdrawable: u64::from_le_bytes(*withdrawable),
aggregator: Pubkey::new_from_array(*aggregator),
owner: Pubkey::new_from_array(*owner),
})
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = self.try_to_vec().unwrap();
dst[..data.len()].copy_from_slice(&data);
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, 121];
let (
submission_dst,
next_submit_time_dst,
description_dst,
is_initialized_dst,
withdrawable_dst,
aggregator_dst,
owner_dst,
) = mut_array_refs![dst, 8, 8, 32, 1, 8, 32, 32];
let &Oracle {
submission,
next_submit_time,
description,
is_initialized,
withdrawable,
aggregator,
owner,
} = self;
*submission_dst = submission.to_le_bytes();
*next_submit_time_dst = next_submit_time.to_le_bytes();
*description_dst = description;
is_initialized_dst[0] = is_initialized as u8;
*withdrawable_dst = withdrawable.to_le_bytes();
aggregator_dst.copy_from_slice(aggregator.as_ref());
owner_dst.copy_from_slice(owner.as_ref());
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut mut_src: &[u8] = src;
Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
}
}
/// Submission data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Submission {
/// submit time
pub time: UnixTimestamp,
/// value
pub value: u64,
/// oracle
pub oracle: Pubkey,
pub oracle: [u8; 32],
}
// Helpers
fn unpack_submissions(mut dst: &[u8]) -> [Submission; MAX_ORACLES] {
let mut arr = [Submission::default(); MAX_ORACLES];
for i in 0 .. MAX_ORACLES {
let ( submission, rem ) = array_refs![dst, 48; ..;];
let ( time, value, oracle ) = array_refs![submission, 8, 8, 32];
arr[i] = Submission {
time: i64::from_le_bytes(*time),
value: u64::from_le_bytes(*value),
oracle: Pubkey::new_from_array(*oracle),
};
dst = rem;
impl Sealed for Submission {}
impl Pack for Submission {
const LEN: usize = 48;
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = self.try_to_vec().unwrap();
dst[..data.len()].copy_from_slice(&data);
}
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut mut_src: &[u8] = src;
Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
}
arr
}
fn pack_submissions(src: &[Submission; MAX_ORACLES], mut dst: &mut [u8]) {
for i in 0 .. MAX_ORACLES {
let ( submission, rem ) = mut_array_refs![dst, 48; ..;];
let (
time_dst,
value_dst,
oracle_dst,
) = mut_array_refs![&mut *submission, 8, 8, 32];
#[cfg(test)]
mod tests {
use super::*;
use crate::borsh_utils;
let &Submission { time, value, oracle } = &src[i];
#[test]
fn test_get_packed_len() {
assert_eq!(
Aggregator::get_packed_len(),
borsh_utils::get_packed_len::<Aggregator>()
);
*time_dst = time.to_le_bytes();
*value_dst = value.to_le_bytes();
oracle_dst.copy_from_slice(oracle.as_ref());
assert_eq!(
Oracle::get_packed_len(),
borsh_utils::get_packed_len::<Oracle>()
);
dst = rem;
assert_eq!(
Submission::get_packed_len(),
borsh_utils::get_packed_len::<Submission>()
);
}
#[test]
fn test_serialize_bytes() {
assert_eq!(
Submission {
time: 0,
value: 1,
oracle: [1; 32]
}.try_to_vec().unwrap(),
vec![
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]
);
}
}

View File

@ -78,8 +78,10 @@ export async function start(params: StartParams) {
submission: BigInt(curPriceCent),
owner: oracleOwner,
})
} catch(err) {}
} catch(err) {
console.log(err)
}
console.log("submit success!")
payerWallet.conn.getAccountInfo(oracle).then((accountInfo) => {