Initial Solitaire Base

Change-Id: I6789364464ef03ada87b7d446abb43df033d133d
This commit is contained in:
Reisen 2021-05-25 16:32:45 +00:00
parent 2c8e25ec12
commit 652f4ea3b1
21 changed files with 923 additions and 3957 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
[workspace] [workspace]
members = [ members = [
"programs/*", "programs/*",
"client"
] ]

View File

@ -1,22 +0,0 @@
[package]
name = "anchor-bridge"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "anchor_bridge"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
# default = ["anchor-debug"]
default = []
anchor-debug = ["anchor-lang/anchor-debug"]
[dependencies]
anchor-lang = {git = "https://github.com/drozdziak1/anchor", branch = "anchor-debug-feature"}
byteorder = "1.4.3"
sha3 = "0.9.1"

View File

@ -1,8 +0,0 @@
pub mod initialize;
pub mod publish_message;
pub mod verify_signatures;
// Re-expose underlying module functions and data, for consuming APIs to use.
pub use initialize::*;
pub use publish_message::*;
pub use verify_signatures::*;

View File

@ -1,32 +0,0 @@
use anchor_lang::{prelude::*, solana_program};
use crate::{
Result,
accounts,
anchor_bridge::Bridge,
types::{BridgeConfig, Index},
Initialize,
InitializeData,
MAX_LEN_GUARDIAN_KEYS,
};
pub fn initialize(
ctx: Context<Initialize>,
len_guardians: u8,
initial_guardian_key: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
config: BridgeConfig,
) -> Result<Bridge> {
let index = Index(0);
// Initialize the Guardian Set for the first time.
ctx.accounts.guardian_set.index = index;
ctx.accounts.guardian_set.creation_time = ctx.accounts.clock.unix_timestamp as u32;
ctx.accounts.guardian_set.keys = initial_guardian_key;
ctx.accounts.guardian_set.len_keys = len_guardians;
// Create an initial bridge state, labeled index 0.
Ok(Bridge {
guardian_set_index: index,
config,
})
}

View File

@ -1,104 +0,0 @@
use anchor_lang::{prelude::*, solana_program};
use crate::{
accounts,
anchor_bridge::Bridge,
types::{BridgeConfig, Index, Chain},
PublishMessage,
PostedMessage,
Result,
MAX_LEN_GUARDIAN_KEYS,
};
/// Constant fee for VAA transactions, measured in lamports.
const VAA_TX_FEE: u64 = 18 * 10000;
/// Maximum size of a posted VAA
pub const MAX_PAYLOAD_SIZE: usize = 400;
pub fn publish_message(bridge: &mut Bridge, ctx: Context<PublishMessage>, nonce: u8) -> Result<()> {
// Manually create message account, as Anchor can't do it.
let mut message: ProgramAccount<PostedMessage> = {
// First create the message account. 8 Bytes additional for the discriminator.
let space = 8 + PostedMessage::default().try_to_vec().unwrap().len();
let lamports = ctx.accounts.rent.minimum_balance(space);
let ix = solana_program::system_instruction::create_account(
ctx.accounts.payer.key,
ctx.accounts.message.key,
lamports,
space as u64,
ctx.program_id,
);
// Derived seeds for a message account.
let seeds = [
ctx.program_id.as_ref(),
ctx.accounts.emitter.key.as_ref(),
&[nonce],
];
// Wrap seeds in a signer list.
let signer = &[&seeds[..]];
// Create account using generated data.
solana_program::program::invoke_signed(
&ix,
&[
ctx.accounts.emitter.clone(),
ctx.accounts.system_program.clone(),
],
signer,
)?;
// Deserialize the newly created account into an object.
ProgramAccount::try_from_init(&ctx.accounts.message)?
};
// Initialize Message data.
message.submission_time = ctx.accounts.clock.unix_timestamp as u32;
message.emitter_chain = Chain::Solana;
message.emitter_address = ctx.accounts.emitter.key.to_bytes();
// Manually persist changes since we manually created the account.
message.exit(ctx.program_id)?;
Ok(())
}
// A const time calculation of the fee required to publish a message.
//
// Cost breakdown:
// - 2 Signatures
// - 1 Claimed VAA Rent
// - 2x Guardian Fees
const fn calculate_transfer_fee() -> u64 {
use std::mem::size_of;
const SIGNATURE_COST: u64 = size_of::<SignatureState>() as u64;
const VAA_COST: u64 = size_of::<ClaimedVAA>() as u64;
const VAA_FEE: u64 = VAA_TX_FEE;
SIGNATURE_COST + VAA_COST + VAA_FEE
}
/// Signature state
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SignatureState {
/// signatures of validators
pub signatures: [[u8; 65]; MAX_LEN_GUARDIAN_KEYS],
/// hash of the data
pub hash: [u8; 32],
/// index of the guardian set
pub guardian_set_index: u32,
}
/// Record of a claimed VAA
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ClaimedVAA {
/// hash of the vaa
pub hash: [u8; 32],
/// time the vaa was submitted
pub vaa_time: u32,
}

View File

@ -1,139 +0,0 @@
use anchor_lang::{prelude::*, solana_program};
use crate::{Result, accounts, anchor_bridge::Bridge, VerifySig, VerifySigsData, MAX_LEN_GUARDIAN_KEYS};
use byteorder::ByteOrder;
use sha3::Digest;
use std::io::Write;
pub const MIN_SECP_PROGRAM_DATA_LEN: usize = 3;
/// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo {
/// index of the signer in the guardianset
signer_index: u8,
/// index of the signature in the secp instruction
sig_index: u8,
}
struct SecpInstructionPart<'a> {
address: &'a [u8],
signature: &'a [u8],
msg_offset: u16,
msg_size: u16,
}
#[inline(always)]
fn filter_empty_signatures(signers: &[i8; MAX_LEN_GUARDIAN_KEYS]) -> Vec<SigInfo> {
signers
.iter()
.enumerate()
.filter_map(|(i, p)| match *p {
-1 => None,
ix => Some(SigInfo {
sig_index: ix as u8,
signer_index: i as u8,
}),
})
.collect()
}
pub fn verify_signatures(
bridge: &mut Bridge,
ctx: Context<VerifySig>,
hash: [u8; 32],
signers: [i8; MAX_LEN_GUARDIAN_KEYS],
initial_creation: bool,
) -> Result<()> {
let sig_infos = filter_empty_signatures(&signers);
let ix_acc = &ctx.accounts.instruction_sysvar;
let current_ix_idx =
solana_program::sysvar::instructions::load_current_index(&ix_acc.try_borrow_data()?);
if current_ix_idx == 0 {
return Err(ProgramError::InvalidInstructionData.into());
}
// Retrieve the previous instruction
let prev_ix_idx = (current_ix_idx - 1) as u8;
let prev_ix = solana_program::sysvar::instructions::load_instruction_at(
prev_ix_idx as usize,
&ix_acc.try_borrow_mut_data()?,
)
.map_err(|_e| ProgramError::InvalidAccountData)?;
// Does prev_ix call the right program?
if prev_ix.program_id != solana_program::secp256k1_program::id() {
return Err(ProgramError::InvalidArgument.into());
}
// Is the data correctly sized?
let prev_data_len = prev_ix.data.len();
if prev_data_len < MIN_SECP_PROGRAM_DATA_LEN {
return Err(ProgramError::InvalidAccountData.into());
}
// Parse the instruction data for verification
let sig_len = prev_ix.data[0];
let mut index = 1;
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
for i in 0..sig_len {
let sig_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize;
index += 2;
let sig_ix = prev_ix.data[index];
index += 1;
let address_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]) as usize;
index += 2;
let address_ix = prev_ix.data[index];
index += 1;
let msg_offset = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]);
index += 2;
let msg_size = byteorder::LE::read_u16(&prev_ix.data[index..index + 2]);
index += 2;
let msg_ix = prev_ix.data[index];
index += 1;
if address_ix != prev_ix_idx || msg_ix != prev_ix_idx || sig_ix != prev_ix_idx {
return Err(ProgramError::InvalidArgument.into());
}
let address: &[u8] = &prev_ix.data[address_offset..address_offset + 20];
let signature: &[u8] = &prev_ix.data[sig_offset..sig_offset + 65];
// Make sure that all messages are equal
if i > 0 {
if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size {
return Err(ProgramError::InvalidArgument.into());
}
}
secp_ixs.push(SecpInstructionPart {
address,
signature,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument.into());
}
// Check message
let message = &prev_ix.data
[secp_ixs[0].msg_offset as usize..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
let mut h = sha3::Keccak256::default();
if let Err(_) = h.write(message) {
return Err(ProgramError::InvalidArgument.into());
};
let msg_hash: [u8; 32] = h.finalize().into();
if msg_hash != hash {
return Err(ProgramError::InvalidArgument.into());
}
// ------ 8>< *SNIP <>8 --------
// In original bridge program specific bridge state checks follow
Ok(())
}

