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]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"programs/*",
|
"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