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