View File

@ -1,208 +0,0 @@
use anchor_lang::{prelude::*, solana_program};
mod api;
mod types;
use types::{Chain, Index};
pub use types::BridgeConfig;
// Without this, Anchor's derivation macros break. It requires names with no path components at all
// otherwise it errors.
use anchor_bridge::Bridge;
/// chain id of this chain
pub const CHAIN_ID_SOLANA: u8 = Chain::Solana as u8;
/// maximum number of guardians
pub const MAX_LEN_GUARDIAN_KEYS: usize = 20;
#[derive(Accounts)]
pub struct VerifySig<'info> {
pub system: AccountInfo<'info>,
pub instruction_sysvar: AccountInfo<'info>,
pub bridge_info: ProgramState<'info, BridgeInfo>,
pub sig_info: AccountInfo<'info>,
pub guardian_set_info: ProgramState<'info, GuardianSetInfo>,
pub payer_info: AccountInfo<'info>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
pub struct VerifySigsData {
/// hash of the VAA
pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS],
/// indicates whether this verification should only succeed if the sig account does not exist
pub initial_creation: bool,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
// /// Account used to pay for auxillary instructions.
#[account(signer)]
pub payer: AccountInfo<'info>,
/// Information about the current guardian set.
#[account(init, associated = state)]
pub guardian_set: ProgramAccount<'info, GuardianSetInfo>,
/// State struct, derived by #[state], used for associated accounts.
pub state: ProgramState<'info, Bridge>,
/// Used for timestamping actions.
pub clock: Sysvar<'info, Clock>,
/// Required by Anchor for associated accounts.
pub rent: Sysvar<'info, Rent>,
/// Required by Anchor for associated accounts.
pub system_program: AccountInfo<'info>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
pub struct InitializeData {
/// number of initial guardians
pub len_guardians: u8,
/// guardians that are allowed to sign mints
pub initial_guardian_keys: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
/// config for the bridge
pub config: BridgeConfig,
}
#[derive(Accounts)]
pub struct PublishMessage<'info> {
/// No need to verify - only used as the fee payer for account creation.
#[account(signer)]
pub payer: AccountInfo<'info>,
/// The emitter, only used as metadata. We verify that the account is a signer to prevent
/// messages from being spoofed.
#[account(signer)]
pub emitter: AccountInfo<'info>,
/// The message account to store data in, note that this cannot be derived by serum and so the
/// pulish_message handler does this by hand.
pub message: AccountInfo<'info>,
/// State struct, derived by #[state], used for associated accounts.
pub state: ProgramState<'info, Bridge>,
/// Instructions used for transaction reflection.
pub instructions: AccountInfo<'info>,
/// Clock used for timestamping.
pub clock: Sysvar<'info, Clock>,
/// Required by Anchor for associated accounts.
pub rent: Sysvar<'info, Rent>,
/// Required by Anchor for associated accounts.
pub system_program: AccountInfo<'info>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
pub struct PublishMessageData {}
#[program]
pub mod anchor_bridge {
use super::*;
#[state]
pub struct Bridge {
pub guardian_set_index: types::Index,
pub config: types::BridgeConfig,
}
impl Bridge {
pub fn new(ctx: Context<Initialize>, data: InitializeData) -> Result<Self> {
msg!("Yeah boiiiiiii");
api::initialize(
ctx,
data.len_guardians,
data.initial_guardian_keys,
data.config,
)
}
pub fn publish_message(
&mut self,
ctx: Context<PublishMessage>,
data: PublishMessageData,
nonce: u8,
) -> Result<()> {
// Sysvar trait not implemented for Instructions by sdk, so manual check required. See
// the VerifySig struct for more info.
if *ctx.accounts.instructions.key != solana_program::sysvar::instructions::id() {
return Err(ErrorCode::InvalidSysVar.into());
}
api::publish_message(self, ctx, nonce)
}
pub fn verify_signatures(
&mut self,
ctx: Context<VerifySig>,
data: VerifySigsData,
) -> Result<()> {
// Sysvar trait not implemented for Instructions by sdk, so manual check required. See
// the VerifySig struct for more info.
if *ctx.accounts.instruction_sysvar.key != solana_program::sysvar::instructions::id() {
return Err(ErrorCode::InvalidSysVar.into());
}
api::verify_signatures(self, ctx, data.hash, data.signers, data.initial_creation)
}
}
}
#[error]
pub enum ErrorCode {
#[msg("System account pubkey did not match expected address.")]
InvalidSysVar,
}
#[account]
pub struct BridgeInfo {}
#[associated]
pub struct GuardianSetInfo {
/// Version number of this guardian set.
pub index: Index,
/// Number of keys stored
pub len_keys: u8,
/// public key hashes of the guardian set
pub keys: [[u8; 20]; MAX_LEN_GUARDIAN_KEYS],
/// creation time
pub creation_time: u32,
/// expiration time when VAAs issued by this set are no longer valid
pub expiration_time: u32,
}
/// Record of a posted wormhole message.
#[account]
#[derive(Default)]
pub struct PostedMessage {
/// header of the posted VAA
pub vaa_version: u8,
/// time the vaa was submitted
pub vaa_time: u32,
/// Account where signatures are stored
pub vaa_signature_account: Pubkey,
/// time the posted message was created
pub submission_time: u32,
/// unique nonce for this message
pub nonce: u32,
/// emitter of the message
pub emitter_chain: Chain,
/// emitter of the message
pub emitter_address: [u8; 32],
/// message payload
pub payload: [[u8; 32]; 13],
}

