use 'borsh' for serialization; add some tests
This commit is contained in:
parent
64ac2ead2f
commit
1f32a52df7
|
@ -5,3 +5,7 @@ yarn.lock
|
|||
build
|
||||
.yarn
|
||||
dist
|
||||
.yarnrc.yml
|
||||
.pnp.js
|
||||
.env
|
||||
deploy.json
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 data = self.try_to_vec().unwrap();
|
||||
dst[..data.len()].copy_from_slice(&data);
|
||||
}
|
||||
|
||||
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 &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);
|
||||
}
|
||||
|
||||
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; ..;];
|
||||
impl Sealed for Submission {}
|
||||
impl Pack for Submission {
|
||||
const LEN: usize = 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;
|
||||
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||
let data = self.try_to_vec().unwrap();
|
||||
dst[..data.len()].copy_from_slice(&data);
|
||||
}
|
||||
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];
|
||||
|
||||
let &Submission { time, value, oracle } = &src[i];
|
||||
|
||||
*time_dst = time.to_le_bytes();
|
||||
*value_dst = value.to_le_bytes();
|
||||
oracle_dst.copy_from_slice(oracle.as_ref());
|
||||
|
||||
dst = rem;
|
||||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||
let mut mut_src: &[u8] = src;
|
||||
Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::borsh_utils;
|
||||
|
||||
#[test]
|
||||
fn test_get_packed_len() {
|
||||
assert_eq!(
|
||||
Aggregator::get_packed_len(),
|
||||
borsh_utils::get_packed_len::<Aggregator>()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Oracle::get_packed_len(),
|
||||
borsh_utils::get_packed_len::<Oracle>()
|
||||
);
|
||||
|
||||
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
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -78,7 +78,9 @@ export async function start(params: StartParams) {
|
|||
submission: BigInt(curPriceCent),
|
||||
owner: oracleOwner,
|
||||
})
|
||||
} catch(err) {}
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
console.log("submit success!")
|
||||
|
||||
|
|
Loading…
Reference in New Issue