Solitaire client implementation
Change-Id: I4d1f37082537cd24a4859802652a177400f6a205
This commit is contained in:
parent
2b473f1f12
commit
c700e8847b
|
@ -12,7 +12,7 @@ let
|
||||||
owner = "tilt-dev";
|
owner = "tilt-dev";
|
||||||
repo = oldAttrs.pname;
|
repo = oldAttrs.pname;
|
||||||
rev = "v0.18.4";
|
rev = "v0.18.4";
|
||||||
sha256 = "sha256-xqBgbsrVSAOqtfHbEF07i6XIdiBXMYoR7H4Kc4xK7x0=";
|
sha256 = null;
|
||||||
};
|
};
|
||||||
buildFlagsArray = [ "-ldflags=-X main.version=0.18.4" ];
|
buildFlagsArray = [ "-ldflags=-X main.version=0.18.4" ];
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"programs/*",
|
"programs/*",
|
||||||
|
"client/",
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,9 +5,12 @@ authors = ["Stan Drozd <stan@nexantic.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-client = {git = "https://github.com/drozdziak1/anchor", branch = "anchor-debug-feature", features = ["anchor-debug"]}
|
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
anchor-bridge = {path = "../programs/anchor-bridge", features = ["no-idl", "no-entrypoint", "anchor-debug"]}
|
bridge = {path = "../programs/bridge", features = ["no-idl", "no-entrypoint"]}
|
||||||
clap = "3.0.0-beta.2"
|
clap = "3.0.0-beta.2"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
|
solana-client = "1.7.0"
|
||||||
|
solana-program = "*"
|
||||||
|
solana-sdk = "1.7.0"
|
||||||
|
solitaire = { path = "../programs/solitaire" }
|
||||||
|
|
|
@ -1,30 +1,37 @@
|
||||||
use anchor_bridge::{
|
use bridge::{
|
||||||
accounts::Initialize,
|
api,
|
||||||
instruction::state::New,
|
client,
|
||||||
BridgeConfig,
|
instruction,
|
||||||
InitializeData,
|
|
||||||
MAX_LEN_GUARDIAN_KEYS,
|
|
||||||
};
|
};
|
||||||
use anchor_client::{
|
use clap::Clap;
|
||||||
solana_sdk::{
|
use solana_client::{
|
||||||
account_info::AccountInfo,
|
rpc_client::RpcClient,
|
||||||
commitment_config::CommitmentConfig,
|
rpc_config::RpcSendTransactionConfig,
|
||||||
instruction::AccountMeta,
|
};
|
||||||
|
use solana_program::{
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{read_keypair_file, Keypair, Signer},
|
|
||||||
system_instruction,
|
system_instruction,
|
||||||
system_program,
|
system_program,
|
||||||
sysvar,
|
sysvar,
|
||||||
},
|
|
||||||
Client,
|
|
||||||
Cluster,
|
|
||||||
EventContext,
|
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use solana_sdk::{
|
||||||
use clap::Clap;
|
commitment_config::CommitmentConfig,
|
||||||
use rand::rngs::OsRng;
|
signature::{
|
||||||
|
read_keypair_file,
|
||||||
|
Signer as SolSigner,
|
||||||
|
},
|
||||||
|
transaction::Transaction,
|
||||||
|
};
|
||||||
|
use solitaire::{
|
||||||
|
AccEntry,
|
||||||
|
Signer,
|
||||||
|
ToInstruction,
|
||||||
|
};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::{
|
||||||
|
error,
|
||||||
|
mem::size_of,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clap)]
|
#[derive(Clap)]
|
||||||
pub struct Opts {
|
pub struct Opts {
|
||||||
|
@ -32,65 +39,95 @@ pub struct Opts {
|
||||||
bridge_address: Pubkey,
|
bridge_address: Pubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
pub type ErrBox = Box<dyn error::Error>;
|
||||||
|
|
||||||
|
fn main() -> Result<(), ErrBox> {
|
||||||
let opts = Opts::parse();
|
let opts = Opts::parse();
|
||||||
|
|
||||||
// Wallet and cluster params.
|
|
||||||
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
|
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
|
||||||
.expect("Example requires a keypair file");
|
.expect("Example requires a keypair file");
|
||||||
let url = Cluster::Custom(
|
|
||||||
"http://localhost:8899".to_owned(),
|
|
||||||
"ws://localhost:8900".to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let client = Client::new_with_options(url, payer, CommitmentConfig::processed());
|
// Keypair is not Clone
|
||||||
|
let payer_for_tx = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
|
||||||
|
.expect("Example requires a keypair file");
|
||||||
|
let url = "http://localhost:8899".to_owned();
|
||||||
|
|
||||||
|
let client = RpcClient::new(url);
|
||||||
|
|
||||||
|
let program_id = opts.bridge_address;
|
||||||
|
|
||||||
|
use AccEntry::*;
|
||||||
|
let init = api::InitializeAccounts {
|
||||||
|
bridge: Derived(program_id.clone()),
|
||||||
|
guardian_set: Derived(program_id.clone()),
|
||||||
|
payer: Signer(payer),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ix_data = vec![];
|
||||||
|
|
||||||
|
let (ix, signers) = init.to_ix(program_id, ix_data.as_slice())?;
|
||||||
|
let (recent_blockhash, _) = client.get_recent_blockhash()?;
|
||||||
|
|
||||||
|
let mut tx = Transaction::new_with_payer(&[ix], Some(&payer_for_tx.pubkey()));
|
||||||
|
|
||||||
|
tx.sign(&signers.iter().collect::<Vec<_>>(), recent_blockhash);
|
||||||
|
|
||||||
|
let signature = client.send_and_confirm_transaction_with_spinner_and_config(
|
||||||
|
&tx,
|
||||||
|
CommitmentConfig::processed(),
|
||||||
|
RpcSendTransactionConfig {
|
||||||
|
skip_preflight: false,
|
||||||
|
preflight_commitment: None,
|
||||||
|
encoding: None,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
println!("Signature: {}", signature);
|
||||||
|
|
||||||
initialize_bridge(&client, opts.bridge_address)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_bridge(client: &Client, bridge_address: Pubkey) -> Result<()> {
|
// fn initialize_bridge(client: &Client, bridge_address: Pubkey) -> Result<()> {
|
||||||
let program = client.program(bridge_address);
|
// let program = client.program(bridge_address);
|
||||||
|
|
||||||
let guardian_set_key = Keypair::generate(&mut OsRng);
|
// let guardian_set_key = Keypair::generate(&mut OsRng);
|
||||||
let state_key = Keypair::generate(&mut OsRng);
|
// let state_key = Keypair::generate(&mut OsRng);
|
||||||
|
|
||||||
program
|
// program
|
||||||
.state_request()
|
// .state_request()
|
||||||
.instruction(system_instruction::create_account(
|
// .instruction(system_instruction::create_account(
|
||||||
&program.payer(),
|
// &program.payer(),
|
||||||
&guardian_set_key.pubkey(),
|
// &guardian_set_key.pubkey(),
|
||||||
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
// program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
||||||
500,
|
// 500,
|
||||||
&program.id(),
|
// &program.id(),
|
||||||
))
|
// ))
|
||||||
.instruction(system_instruction::create_account(
|
// .instruction(system_instruction::create_account(
|
||||||
&program.payer(),
|
// &program.payer(),
|
||||||
&state_key.pubkey(),
|
// &state_key.pubkey(),
|
||||||
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
// program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
||||||
500,
|
// 500,
|
||||||
&program.id(),
|
// &program.id(),
|
||||||
))
|
// ))
|
||||||
.signer(&guardian_set_key)
|
// .signer(&guardian_set_key)
|
||||||
// .signer(&state_key)
|
// // .signer(&state_key)
|
||||||
.accounts(Initialize {
|
// .accounts(Initialize {
|
||||||
payer: program.payer(),
|
// payer: program.payer(),
|
||||||
guardian_set: guardian_set_key.pubkey(),
|
// guardian_set: guardian_set_key.pubkey(),
|
||||||
state: state_key.pubkey(),
|
// state: state_key.pubkey(),
|
||||||
system_program: system_program::id(),
|
// system_program: system_program::id(),
|
||||||
clock: sysvar::clock::id(),
|
// clock: sysvar::clock::id(),
|
||||||
rent: sysvar::rent::id(),
|
// rent: sysvar::rent::id(),
|
||||||
})
|
// })
|
||||||
.new(New {
|
// .new(New {
|
||||||
data: InitializeData {
|
// data: InitializeData {
|
||||||
len_guardians: 0,
|
// len_guardians: 0,
|
||||||
initial_guardian_keys: [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS],
|
// initial_guardian_keys: [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS],
|
||||||
config: BridgeConfig {
|
// config: BridgeConfig {
|
||||||
guardian_set_expiration_time: 0u32,
|
// guardian_set_expiration_time: 0u32,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
.send()?;
|
// .send()?;
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
|
@ -21,3 +21,4 @@ solitaire = { path = "../solitaire" }
|
||||||
sha3 = "0.9.1"
|
sha3 = "0.9.1"
|
||||||
solana-program = "*"
|
solana-program = "*"
|
||||||
primitive-types = { version = "0.9.0", default-features = false }
|
primitive-types = { version = "0.9.0", default-features = false }
|
||||||
|
solana-sdk = "1.7.0"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
mod governance;
|
pub mod governance;
|
||||||
mod initialize;
|
pub mod initialize;
|
||||||
mod post_message;
|
pub mod post_message;
|
||||||
mod post_vaa;
|
pub mod post_vaa;
|
||||||
mod verify_signature;
|
pub mod verify_signature;
|
||||||
|
|
||||||
pub use governance::*;
|
pub use governance::*;
|
||||||
pub use initialize::*;
|
pub use initialize::*;
|
||||||
|
|
|
@ -9,7 +9,7 @@ type GuardianSet<'a> =
|
||||||
Derive<Data<'a, GuardianSetData, { AccountState::Uninitialized }>, "GuardianSet">;
|
Derive<Data<'a, GuardianSetData, { AccountState::Uninitialized }>, "GuardianSet">;
|
||||||
type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Uninitialized }>, "Bridge">;
|
type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Uninitialized }>, "Bridge">;
|
||||||
|
|
||||||
#[derive(FromAccounts, ToAccounts)]
|
#[derive(FromAccounts, ToInstruction)]
|
||||||
pub struct Initialize<'b> {
|
pub struct Initialize<'b> {
|
||||||
pub bridge: Bridge<'b>,
|
pub bridge: Bridge<'b>,
|
||||||
pub guardian_set: GuardianSet<'b>,
|
pub guardian_set: GuardianSet<'b>,
|
||||||
|
|
|
@ -20,3 +20,4 @@ byteorder = "1.4.3"
|
||||||
rocksalt = { path = "../../rocksalt" }
|
rocksalt = { path = "../../rocksalt" }
|
||||||
sha3 = "0.9.1"
|
sha3 = "0.9.1"
|
||||||
solana-program = "*"
|
solana-program = "*"
|
||||||
|
solana-sdk = "1.7.0"
|
||||||
|
|
|
@ -16,7 +16,10 @@ use solana_program::{
|
||||||
},
|
},
|
||||||
entrypoint,
|
entrypoint,
|
||||||
entrypoint::ProgramResult,
|
entrypoint::ProgramResult,
|
||||||
instruction::AccountMeta,
|
instruction::{
|
||||||
|
AccountMeta,
|
||||||
|
Instruction,
|
||||||
|
},
|
||||||
program::invoke_signed,
|
program::invoke_signed,
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
program_pack::Pack,
|
program_pack::Pack,
|
||||||
|
@ -29,6 +32,10 @@ use solana_program::{
|
||||||
SysvarId,
|
SysvarId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use solana_sdk::signature::{
|
||||||
|
Keypair,
|
||||||
|
Signer as SolSigner,
|
||||||
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{
|
io::{
|
||||||
|
@ -74,6 +81,8 @@ pub use crate::{
|
||||||
types::*,
|
types::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StdResult<T, E> = std::result::Result<T, E>;
|
||||||
|
|
||||||
pub struct ExecutionContext<'a, 'b: 'a> {
|
pub struct ExecutionContext<'a, 'b: 'a> {
|
||||||
/// A reference to the program_id of the current program.
|
/// A reference to the program_id of the current program.
|
||||||
pub program_id: &'a Pubkey,
|
pub program_id: &'a Pubkey,
|
||||||
|
@ -98,36 +107,88 @@ impl CreationLamports {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sum type for clearly specifying the accounts required on client side.
|
||||||
|
pub enum AccEntry {
|
||||||
|
/// Least privileged account.
|
||||||
|
Unprivileged(Pubkey),
|
||||||
|
|
||||||
|
/// Accounts that need to sign a Solana call
|
||||||
|
Signer(Keypair),
|
||||||
|
SignerRO(Keypair),
|
||||||
|
|
||||||
|
/// Program addresses for privileged/unprivileged cross calls
|
||||||
|
CPIProgram(Pubkey),
|
||||||
|
CPIProgramSigner(Keypair),
|
||||||
|
|
||||||
|
/// Key decided by Wrap implementation
|
||||||
|
Sysvar,
|
||||||
|
Derived(Pubkey),
|
||||||
|
DerivedRO(Pubkey),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Types implementing Wrap are those that can be turned into a
|
||||||
|
/// partial account vector tha
|
||||||
|
/// payload.
|
||||||
pub trait Wrap {
|
pub trait Wrap {
|
||||||
fn wrap(&self) -> Vec<AccountMeta>;
|
fn wrap(_: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox>;
|
||||||
|
|
||||||
|
/// If the implementor wants to sign using other AccEntry
|
||||||
|
/// variants, they should override this.
|
||||||
|
fn keypair(a: AccEntry) -> Option<Keypair> {
|
||||||
|
use AccEntry::*;
|
||||||
|
match a {
|
||||||
|
Signer(pair) => Some(pair),
|
||||||
|
SignerRO(pair) => Some(pair),
|
||||||
|
_other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Wrap for T
|
impl<'a, 'b: 'a, T> Wrap for Signer<T>
|
||||||
where
|
where
|
||||||
T: ToAccounts,
|
T: Keyed<'a, 'b>,
|
||||||
{
|
{
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
|
||||||
self.to()
|
use AccEntry::*;
|
||||||
|
match a {
|
||||||
|
Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]),
|
||||||
|
SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]),
|
||||||
|
other => Err(format!(
|
||||||
|
"{} must be passed as Signer or SignerRO",
|
||||||
|
std::any::type_name::<Self>()
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Wrap for Signer<T> {
|
impl<'a, 'b: 'a, T, const Seed: &'static str> Wrap for Derive<T, Seed> {
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
|
||||||
todo!()
|
match a {
|
||||||
}
|
AccEntry::Derived(program_id) => {
|
||||||
}
|
let (k, extra_seed) = Pubkey::find_program_address(&[Seed.as_bytes()], &program_id);
|
||||||
|
|
||||||
impl<T, const Seed: &'static str> Wrap for Derive<T, Seed> {
|
Ok(vec![AccountMeta::new(k, false)])
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
}
|
||||||
todo!()
|
AccEntry::DerivedRO(program_id) => {
|
||||||
|
let (k, extra_seed) = Pubkey::find_program_address(&[Seed.as_bytes()], &program_id);
|
||||||
|
|
||||||
|
Ok(vec![AccountMeta::new_readonly(k, false)])
|
||||||
|
}
|
||||||
|
other => Err(format!(
|
||||||
|
"{} must be passed as Derived or DerivedRO",
|
||||||
|
std::any::type_name::<Self>()
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: BorshSerialize + Owned + Default, const IsInitialized: AccountState> Wrap
|
impl<'a, T: BorshSerialize + Owned + Default, const IsInitialized: AccountState> Wrap
|
||||||
for Data<'a, T, IsInitialized>
|
for Data<'a, T, IsInitialized>
|
||||||
{
|
{
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
|
||||||
todo!()
|
todo!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +202,9 @@ pub trait InstructionContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToAccounts {
|
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
|
||||||
fn to(&self) -> Vec<AccountMeta>;
|
pub trait ToInstruction {
|
||||||
|
fn to_ix(self, program_id: Pubkey, ix_data: &[u8]) -> StdResult<(Instruction, Vec<Keypair>), ErrBox>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait definition that describes types that can be constructed from a list of solana account
|
/// Trait definition that describes types that can be constructed from a list of solana account
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::ops::{
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! solitaire {
|
macro_rules! solitaire {
|
||||||
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
|
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
|
||||||
mod instruction {
|
pub mod instruction {
|
||||||
use super::*;
|
use super::*;
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
@ -60,7 +60,7 @@ macro_rules! solitaire {
|
||||||
|
|
||||||
/// This module contains a 1-1 mapping for each function to an enum variant. The variants
|
/// This module contains a 1-1 mapping for each function to an enum variant. The variants
|
||||||
/// can be matched to the Instruction found above.
|
/// can be matched to the Instruction found above.
|
||||||
mod client {
|
pub mod client {
|
||||||
use super::*;
|
use super::*;
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
mod to_accounts;
|
mod to_instruction;
|
||||||
|
|
||||||
use to_accounts::*;
|
use to_instruction::*;
|
||||||
|
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
account_info::AccountInfo,
|
account_info::AccountInfo,
|
||||||
|
@ -12,7 +12,7 @@ use solana_program::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{
|
use quote::{
|
||||||
quote,
|
quote,
|
||||||
quote_spanned,
|
quote_spanned,
|
||||||
|
@ -30,21 +30,41 @@ use syn::{
|
||||||
Index,
|
Index,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[proc_macro_derive(ToAccounts)]
|
#[proc_macro_derive(ToInstruction)]
|
||||||
pub fn derive_to_accounts(input: TokenStream) -> TokenStream {
|
pub fn derive_to_instruction(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let name = input.ident;
|
let name = input.ident;
|
||||||
let to_method_body = generate_to_method(&name, &input.data);
|
|
||||||
|
|
||||||
let expanded = quote! {
|
// Type params of the instruction context account
|
||||||
/// Macro-generated implementation of ToAccounts by Solitaire.
|
let type_params: Vec<GenericParam> = input
|
||||||
impl<'a> solitaire::ToAccounts for #name<'a> {
|
.generics
|
||||||
fn to(&self) -> Vec<solana_program::instruction::AccountMeta> {
|
.type_params()
|
||||||
#to_method_body
|
.map(|v| GenericParam::Type(v.clone()))
|
||||||
}
|
.collect();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Generics lifetimes of the peel type
|
||||||
|
let mut peel_g = input.generics.clone();
|
||||||
|
peel_g.params = parse_quote!('a, 'b: 'a, 'c);
|
||||||
|
let (_, peel_type_g, _) = peel_g.split_for_impl();
|
||||||
|
|
||||||
|
// Params of the instruction context
|
||||||
|
let mut type_generics = input.generics.clone();
|
||||||
|
type_generics.params = parse_quote!('b);
|
||||||
|
for x in &type_params {
|
||||||
|
type_generics.params.push(x.clone());
|
||||||
|
}
|
||||||
|
let (type_impl_g, type_g, _) = type_generics.split_for_impl();
|
||||||
|
|
||||||
|
// Combined lifetimes of peel and the instruction context
|
||||||
|
let mut combined_generics = Generics::default();
|
||||||
|
combined_generics.params = peel_g.params.clone();
|
||||||
|
for x in &type_params {
|
||||||
|
combined_generics.params.push(x.clone());
|
||||||
|
}
|
||||||
|
let (combined_impl_g, _, _) = combined_generics.split_for_impl();
|
||||||
|
|
||||||
|
let from_method = generate_fields(&name, &input.data);
|
||||||
|
let expanded = generate_to_instruction(&name, &combined_impl_g, &input.data);
|
||||||
TokenStream::from(expanded)
|
TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
//! Derive macro logic for ToAccounts
|
|
||||||
|
|
||||||
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,
|
|
||||||
DataStruct,
|
|
||||||
DeriveInput,
|
|
||||||
Fields,
|
|
||||||
GenericParam,
|
|
||||||
Generics,
|
|
||||||
Index,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn generate_to_method(name: &syn::Ident, data: &Data) -> TokenStream2 {
|
|
||||||
match *data {
|
|
||||||
Data::Struct(DataStruct {
|
|
||||||
fields: Fields::Named(ref fields),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let expanded_fields = fields.named.iter().map(|field| {
|
|
||||||
let name = &field.ident;
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
v.append(&mut solitaire::Wrap::wrap(&self.#name))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
let mut v = Vec::new();
|
|
||||||
#(#expanded_fields;)*
|
|
||||||
v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
//! Derive macro logic for ToInstruction
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{
|
||||||
|
Span,
|
||||||
|
TokenStream as TokenStream2,
|
||||||
|
};
|
||||||
|
use quote::{
|
||||||
|
quote,
|
||||||
|
quote_spanned,
|
||||||
|
};
|
||||||
|
use syn::{
|
||||||
|
parse_macro_input,
|
||||||
|
parse_quote,
|
||||||
|
spanned::Spanned,
|
||||||
|
Data,
|
||||||
|
DataStruct,
|
||||||
|
DeriveInput,
|
||||||
|
Fields,
|
||||||
|
GenericParam,
|
||||||
|
Generics,
|
||||||
|
Index,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_to_instruction(name: &syn::Ident, impl_generics: &syn::ImplGenerics, data: &Data) -> TokenStream2 {
|
||||||
|
match *data {
|
||||||
|
Data::Struct(DataStruct {
|
||||||
|
fields: Fields::Named(ref fields),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let expanded_appends = fields.named.iter().map(|field| {
|
||||||
|
let name = &field.ident;
|
||||||
|
let ty = &field.ty;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
account_metas.append(&mut <#ty as solitaire::Wrap>::wrap(&self.#name)?);
|
||||||
|
if let Some(pair) = <#ty as solitaire::Wrap>::keypair(self.#name) {
|
||||||
|
signers.push(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let client_struct_name =
|
||||||
|
syn::Ident::new(&format!("{}Accounts", name.to_string()), Span::call_site());
|
||||||
|
|
||||||
|
let client_struct_decl = generate_clientside_struct(&name, &client_struct_name, &data);
|
||||||
|
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
/// Solitaire-generated client-side #name representation
|
||||||
|
#client_struct_decl
|
||||||
|
|
||||||
|
/// Solitaire-generatied ToInstruction implementation
|
||||||
|
impl #impl_generics solitaire::ToInstruction for #client_struct_name {
|
||||||
|
fn to_ix(
|
||||||
|
self,
|
||||||
|
program_id: solana_program::pubkey::Pubkey,
|
||||||
|
ix_data: &[u8]) -> std::result::Result<
|
||||||
|
(solana_program::instruction::Instruction, Vec<solana_sdk::signer::keypair::Keypair>),
|
||||||
|
solitaire::ErrBox
|
||||||
|
> {
|
||||||
|
use solana_program::{pubkey::Pubkey, instruction::Instruction};
|
||||||
|
let mut account_metas = Vec::new();
|
||||||
|
let mut signers = Vec::new();
|
||||||
|
|
||||||
|
#(#expanded_appends;)*
|
||||||
|
|
||||||
|
Ok((solana_program::instruction::Instruction::new_with_bytes(program_id,
|
||||||
|
ix_data,
|
||||||
|
account_metas), signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_clientside_struct(name: &syn::Ident, client_struct_name: &syn::Ident, data: &Data) -> TokenStream2 {
|
||||||
|
match *data {
|
||||||
|
Data::Struct(DataStruct {
|
||||||
|
fields: Fields::Named(ref fields),
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let expanded_fields = fields.named.iter().map(|field| {
|
||||||
|
let field_name = &field.ident;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#field_name: solitaire::AccEntry
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub struct #client_struct_name {
|
||||||
|
#(pub #expanded_fields,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ byteorder = "1.3.4"
|
||||||
zerocopy = "0.3.0"
|
zerocopy = "0.3.0"
|
||||||
sha3 = "0.9.1"
|
sha3 = "0.9.1"
|
||||||
primitive-types = { version = "0.7.2", default-features = false }
|
primitive-types = { version = "0.7.2", default-features = false }
|
||||||
|
solana-sdk = "1.7.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = { version = "0.7.0" }
|
rand = { version = "0.7.0" }
|
||||||
|
|
Loading…
Reference in New Issue