View File

@ -1,29 +0,0 @@
use anchor_lang::prelude::*;
// Enforces a single bumping index number.
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Default)]
pub struct Index(pub u32);
#[repr(C)]
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
pub struct BridgeConfig {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
}
/// An enum with labeled network identifiers. These must be consistent accross all wormhole
/// contracts deployed on each chain.
#[repr(u8)]
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
pub enum Chain {
Unknown,
Solana = 1u8,
}
impl Default for Chain {
fn default() -> Self {
Chain::Unknown
}
}

View File

@ -0,0 +1,22 @@
[package]
name = "example"
version = "0.1.0"
description = "Created with Rocksalt"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "example"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
borsh = "0.8.1"
byteorder = "1.4.3"
solitaire = { path = "../solitaire" }
sha3 = "0.9.1"
solana-program = "*"

View File

@ -0,0 +1,3 @@
mod initialize;
pub use initialize::*;

View File

@ -0,0 +1,55 @@
use crate::types::*;
use solana_program::program_error::ProgramError;
use solitaire::*;
type Payer<'a> = Signer<Info<'a>>;
type GuardianSet<'a> = Derive<Data<'a, GuardianSetData, Uninitialized>, "GuardianSet">;
type Bridge<'a> = Derive<Data<'a, BridgeData, Uninitialized>, "Bridge">;
#[derive(FromAccounts)]
pub struct Initialize<'b> {
pub payer: Payer<'b>,
pub guardian_set: GuardianSet<'b>,
pub bridge: Bridge<'b>,
pub transfer: Transfer<'b>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {}
#[derive(FromAccounts)]
pub struct Transfer<'b> {
pub mint: Data<'b, Test, Initialized>,
pub from: Data<'b, Test, Initialized>,
pub to: Data<'b, Test, Initialized>,
}
impl<'b> InstructionContext<'b> for Transfer<'b> {
fn verify(&self) -> Result<()> {
return if self.mint.mint == self.from.mint {
Ok(())
} else {
Err(SolitaireError::InvalidDerive(*self.mint.0.key).into())
};
}
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct Test {
mint: Pubkey,
}
pub fn initialize(ctx: &ExecutionContext, accs: &mut Initialize, config: BridgeConfig) -> Result<()> {
// Initialize the Guardian Set for the first time.
let index = Index::new(0);
// Initialize Guardian Set
accs.guardian_set.index = index;
accs.guardian_set.creation_time = 0;
accs.guardian_set.keys = Vec::with_capacity(20);
// Initialize the Bridge state for the first time.
accs.bridge.guardian_set_index = index;
accs.bridge.config = config;
Ok(())
}

View File

@ -0,0 +1,15 @@
// #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
// Salt contains the framework definition, single file for now but to be extracted into a cargo
// package as soon as possible.
mod api;
mod types;
use api::{initialize, Initialize};
use types::BridgeConfig;
use solitaire::*;
solitaire! {
Initialize(config: BridgeConfig) => initialize,
}

View File

@ -0,0 +1,47 @@
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(Default, Clone, Copy, BorshSerialize, BorshDeserialize)]
pub struct Index(u8);
impl Index {
pub fn new(n: u8) -> Self {
Index(n)
}
pub fn bump(mut self) -> Self {
self.0 += 1;
self
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct GuardianSetData {
/// Version number of this guardian set.
pub index: Index,
/// public key hashes of the guardian set
pub keys: Vec<u8>,
/// creation time
pub creation_time: u32,
/// expiration time when VAAs issued by this set are no longer valid
pub expiration_time: u32,
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct BridgeConfig {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct BridgeData {
/// The current guardian set index, used to decide which signature sets to accept.
pub guardian_set_index: Index,
/// Bridge configuration, which is set once upon initialization.
pub config: BridgeConfig,
}

View File

@ -0,0 +1,22 @@
[package]
name = "solitaire"
version = "0.1.0"
description = "Solana program framework"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "solitaire"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
borsh = "0.8.1"
byteorder = "1.4.3"
rocksalt = { path = "../../rocksalt" }
sha3 = "0.9.1"
solana-program = "*"

View File

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

View File

@ -0,0 +1,456 @@
#![feature(const_generics)]
#![feature(const_generics_defaults)]
#![allow(warnings)]
pub use rocksalt::*;
// Lacking:
//
// - Error is a lacking as its just a basic enum, maybe use errorcode.
// - Client generation incomplete.
// We need a few Solana things in scope in order to properly abstract Solana.
pub use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
system_program,
sysvar::{self, SysvarId},
system_instruction,
};
// Later on we will define types that don't actually contain data, PhantomData will help us.
pub use std::marker::PhantomData;
// We'll need these traits to make any wrappers we define more ergonomic for users.
pub use std::ops::{Deref, DerefMut};
// Borsh is Solana's goto serialization target, so we'll need this if we want to do any
// serialization on the users behalf.
pub use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::account_info::next_account_info;
use std::slice::Iter;
use solana_program::program::invoke_signed;
use solana_program::rent::Rent;
use solana_program::program_error::ProgramError;
/// There are several places in Solitaire that might fail, we want descriptive errors.
#[derive(Debug)]
pub enum SolitaireError {
/// The AccountInfo parser expected a Signer, but the account did not sign.
InvalidSigner(Pubkey),
/// The AccountInfo parser expected a Sysvar, but the key was invalid.
InvalidSysvar(Pubkey),
/// The AccountInfo parser tried to derive the provided key, but it did not match.
InvalidDerive(Pubkey),
/// The instruction payload itself could not be deserialized.
InstructionDeserializeFailed,
/// An IO error was captured, wrap it up and forward it along.
IoError(std::io::Error),
}
impl Into<ProgramError> for SolitaireError {
fn into(self) -> ProgramError {
//TODO
ProgramError::Custom(0)
}
}
/// Quality of life Result type for the Solitaire stack.
pub type Result<T> = std::result::Result<T, ProgramError>;
pub trait Persist {
fn persist(self);
}
pub trait Seeded {
fn seeds(self) -> Vec<Vec<Vec<u8>>>;
}
// The following set of recursive types form the basis of this library, each one represents a
// constraint to be fulfilled during parsing an account info.
#[repr(transparent)]
pub struct Signer<Next>(Next);
#[repr(transparent)]
pub struct System<Next>(Next);
#[repr(transparent)]
pub struct Sysvar<Next, Var>(Next, PhantomData<Var>);
#[repr(transparent)]
pub struct Derive<Next, const Seed: &'static str>(Next);
/// A shorter type alias for AccountInfo to make types slightly more readable.
pub type Info<'r> = AccountInfo<'r>;
/// Another alias for an AccountInfo that pairs it with the deserialized data that resides in the
/// accounts storage.
///
/// Note on const generics:
///
/// Solana's Rust version is JUST old enough that it cannot use constant variables in its default
/// parameter assignments. But these DO work in the consumption side so a user can still happily
/// use this type by writing for example:
///
/// Data<(), Uninitialized, Lazy>
///
/// But here, we must write `Lazy: bool = true` for now unfortunately.
#[rustfmt::skip]
pub struct Data < 'r, T, const IsInitialized: bool = true, const Lazy: bool = false > (
pub Info<'r >,
pub T,
);
/// A tag for accounts that should be deserialized lazily.
pub const Lazy: bool = true;
/// A tag for accounts that should be deserialized immediately (the default).
pub const Strict: bool = false;
/// A tag for accounts that are expected to have already been initialized.
pub const Initialized: bool = true;
/// A tag for accounts that must be uninitialized.
pub const Uninitialized: bool = false;
/// The context is threaded through each check. Include anything within this structure that you
/// would like to have access to as each layer of dependency is peeled off.
pub struct Context<'a, 'b: 'a, 'c, T> {
/// A reference to the program_id of the current program.
pub this: &'a Pubkey,
pub iter: &'c mut Iter<'a, AccountInfo<'b>>,
/// This is a reference to the instruction data we are processing this
/// account for.
pub data: &'a T,
/// This is a list of dependent keys that are emitted by this verification pipeline. This
/// allows things such as `rent`/`system` to be emitted as required for an account without
/// having to specify them in the original instruction account data.
pub deps: &'c mut Vec<Pubkey>,
info: Option<&'a AccountInfo<'b>>,
}
pub struct ExecutionContext<'a, 'b: 'a> {
/// A reference to the program_id of the current program.
pub program_id: &'a Pubkey,
/// All accounts passed into the program
pub accounts: &'a [AccountInfo<'b>],
}
impl<'a, 'b: 'a, 'c, T> Context<'a, 'b, 'c, T> {
pub fn new(
program: &'a Pubkey,
iter: &'c mut Iter<'a, AccountInfo<'b>>,
data: &'a T,
deps: &'c mut Vec<Pubkey>,
) -> Self {
Context {
this: program,
iter,
data,
deps,
info: None,
}
}
pub fn info<'d>(&'d mut self) -> &'a AccountInfo<'b> {
match self.info {
None => {
let info = next_account_info(self.iter).unwrap();
self.info = Some(info);
info
}
Some(v) => v,
}
}
}
impl<'r, T, const IsInitialized: bool, const Lazy: bool> Deref
for Data<'r, T, IsInitialized, Lazy>
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<'r, T, const IsInitialized: bool, const Lazy: bool> DerefMut
for Data<'r, T, IsInitialized, Lazy>
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.1
}
}
impl<T> Deref for Signer<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for Signer<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T, Var> Deref for Sysvar<T, Var> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T, Var> DerefMut for Sysvar<T, Var> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T> Deref for System<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T> DerefMut for System<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T, const Seed: &'static str> Deref for Derive<T, Seed> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
}
}
impl<T, const Seed: &'static str> DerefMut for Derive<T, Seed> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
}
}
impl<T, const Seed: &'static str> Seeded for Derive<T, Seed> {
fn seeds(self) -> Vec<Vec<Vec<u8>>> {
vec![vec![Seed.try_to_vec().unwrap()]]
}
}
/// Lamports to pay to an account being created
pub enum CreationLamports {
Exempt,
Amount(u64),
}
impl CreationLamports {
/// Amount of lamports to be paid in account creation
pub fn amount(self, size: usize) -> u64 {
match self {
CreationLamports::Exempt => Rent::default().minimum_balance(size),
CreationLamports::Amount(v) => v
}
}
}
impl<const Seed: &'static str> Derive<AccountInfo<'_>, Seed> {
pub fn create(&self, ctx: &ExecutionContext, payer: &Pubkey, lamports: CreationLamports, space: usize, owner: &Pubkey) -> Result<()> {
let ix = system_instruction::create_account(payer, self.0.key, lamports.amount(space), space as u64, owner);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])
}
}
impl<const Seed: &'static str, T: BorshSerialize> Derive<Data<'_, T, Uninitialized>, Seed> {
pub fn create(&self, ctx: &ExecutionContext, payer: &Pubkey, lamports: CreationLamports) -> Result<()> {
// Get serialized struct size
let size = self.0.try_to_vec().unwrap().len();
let ix = system_instruction::create_account(payer, self.0.0.key, lamports.amount(size), size as u64, ctx.program_id);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])
}
}
/// Generic Peel trait. This provides a way to describe what each "peeled"
/// layer of our constraints should check.
pub trait Peel<'a, 'b: 'a, 'c> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized;
}
/// Peel a Derived Key
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b, 'c>
for Derive<T, Seed>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// Attempt to Derive Seed
let (derived, bump) = Pubkey::find_program_address(&[Seed.as_ref()], ctx.this);
match derived == *ctx.info().key {
true => T::peel(ctx).map(|v| Derive(v)),
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key).into()),
}
}
}
/// Peel a Signer.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match ctx.info().is_signer {
true => T::peel(ctx).map(|v| Signer(v)),
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()),
}
}
}
/// Expicitly depend upon the System account.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match true {
true => T::peel(ctx).map(|v| System(v)),
_ => panic!(),
}
}
}
/// Peel a Sysvar
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, Var: SysvarId> Peel<'a, 'b, 'c> for Sysvar<T, Var> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match <Var as SysvarId>::check_id(ctx.info().key) {
true => T::peel(ctx).map(|v| Sysvar(v, PhantomData)),
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()),
}
}
}
/// This is our structural recursion base case, the trait system will stop
/// generating new nested calls here.
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for Info<'b> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
Ok(ctx.info().clone())
}
}
/// This is our structural recursion base case, the trait system will stop
/// generating new nested calls here.
impl<'a, 'b: 'a, 'c, T: BorshDeserialize, const IsInitialized: bool, const Lazy: bool>
Peel<'a, 'b, 'c> for Data<'b, T, IsInitialized, Lazy>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// If we're initializing the type, we should emit system/rent as deps.
if !IsInitialized {
ctx.deps.push(sysvar::rent::ID);
ctx.deps.push(system_program::ID);
}
let data = T::try_from_slice(&mut *ctx.info().data.borrow_mut())
.map_err::<ProgramError, _>(|e| SolitaireError::IoError(e).into())?;
Ok(Data(ctx.info().clone(), data))
}
}
pub trait InstructionContext<'a> {
fn verify(&self) -> Result<()> {
Ok(())
}
fn deps(&self) -> Vec<Pubkey> {
vec![]
}
}
/// Trait definition that describes types that can be constructed from a list of solana account
/// references. A list of dependent accounts is produced as a side effect of the parsing stage.
pub trait FromAccounts<'a, 'b: 'a, 'c> {
fn from<T>(
_: &'a Pubkey,
_: &'c mut Iter<'a, AccountInfo<'b>>,
_: &'a T,
) -> Result<(Self, Vec<Pubkey>)>
where
Self: Sized;
}
/// This is our main codegen macro. It takes as input a list of enum-like variants mapping field
/// types to function calls. The generated code produces:
///
/// - An `Instruction` enum with the enum variants passed in.
/// - A set of functions which take as arguments the enum fields.
/// - A Dispatcher that deserializes bytes into the enum and dispatches the function call.
/// - A set of client calls scoped to the module `api` that can generate instructions.
#[macro_export]
macro_rules! solitaire {
{ $($row:ident($($name:ident: $kind:ty),* $(,)*) => $fn:ident),+ $(,)* } => {
mod instruction {
use super::*;
use borsh::{BorshDeserialize, BorshSerialize};
use solitaire::{Persist, FromAccounts};
/// Generated:
/// This Instruction contains a 1-1 mapping for each enum variant to function call. The
/// function calls can be found below in the `api` module.
#[derive(BorshSerialize, BorshDeserialize)]
enum Instruction {
$($row($($kind,)*),)*
}
/// This entrypoint is generated from the enum above, it deserializes incoming bytes
/// and automatically dispatches to the correct method.
pub fn dispatch<'a, 'b: 'a, 'c>(p: &Pubkey, a: &'c [AccountInfo<'b>], d: &[u8]) -> ProgramResult {
match Instruction::try_from_slice(d)? {
$(
Instruction::$row($($name,)*) => {
let (mut accounts, _deps): ($row, _) = FromAccounts::from(p, &mut a.iter(), &()).unwrap();
$fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, $($name,)*);
accounts.persist();
Ok(())
}
)*
_ => {
Ok(())
}
}
}
pub fn solitaire<'a, 'b: 'a>(p: &Pubkey, a: &'a [AccountInfo<'b>], d: &[u8]) -> ProgramResult {
if let Err(err) = dispatch(p, a, d) {
}
Ok(())
}
}
/// This module contains a 1-1 mapping for each function to an enum variant. The variants
/// can be matched to the Instruction found above.
mod client {
use super::*;
use solana_program::instruction::Instruction;
/// Generated from Instruction Field
$(pub(crate) fn $fn(pid: &Pubkey, $($name: $kind,)*) -> Instruction {
Instruction {
program_id: *pid,
accounts: vec![],
data: vec![],
}
})*
}
use instruction::solitaire;
entrypoint!(solitaire);
}
}

