Initial commit
This commit is contained in:
commit
b7230006b3
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "flux-aggregator"
|
||||
version = "0.1.0"
|
||||
authors = ["czl1378 <czl1378@126.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.4.8"
|
||||
byteorder = "1.3"
|
||||
thiserror = "1.0"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
arrayref = "0.3.6"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[features]
|
||||
program = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
Binary file not shown.
|
@ -0,0 +1,27 @@
|
|||
//! Program entrypoint
|
||||
|
||||
use crate::{error::Error, processor::Processor};
|
||||
|
||||
use solana_program::{
|
||||
account_info::{AccountInfo},
|
||||
entrypoint,
|
||||
program_error::PrintProgramError,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
// Program entrypoint's implementation
|
||||
fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
if let Err(error) = Processor::process(program_id, accounts, instruction_data) {
|
||||
// catch the error so we can print it
|
||||
error.print::<Error>();
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//! Error types
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
use solana_program::{decode_error::DecodeError, program_error::ProgramError};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that may be returned by the program.
|
||||
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Invalid instruction
|
||||
#[error("Invalid instruction")]
|
||||
InvalidInstruction,
|
||||
/// Already in use
|
||||
#[error("Already in use")]
|
||||
AlreadyInUse,
|
||||
/// Not rent exempt
|
||||
#[error("Not rent exempt")]
|
||||
NotRentExempt,
|
||||
}
|
||||
|
||||
impl From<Error> for ProgramError {
|
||||
fn from(e: Error) -> Self {
|
||||
ProgramError::Custom(e as u32)
|
||||
}
|
||||
}
|
||||
impl<T> DecodeError<T> for Error {
|
||||
fn type_of() -> &'static str {
|
||||
"Error"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
//! Instruction types
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use solana_program::{
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Maximum number of oracles
|
||||
pub const MAX_ORACLES: usize = 21;
|
||||
|
||||
/// Instructions supported by the program.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Instruction {
|
||||
/// Initializes a new Aggregator
|
||||
Initialize {
|
||||
/// The authority/multisignature
|
||||
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,
|
||||
},
|
||||
|
||||
/// Add an oracle
|
||||
AddOracle {
|
||||
|
||||
},
|
||||
|
||||
/// Remove an oracle
|
||||
RemoveOracle {
|
||||
|
||||
},
|
||||
|
||||
/// Called by oracles when they have witnessed a need to update
|
||||
Submit {
|
||||
/// submission is the updated data that the oracle is submitting
|
||||
submission: u64,
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Unpacks a byte buffer into a [Instruction](enum.Instruction.html).
|
||||
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
|
||||
use Error::InvalidInstruction;
|
||||
|
||||
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
|
||||
Ok(match tag {
|
||||
0 => {
|
||||
let (authority, rest) = Self::unpack_pubkey(rest)?;
|
||||
let description = rest
|
||||
.get(..32)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.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)?;
|
||||
|
||||
Self::Initialize {
|
||||
authority,
|
||||
description,
|
||||
min_submission_value: min_submission_value,
|
||||
max_submission_value: max_submission_value,
|
||||
}
|
||||
},
|
||||
1 => {
|
||||
let submission = rest
|
||||
.get(..8)
|
||||
.and_then(|slice| slice.try_into().ok())
|
||||
.map(u64::from_le_bytes)
|
||||
.ok_or(InvalidInstruction)?;
|
||||
Self::Submit { submission }
|
||||
},
|
||||
_ => return Err(Error::InvalidInstruction.into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn unpack_pubkey(input: &[u8]) -> Result<(Pubkey, &[u8]), ProgramError> {
|
||||
if input.len() >= 32 {
|
||||
let (key, rest) = input.split_at(32);
|
||||
let pk = Pubkey::new(key);
|
||||
Ok((pk, rest))
|
||||
} else {
|
||||
Err(Error::InvalidInstruction.into())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! An Flux Aggregator program for the Solana blockchain
|
||||
|
||||
pub mod instruction;
|
||||
pub mod processor;
|
||||
pub mod error;
|
||||
pub mod state;
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
pub mod entrypoint;
|
||||
|
||||
// Export current sdk types for downstream users building with a different sdk version
|
||||
pub use solana_program;
|
|
@ -0,0 +1,132 @@
|
|||
//! Program state processor
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
instruction::{Instruction, MAX_ORACLES},
|
||||
state::{Aggregator},
|
||||
};
|
||||
use num_traits::FromPrimitive;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
decode_error::DecodeError,
|
||||
entrypoint::ProgramResult,
|
||||
info,
|
||||
program_pack::{Pack},
|
||||
program_error::{PrintProgramError},
|
||||
pubkey::Pubkey,
|
||||
sysvar::{rent::Rent, Sysvar},
|
||||
};
|
||||
|
||||
/// Program state handler.
|
||||
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)?;
|
||||
|
||||
match instruction {
|
||||
Instruction::Initialize {
|
||||
authority,
|
||||
description,
|
||||
min_submission_value,
|
||||
max_submission_value,
|
||||
} => {
|
||||
info!("Instruction: Initialize");
|
||||
info!(&format!("description: {:?}", description)[..]);
|
||||
|
||||
Self::process_initialize(
|
||||
accounts, authority, description, min_submission_value, max_submission_value
|
||||
)
|
||||
},
|
||||
Instruction::AddOracle {
|
||||
} => {
|
||||
info!("Instruction: AddOracle");
|
||||
Self::process_add_oracle()
|
||||
},
|
||||
Instruction::RemoveOracle {
|
||||
} => {
|
||||
info!("Instruction: RemoveOracle");
|
||||
Self::process_remove_oracle()
|
||||
},
|
||||
Instruction::Submit {
|
||||
submission,
|
||||
} => {
|
||||
info!("Instruction: Submit");
|
||||
Self::process_submit(submission)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an [Initialize](enum.Instruction.html) instruction.
|
||||
pub fn process_initialize(
|
||||
accounts: &[AccountInfo],
|
||||
authority: Pubkey,
|
||||
description: [u8; 32],
|
||||
min_submission_value: u64,
|
||||
max_submission_value: u64,
|
||||
) -> 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 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) {
|
||||
return Err(Error::NotRentExempt.into());
|
||||
}
|
||||
|
||||
aggregator.min_submission_value = min_submission_value;
|
||||
aggregator.max_submission_value = max_submission_value;
|
||||
aggregator.description = description;
|
||||
aggregator.is_initialized = true;
|
||||
aggregator.answer = 123;
|
||||
aggregator.submissions = [0; MAX_ORACLES];
|
||||
aggregator.authority = authority;
|
||||
|
||||
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes an [AddOracle](enum.Instruction.html) instruction.
|
||||
pub fn process_add_oracle(
|
||||
|
||||
) -> ProgramResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes an [RemoveOracle](enum.Instruction.html) instruction.
|
||||
pub fn process_remove_oracle(
|
||||
|
||||
) -> ProgramResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes an [Submit](enum.Instruction.html) instruction.
|
||||
pub fn process_submit(
|
||||
_submission: u64,
|
||||
) -> ProgramResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PrintProgramError for Error {
|
||||
fn print<E>(&self)
|
||||
where
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
//! State transition types
|
||||
|
||||
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},
|
||||
clock::UnixTimestamp,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
/// Aggregator data.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy, Default, PartialEq)]
|
||||
pub struct Aggregator {
|
||||
/// min submission value
|
||||
pub min_submission_value: u64,
|
||||
/// max submission value
|
||||
pub max_submission_value: u64,
|
||||
/// description
|
||||
pub description: [u8; 32],
|
||||
/// is initialized
|
||||
pub is_initialized: bool,
|
||||
/// answer
|
||||
pub answer: u64,
|
||||
/// authority
|
||||
pub authority: Pubkey,
|
||||
/// submissions
|
||||
pub submissions: [u64; MAX_ORACLES],
|
||||
}
|
||||
|
||||
impl IsInitialized for Aggregator {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.is_initialized
|
||||
}
|
||||
}
|
||||
|
||||
impl Sealed for Aggregator {}
|
||||
impl Pack for Aggregator {
|
||||
const LEN: usize = 89 + MAX_ORACLES*8;
|
||||
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||
let src = array_ref![src, 0, 89 + MAX_ORACLES*8];
|
||||
let (
|
||||
min_submission_value, max_submission_value,
|
||||
description, is_initialized, answer, authority, sub_rem,
|
||||
) = array_refs![src, 8, 8, 32, 1, 8, 32; ..;];
|
||||
|
||||
let is_initialized = match is_initialized {
|
||||
[0] => false,
|
||||
[1] => true,
|
||||
_ => return Err(ProgramError::InvalidAccountData),
|
||||
};
|
||||
|
||||
Ok(Aggregator {
|
||||
min_submission_value: u64::from_le_bytes(*min_submission_value),
|
||||
max_submission_value: u64::from_le_bytes(*max_submission_value),
|
||||
description: *description,
|
||||
is_initialized,
|
||||
answer: u64::from_le_bytes(*answer),
|
||||
authority: Pubkey::new_from_array(*authority),
|
||||
submissions: unpack_submissions(sub_rem),
|
||||
})
|
||||
}
|
||||
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||
let dst = array_mut_ref![dst, 0, 89 + MAX_ORACLES*8];
|
||||
let (
|
||||
min_submission_value_dst,
|
||||
max_submission_value_dst,
|
||||
description_dst,
|
||||
is_initialized_dst,
|
||||
answer_dst,
|
||||
authority_dst,
|
||||
sub_rem,
|
||||
) = mut_array_refs![dst, 8, 8, 32, 1, 8, 32; ..;];
|
||||
|
||||
let &Aggregator {
|
||||
min_submission_value,
|
||||
max_submission_value,
|
||||
description,
|
||||
is_initialized,
|
||||
answer,
|
||||
ref authority,
|
||||
ref submissions,
|
||||
} = self;
|
||||
|
||||
*min_submission_value_dst = min_submission_value.to_le_bytes();
|
||||
*max_submission_value_dst = max_submission_value.to_le_bytes();
|
||||
*description_dst = description;
|
||||
is_initialized_dst[0] = is_initialized as u8;
|
||||
*answer_dst = answer.to_le_bytes();
|
||||
authority_dst.copy_from_slice(authority.as_ref());
|
||||
pack_submissions(submissions, sub_rem);
|
||||
}
|
||||
}
|
||||
|
||||
/// Oracle data.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Oracle {
|
||||
/// next submit time
|
||||
pub next_submit_time: UnixTimestamp,
|
||||
}
|
||||
|
||||
// Helpers
|
||||
fn unpack_submissions(mut dst: &[u8]) -> [u64; MAX_ORACLES] {
|
||||
let mut arr = [0u64; MAX_ORACLES];
|
||||
for i in 0 .. MAX_ORACLES {
|
||||
let (s, rem) = array_refs![dst, 8; ..;];
|
||||
arr[i] = u64::from_le_bytes(*s);
|
||||
dst = rem;
|
||||
}
|
||||
arr
|
||||
}
|
||||
|
||||
fn pack_submissions(src: &[u64; MAX_ORACLES], mut dst: &mut [u8]) {
|
||||
for i in 0 .. MAX_ORACLES {
|
||||
let (s, rem) = mut_array_refs![dst, 8; ..;];
|
||||
*s = src[i].to_le_bytes();
|
||||
dst = rem;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue