Solitaire client implementation

Change-Id: I4d1f37082537cd24a4859802652a177400f6a205
This commit is contained in:
Stan Drozd 2021-06-04 13:02:35 +02:00
parent 2b473f1f12
commit c700e8847b
15 changed files with 3718 additions and 271 deletions

View File

@ -12,7 +12,7 @@ let
owner = "tilt-dev";
repo = oldAttrs.pname;
rev = "v0.18.4";
sha256 = "sha256-xqBgbsrVSAOqtfHbEF07i6XIdiBXMYoR7H4Kc4xK7x0=";
sha256 = null;
};
buildFlagsArray = [ "-ldflags=-X main.version=0.18.4" ];
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,9 +5,12 @@ authors = ["Stan Drozd <stan@nexantic.com>"]
edition = "2018"
[dependencies]
anchor-client = {git = "https://github.com/drozdziak1/anchor", branch = "anchor-debug-feature", features = ["anchor-debug"]}
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"
rand = "0.7.3"
shellexpand = "2.1.0"
solana-client = "1.7.0"
solana-program = "*"
solana-sdk = "1.7.0"
solitaire = { path = "../programs/solitaire" }

View File

@ -1,30 +1,37 @@
use anchor_bridge::{
accounts::Initialize,
instruction::state::New,
BridgeConfig,
InitializeData,
MAX_LEN_GUARDIAN_KEYS,
use bridge::{
api,
client,
instruction,
};
use anchor_client::{
solana_sdk::{
account_info::AccountInfo,
commitment_config::CommitmentConfig,
instruction::AccountMeta,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, Signer},
system_instruction,
system_program,
sysvar,
},
Client,
Cluster,
EventContext,
};
use anyhow::Result;
use clap::Clap;
use rand::rngs::OsRng;
use solana_client::{
rpc_client::RpcClient,
rpc_config::RpcSendTransactionConfig,
};
use solana_program::{
pubkey::Pubkey,
system_instruction,
system_program,
sysvar,
};
use solana_sdk::{
commitment_config::CommitmentConfig,
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)]
pub struct Opts {
@ -32,65 +39,95 @@ pub struct Opts {
bridge_address: Pubkey,
}
fn main() -> Result<()> {
pub type ErrBox = Box<dyn error::Error>;
fn main() -> Result<(), ErrBox> {
let opts = Opts::parse();
// Wallet and cluster params.
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
.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();
initialize_bridge(&client, opts.bridge_address)?;
Ok(())
}
let client = RpcClient::new(url);
fn initialize_bridge(client: &Client, bridge_address: Pubkey) -> Result<()> {
let program = client.program(bridge_address);
let program_id = opts.bridge_address;
let guardian_set_key = Keypair::generate(&mut OsRng);
let state_key = Keypair::generate(&mut OsRng);
use AccEntry::*;
let init = api::InitializeAccounts {
bridge: Derived(program_id.clone()),
guardian_set: Derived(program_id.clone()),
payer: Signer(payer),
};
program
.state_request()
.instruction(system_instruction::create_account(
&program.payer(),
&guardian_set_key.pubkey(),
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
500,
&program.id(),
))
.instruction(system_instruction::create_account(
&program.payer(),
&state_key.pubkey(),
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
500,
&program.id(),
))
.signer(&guardian_set_key)
// .signer(&state_key)
.accounts(Initialize {
payer: program.payer(),
guardian_set: guardian_set_key.pubkey(),
state: state_key.pubkey(),
system_program: system_program::id(),
clock: sysvar::clock::id(),
rent: sysvar::rent::id(),
})
.new(New {
data: InitializeData {
len_guardians: 0,
initial_guardian_keys: [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS],
config: BridgeConfig {
guardian_set_expiration_time: 0u32,
},
},
})
.send()?;
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);
Ok(())
}
// fn initialize_bridge(client: &Client, bridge_address: Pubkey) -> Result<()> {
// let program = client.program(bridge_address);
// let guardian_set_key = Keypair::generate(&mut OsRng);
// let state_key = Keypair::generate(&mut OsRng);
// program
// .state_request()
// .instruction(system_instruction::create_account(
// &program.payer(),
// &guardian_set_key.pubkey(),
// program.rpc().get_minimum_balance_for_rent_exemption(500)?,
// 500,
// &program.id(),
// ))
// .instruction(system_instruction::create_account(
// &program.payer(),
// &state_key.pubkey(),
// program.rpc().get_minimum_balance_for_rent_exemption(500)?,
// 500,
// &program.id(),
// ))
// .signer(&guardian_set_key)
// // .signer(&state_key)
// .accounts(Initialize {
// payer: program.payer(),
// guardian_set: guardian_set_key.pubkey(),
// state: state_key.pubkey(),
// system_program: system_program::id(),
// clock: sysvar::clock::id(),
// rent: sysvar::rent::id(),
// })
// .new(New {
// data: InitializeData {
// len_guardians: 0,
// initial_guardian_keys: [[0u8; 20]; MAX_LEN_GUARDIAN_KEYS],
// config: BridgeConfig {
// guardian_set_expiration_time: 0u32,
// },
// },
// })
// .send()?;
// Ok(())
// }

View File

@ -20,4 +20,5 @@ byteorder = "1.4.3"
solitaire = { path = "../solitaire" }
sha3 = "0.9.1"
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"

View File

@ -1,8 +1,8 @@
mod governance;
mod initialize;
mod post_message;
mod post_vaa;
mod verify_signature;
pub mod governance;
pub mod initialize;
pub mod post_message;
pub mod post_vaa;
pub mod verify_signature;
pub use governance::*;
pub use initialize::*;

View File

@ -9,7 +9,7 @@ type GuardianSet<'a> =
Derive<Data<'a, GuardianSetData, { AccountState::Uninitialized }>, "GuardianSet">;
type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Uninitialized }>, "Bridge">;
#[derive(FromAccounts, ToAccounts)]
#[derive(FromAccounts, ToInstruction)]
pub struct Initialize<'b> {
pub bridge: Bridge<'b>,
pub guardian_set: GuardianSet<'b>,

View File

@ -20,3 +20,4 @@ byteorder = "1.4.3"
rocksalt = { path = "../../rocksalt" }
sha3 = "0.9.1"
solana-program = "*"
solana-sdk = "1.7.0"

View File

@ -16,7 +16,10 @@ use solana_program::{
},
entrypoint,
entrypoint::ProgramResult,
instruction::AccountMeta,
instruction::{
AccountMeta,
Instruction,
},
program::invoke_signed,
program_error::ProgramError,
program_pack::Pack,
@ -29,6 +32,10 @@ use solana_program::{
SysvarId,
},
};
use solana_sdk::signature::{
Keypair,
Signer as SolSigner,
};
use std::{
io::{
@ -74,6 +81,8 @@ pub use crate::{
types::*,
};
type StdResult<T, E> = std::result::Result<T, E>;
pub struct ExecutionContext<'a, 'b: 'a> {
/// A reference to the program_id of the current program.
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 {
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
T: ToAccounts,
T: Keyed<'a, 'b>,
{
fn wrap(&self) -> Vec<AccountMeta> {
self.to()
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
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> {
fn wrap(&self) -> Vec<AccountMeta> {
todo!()
}
}
impl<'a, 'b: 'a, T, const Seed: &'static str> Wrap for Derive<T, Seed> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
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> {
fn wrap(&self) -> Vec<AccountMeta> {
todo!()
Ok(vec![AccountMeta::new(k, false)])
}
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
for Data<'a, T, IsInitialized>
{
fn wrap(&self) -> Vec<AccountMeta> {
todo!()
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
todo!();
}
}
@ -141,8 +202,9 @@ pub trait InstructionContext<'a> {
}
}
pub trait ToAccounts {
fn to(&self) -> Vec<AccountMeta>;
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
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

View File

@ -13,7 +13,7 @@ use std::ops::{
#[macro_export]
macro_rules! solitaire {
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
mod instruction {
pub mod instruction {
use super::*;
use borsh::{BorshDeserialize, BorshSerialize};
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
/// can be matched to the Instruction found above.
mod client {
pub mod client {
use super::*;
use borsh::BorshSerialize;
use solana_program::{instruction::Instruction, pubkey::Pubkey};

View File

@ -1,8 +1,8 @@
#![allow(warnings)]
mod to_accounts;
mod to_instruction;
use to_accounts::*;
use to_instruction::*;
use solana_program::{
account_info::AccountInfo,
@ -12,7 +12,7 @@ use solana_program::{
};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{
quote,
quote_spanned,
@ -30,21 +30,41 @@ use syn::{
Index,
};
#[proc_macro_derive(ToAccounts)]
pub fn derive_to_accounts(input: TokenStream) -> TokenStream {
#[proc_macro_derive(ToInstruction)]
pub fn derive_to_instruction(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let to_method_body = generate_to_method(&name, &input.data);
let expanded = quote! {
/// Macro-generated implementation of ToAccounts by Solitaire.
impl<'a> solitaire::ToAccounts for #name<'a> {
fn to(&self) -> Vec<solana_program::instruction::AccountMeta> {
#to_method_body
}
// Type params of the instruction context account
let type_params: Vec<GenericParam> = input
.generics
.type_params()
.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)
}

View File

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

View File

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

View File

@ -24,6 +24,7 @@ byteorder = "1.3.4"
zerocopy = "0.3.0"
sha3 = "0.9.1"
primitive-types = { version = "0.7.2", default-features = false }
solana-sdk = "1.7.0"
[dev-dependencies]
rand = { version = "0.7.0" }