View File

@ -0,0 +1,21 @@
[package]
name = "rocksalt"
version = "0.1.0"
description = "Created with Rocksalt"
edition = "2018"
[lib]
proc-macro = true
name = "rocksalt"
[features]
no-entrypoint = []
default = []
[dependencies]
byteorder = "1.4.3"
proc-macro2 = "1.0"
quote = "1.0"
sha3 = "0.9.1"
solana-program = "*"
syn = "1.0"

View File

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

View File

@ -0,0 +1,177 @@
#![allow(warnings)]
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use syn::{
parse_macro_input,
parse_quote,
spanned::Spanned,
Data,
DeriveInput,
Fields,
GenericParam,
Generics,
Index,
};
/// Generate a FromAccounts implementation for a product of accounts. Each field is constructed by
/// a call to the Verify::verify instance of its type.
#[proc_macro_derive(FromAccounts)]
pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let from_method = generate_fields(&name, &input.data);
let persist_method = generate_persist(&name, &input.data);
let expanded = quote! {
/// Macro generated implementation of FromAccounts by Solitaire.
impl<'a, 'b: 'a, 'c> solitaire::FromAccounts<'a, 'b, 'c> for #name<'b> {
fn from<T>(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, AccountInfo<'b>>, data: &'a T) -> solitaire::Result<(Self, Vec<solana_program::pubkey::Pubkey>)> {
#from_method
}
}
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for #name<'b> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> solitaire::Result<Self> where Self: Sized {
let v: #name = FromAccounts::from(ctx.this, ctx.iter, ctx.data).map(|v| v.0)?;
// Verify the instruction constraints
solitaire::InstructionContext::verify(&v)?;
// Append instruction level dependencies
ctx.deps.append(&mut solitaire::InstructionContext::deps(&v));
Ok(v)
}
}
/// Macro generated implementation of Persist by Solitaire.
impl<'a> solitaire::Persist for #name<'a> {
fn persist(self) {
use borsh::BorshSerialize;
//self.guardian_set.serialize(
// &mut *self.guardian_set.0.data.borrow_mut()
//);
}
}
};
// Hand the output tokens back to the compiler
TokenStream::from(expanded)
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression that parses an account info field
// from the Solana accounts list. This relies on Verify::verify to do most of
// the work.
let recurse = fields.named.iter().map(|f| {
// Field name, to assign to.
let name = &f.ident;
let ty = &f.ty;
quote! {
let #name: #ty = solitaire::Peel::peel(&mut solitaire::Context::new(
pid,
iter,
data,
&mut deps,
))?;
}
});
let names = fields.named.iter().map(|f| {
let name = &f.ident;
quote!(#name)
});
// Write out our iterator and return the filled structure.
quote! {
use solana_program::account_info::next_account_info;
let mut deps = Vec::new();
#(#recurse;)*
Ok((#name { #(#names,)* }, deps))
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_persist(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression that parses an account info field
// from the Solana accounts list. This relies on Verify::verify to do most of
// the work.
let recurse = fields.named.iter().map(|f| {
// Field name, to assign to.
let name = &f.ident;
let ty = &f.ty;
quote! {
let #name: #ty = Peel::peel(&mut solitaire::Context::new(
pid,
iter,
data,
&mut deps,
))?;
}
});
let names = fields.named.iter().map(|f| {
let name = &f.ident;
quote!(#name)
});
// Write out our iterator and return the filled structure.
quote! {
use solana_program::account_info::next_account_info;
let mut deps = Vec::new();
#(#recurse;)*
Ok((#name { #(#names,)* }, deps))
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}