Initial Solitaire Base
Change-Id: I6789364464ef03ada87b7d446abb43df033d133d
This commit is contained in:
parent
2c8e25ec12
commit
652f4ea3b1
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*",
|
||||
"client"
|
||||
]
|
||||
|
|
|
@ -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"
|
|
@ -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::*;
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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],
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 = "*"
|
|
@ -0,0 +1,3 @@
|
|||
mod initialize;
|
||||
|
||||
pub use initialize::*;
|
|
@ -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(())
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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 = "*"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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!(),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue