Initial commit

This commit is contained in:
czl1378 2020-11-27 06:19:50 +08:00
commit b7230006b3
10 changed files with 460 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

24
Cargo.toml Normal file
View File

@ -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"]

2
Xargo.toml Normal file
View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

BIN
build/flux-aggregator.so Executable file

Binary file not shown.

27
src/entrypoint.rs Normal file
View File

@ -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(())
}

30
src/error.rs Normal file
View File

@ -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"
}
}

105
src/instruction.rs Normal file
View File

@ -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())
}
}
}

15
src/lib.rs Normal file
View File

@ -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;

132
src/processor.rs Normal file
View File

@ -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"),
}
}
}

123
src/state.rs Normal file
View File

@ -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;
}
}