├── macros ├── .gitignore ├── Cargo.toml └── src │ ├── lib.rs │ ├── meta.rs │ ├── interface.rs │ ├── client.rs │ └── program.rs ├── .gitignore ├── test └── build-wasm ├── src ├── wallet │ ├── mod.rs │ └── foreign │ │ ├── mod.rs │ │ ├── native.rs │ │ └── wasm.rs ├── result.rs ├── transport │ ├── api │ │ ├── mod.rs │ │ └── components.rs │ ├── config.rs │ ├── mod.rs │ ├── loaders.rs │ ├── lookup.rs │ └── reflector.rs ├── container │ ├── collection │ │ ├── mod.rs │ │ ├── proxy.rs │ │ └── reference.rs │ ├── structure.rs │ ├── data.rs │ ├── interfaces.rs │ ├── string.rs │ └── serialized.rs ├── client.rs ├── identity │ ├── mod.rs │ ├── tests.rs │ └── client.rs ├── rent.rs ├── instruction.rs ├── emulator │ ├── mockdata.rs │ ├── interface.rs │ ├── rpc.rs │ ├── client.rs │ ├── simulator.rs │ ├── stubs.rs │ └── server.rs ├── store │ ├── mod.rs │ ├── memorystore.rs │ └── filestore.rs ├── pubkey.rs ├── sequencer.rs ├── wasm │ └── mod.rs ├── payload.rs ├── time.rs ├── hash.rs ├── address.rs ├── prelude.rs ├── date.rs ├── lib.rs ├── cache.rs ├── utils.rs ├── realloc.rs ├── user.rs ├── program.rs ├── tokens.rs └── solana.rs ├── Cargo.toml └── README.md /macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | /Cargo.lock 4 | /analyzer-target 5 | -------------------------------------------------------------------------------- /test/build-wasm: -------------------------------------------------------------------------------- 1 | wasm-pack build --target web --out-name kaizen --out-dir test/kaizen -------------------------------------------------------------------------------- /src/wallet/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Kaizen wallet data structures 3 | //! 4 | 5 | pub mod foreign; 6 | -------------------------------------------------------------------------------- /src/result.rs: -------------------------------------------------------------------------------- 1 | //! [`Result`] type used by the Kaizen framework (client-side) 2 | 3 | pub type Result = std::result::Result; 4 | -------------------------------------------------------------------------------- /src/transport/api/mod.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | if #[cfg(not(target_os = "solana"))] { 5 | mod components; 6 | pub use components::*; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/container/collection/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Account collections 3 | //! 4 | 5 | mod meta; 6 | pub use meta::*; 7 | mod account; 8 | pub use account::*; 9 | mod reference; 10 | pub use reference::*; 11 | mod pubkey; 12 | pub use pubkey::*; 13 | mod proxy; 14 | pub use proxy::*; 15 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Traits use by kaizen macros to define client-side program interfaces. 3 | //! 4 | 5 | use crate::builder::InstructionBuilder; 6 | use crate::context::HandlerFn; 7 | use std::sync::Arc; 8 | 9 | pub trait Client { 10 | fn handler_id(handler_fn: HandlerFn) -> usize; 11 | fn execution_context_for(handler: HandlerFn) -> Arc; 12 | } 13 | -------------------------------------------------------------------------------- /src/identity/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! User Identity proxy account interface. 3 | //! 4 | //! User Identity allows: 5 | //! - Binding multiple user wallets to the same user account 6 | //! - Track various user properties via a single identity account 7 | //! 8 | 9 | pub mod program; 10 | 11 | #[cfg(not(target_os = "solana"))] 12 | pub mod client; 13 | 14 | #[cfg(not(target_arch = "wasm32"))] 15 | pub mod tests; 16 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kaizen-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | # workflow-macro-tools = { path = "../../workflow-rs/macro-tools" } 11 | workflow-macro-tools = {version = "0.3.12"} 12 | syn = {version="1.0.91",features=["full","fold","extra-traits"]} 13 | quote = "1.0.8" 14 | proc-macro2 = { version = "1.0.37" } 15 | parse-variants = "0.1" 16 | -------------------------------------------------------------------------------- /src/rent.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Rent collection utilities. 3 | //! 4 | 5 | use solana_program::account_info::AccountInfo; 6 | 7 | #[derive(Debug, Copy, Clone)] 8 | pub enum RentCollector<'info, 'refs> { 9 | Program, 10 | Account(&'refs AccountInfo<'info>), 11 | } 12 | 13 | impl<'info, 'refs> Default for RentCollector<'info, 'refs> { 14 | fn default() -> RentCollector<'info, 'refs> { 15 | RentCollector::Program 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/instruction.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Helper functions for program instruction generation. 3 | //! 4 | 5 | use solana_program::instruction::AccountMeta; 6 | use solana_program::pubkey::Pubkey; 7 | 8 | #[inline(always)] 9 | pub fn readonly(pubkey: Pubkey) -> AccountMeta { 10 | AccountMeta::new_readonly(pubkey, false) 11 | } 12 | 13 | #[inline(always)] 14 | pub fn writable(pubkey: Pubkey) -> AccountMeta { 15 | AccountMeta::new(pubkey, false) 16 | } 17 | -------------------------------------------------------------------------------- /src/emulator/mockdata.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Mock data (mock authority and program_id) for testing. 3 | //! 4 | use crate::pubkey::generate_random_pubkey; 5 | use solana_program::pubkey::Pubkey; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct InProcMockData { 9 | pub authority: Pubkey, 10 | pub program_id: Pubkey, 11 | } 12 | 13 | impl InProcMockData { 14 | pub fn new(authority: &Pubkey, program_id: &Pubkey) -> Self { 15 | InProcMockData { 16 | authority: *authority, 17 | program_id: *program_id, 18 | } 19 | } 20 | } 21 | 22 | impl Default for InProcMockData { 23 | fn default() -> Self { 24 | InProcMockData { 25 | authority: generate_random_pubkey(), 26 | program_id: generate_random_pubkey(), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/store/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Account stores used by the Emulator and client-side application account caching mechanisms. 3 | //! 4 | 5 | use async_trait::async_trait; 6 | use cfg_if::cfg_if; 7 | use kaizen::accounts::{AccountDataReference, AccountDescriptorList}; 8 | use kaizen::result::Result; 9 | use solana_program::pubkey::Pubkey; 10 | use std::sync::Arc; 11 | 12 | mod memorystore; 13 | pub use memorystore::MemoryStore; 14 | cfg_if! { 15 | if #[cfg(not(target_arch = "wasm32"))] { 16 | mod filestore; 17 | pub use filestore::FileStore; 18 | } 19 | } 20 | 21 | #[async_trait] 22 | pub trait Store: Sync + Send { 23 | async fn list(&self) -> Result; 24 | async fn lookup(&self, pubkey: &Pubkey) -> Result>>; 25 | async fn store(&self, reference: &Arc) -> Result<()>; 26 | async fn purge(&self, pubkey: &Pubkey) -> Result<()>; 27 | } 28 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_snake_case)] 2 | extern crate proc_macro; 3 | 4 | mod client; 5 | mod container; 6 | mod interface; 7 | mod meta; 8 | mod program; 9 | 10 | use proc_macro::TokenStream; 11 | 12 | #[proc_macro] 13 | pub fn declare_program(input: TokenStream) -> TokenStream { 14 | program::declare_program(input) 15 | } 16 | 17 | #[proc_macro] 18 | pub fn declare_handlers(input: TokenStream) -> TokenStream { 19 | interface::declare_interface(input) 20 | } 21 | 22 | #[proc_macro] 23 | pub fn declare_interface(input: TokenStream) -> TokenStream { 24 | interface::declare_interface(input) 25 | } 26 | 27 | #[proc_macro] 28 | pub fn declare_client(input: TokenStream) -> TokenStream { 29 | client::declare_client(input) 30 | } 31 | 32 | #[proc_macro_attribute] 33 | pub fn container(attr: TokenStream, item: TokenStream) -> TokenStream { 34 | container::container_attribute_handler(attr, item) 35 | } 36 | 37 | #[proc_macro_derive(Meta)] 38 | pub fn meta(input: TokenStream) -> TokenStream { 39 | meta::derive_meta(input) 40 | } 41 | -------------------------------------------------------------------------------- /src/pubkey.rs: -------------------------------------------------------------------------------- 1 | //use crate::result::Result; 2 | use serde_wasm_bindgen::from_value; 3 | use solana_program::pubkey::Pubkey; 4 | use std::str::FromStr; 5 | use wasm_bindgen::prelude::*; 6 | use workflow_wasm::abi::ref_from_abi; 7 | 8 | pub trait PubkeyExt { 9 | fn from_value(value: &JsValue) -> std::result::Result; 10 | } 11 | 12 | impl PubkeyExt for Pubkey { 13 | fn from_value(value: &JsValue) -> std::result::Result { 14 | if value.is_string() { 15 | Ok(Pubkey::from_str(&value.as_string().unwrap()) 16 | .map_err(|_| JsValue::from("Invalid pubkey"))?) 17 | } else if value.is_array() { 18 | Ok(from_value(value.into())?) 19 | } else { 20 | Ok(ref_from_abi!(Pubkey, value)?) 21 | } 22 | } 23 | } 24 | 25 | /// Generates a [`Pubkey`] filled with random bytes (used explicitly in unit tests) 26 | #[cfg(not(target_os = "solana"))] 27 | pub fn generate_random_pubkey() -> Pubkey { 28 | // Pubkey::new(&rand::random::<[u8; 32]>()) 29 | Pubkey::new_from_array(rand::random::<[u8; 32]>()) 30 | } 31 | 32 | #[cfg(target_os = "solana")] 33 | pub fn generate_random_pubkey() -> Pubkey { 34 | Pubkey::new_unique() 35 | } 36 | -------------------------------------------------------------------------------- /src/container/structure.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Segment-based Memory-mapped strongly-typed data (a single struct) 3 | //! 4 | use crate::container::segment::Segment; 5 | use crate::result::Result; 6 | use std::marker::PhantomData; 7 | use std::rc::Rc; 8 | 9 | pub struct Struct<'info, 'refs, T> { 10 | pub segment: Rc>, 11 | phantom: PhantomData, 12 | } 13 | 14 | impl<'info, 'refs, T> Struct<'info, 'refs, T> { 15 | pub fn data_len_min() -> usize { 16 | std::mem::size_of::() 17 | } 18 | 19 | pub fn try_create_from_segment( 20 | segment: Rc>, 21 | ) -> Result> { 22 | Ok(Struct { 23 | segment, 24 | phantom: PhantomData, 25 | }) 26 | } 27 | 28 | pub fn try_load_from_segment( 29 | segment: Rc>, 30 | ) -> Result> { 31 | Ok(Struct { 32 | segment, 33 | phantom: PhantomData, 34 | }) 35 | } 36 | 37 | pub fn try_as_ref(&self) -> Result<&T> 38 | where 39 | T: 'info, 40 | { 41 | self.segment.try_as_struct_ref() 42 | } 43 | 44 | pub fn try_as_mut_ref(&self) -> Result<&mut T> 45 | where 46 | T: 'info, 47 | { 48 | self.segment.try_as_struct_mut() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/container/data.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Segment-base Memory-mapped variable-type data 3 | //! 4 | 5 | use crate::container::segment::Segment; 6 | use crate::result::Result; 7 | use std::rc::Rc; 8 | 9 | #[derive(Debug)] 10 | pub struct Data<'info, 'refs> { 11 | pub segment: Rc>, 12 | } 13 | 14 | impl<'info, 'refs> Data<'info, 'refs> { 15 | pub fn data_len_min() -> usize { 16 | 0 17 | } 18 | 19 | pub fn try_create_from_segment( 20 | segment: Rc>, 21 | ) -> Result> { 22 | Ok(Data { segment }) 23 | } 24 | 25 | pub fn try_load_from_segment(segment: Rc>) -> Result> { 26 | Ok(Data { segment }) 27 | } 28 | 29 | pub fn as_slice(&self) -> &[T] 30 | where 31 | T: 'info, 32 | { 33 | self.segment.as_slice() 34 | } 35 | 36 | pub fn as_slice_mut(&self) -> &mut [T] 37 | where 38 | T: 'info, 39 | { 40 | self.segment.as_slice_mut() 41 | } 42 | } 43 | 44 | impl<'info, 'refs> AsRef<[u8]> for Data<'info, 'refs> { 45 | fn as_ref(&self) -> &[u8] { 46 | self.segment.as_ref_u8() 47 | } 48 | } 49 | 50 | impl<'info, 'refs> AsMut<[u8]> for Data<'info, 'refs> { 51 | fn as_mut(&mut self) -> &mut [u8] { 52 | (*self.segment).as_ref_mut_u8() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/wallet/foreign/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! User (foreign) wallet interfaces (native & web3 adaptor-based interfaces). 3 | //! 4 | 5 | use async_trait::async_trait; 6 | use cfg_if::cfg_if; 7 | use downcast::{downcast_sync, AnySync}; 8 | use kaizen::result::Result; 9 | use solana_program::pubkey::Pubkey; 10 | 11 | cfg_if! { 12 | if #[cfg(target_arch = "wasm32")] { 13 | pub mod wasm; 14 | pub use wasm::*; 15 | } else if #[cfg(not(target_arch = "wasm32"))] { 16 | pub mod native; 17 | pub use native::*; 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct Adapter { 23 | pub name: String, 24 | pub icon: String, 25 | pub index: usize, 26 | pub detected: bool, 27 | } 28 | 29 | #[async_trait(?Send)] 30 | pub trait WalletInterface: AnySync { 31 | fn is_connected(&self) -> bool; 32 | fn pubkey(&self) -> Result; 33 | async fn get_adapter_list(&self) -> Result>>; 34 | async fn connect(&self, adapter: Option) -> Result<()>; 35 | // belongs to transport... async fn get_balance(&self) -> Result; 36 | 37 | // ^ TODO - to sign, we should downcast_arc to native struct... 38 | 39 | // #[cfg(not(target_arch = "wasm32"))] 40 | // async fn sign(&self, instruction: &Instruction) -> Result; 41 | 42 | // #[cfg(target_arch = "wasm32")] 43 | // async fn sign(&self, instruction: &Instruction) -> Result; 44 | } 45 | 46 | downcast_sync!(dyn WalletInterface); 47 | -------------------------------------------------------------------------------- /src/container/interfaces.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Collection interface traits 3 | //! 4 | use kaizen::prelude::*; 5 | use kaizen::result::Result; 6 | 7 | #[workflow_async_trait] 8 | pub trait AsyncAccountAggregatorInterface { 9 | type Key; 10 | async fn writable_account_metas(&self, key: Option<&Self::Key>) -> Result>; 11 | async fn readonly_account_metas(&self, key: Option<&Self::Key>) -> Result>; 12 | } 13 | 14 | pub trait AccountAggregatorInterface { 15 | type Aggregator: AsyncAccountAggregatorInterface; 16 | fn aggregator(&self) -> Result>; 17 | } 18 | 19 | pub trait PdaCollectionCreatorInterface { 20 | type Creator: AsyncPdaCollectionCreatorInterface; 21 | fn creator( 22 | &self, 23 | _program_id: &Pubkey, 24 | _number_of_accounts: usize, 25 | ) -> Result>; 26 | } 27 | 28 | #[workflow_async_trait] 29 | pub trait AsyncPdaCollectionCreatorInterface { 30 | async fn writable_accounts_meta(&self) -> Result>; 31 | } 32 | 33 | pub trait PdaCollectionAccessorInterface { 34 | type Accessor: AsyncPdaCollectionAccessorInterface; 35 | fn accessor( 36 | &self, 37 | _program_id: &Pubkey, 38 | index_range: std::ops::Range, 39 | ) -> Result>; 40 | } 41 | 42 | #[workflow_async_trait] 43 | pub trait AsyncPdaCollectionAccessorInterface { 44 | async fn writable_accounts_meta(&self) -> Result>; 45 | } 46 | -------------------------------------------------------------------------------- /src/transport/config.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Transport interface configuration 3 | //! 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use solana_program::pubkey::Pubkey; 7 | use std::time::Duration; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize)] 10 | pub enum TransportMode { 11 | Inproc, 12 | Emulator, 13 | Validator, 14 | } 15 | 16 | impl TransportMode { 17 | pub fn is_emulator(&self) -> bool { 18 | !matches!(self, TransportMode::Validator) 19 | } 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct TransportConfig { 24 | pub root: Pubkey, 25 | pub timeout: Duration, 26 | pub confirm_transaction_initial_timeout: Duration, 27 | pub retries: usize, 28 | } 29 | 30 | impl TransportConfig { 31 | pub fn new( 32 | root: Pubkey, 33 | timeout: Duration, 34 | confirm_transaction_initial_timeout: Duration, 35 | retries: usize, 36 | ) -> TransportConfig { 37 | TransportConfig { 38 | root, 39 | timeout, 40 | confirm_transaction_initial_timeout, 41 | retries, 42 | } 43 | } 44 | 45 | pub fn default_with_root(root: Pubkey) -> TransportConfig { 46 | TransportConfig { 47 | root, 48 | ..Default::default() 49 | } 50 | } 51 | } 52 | 53 | impl Default for TransportConfig { 54 | fn default() -> Self { 55 | TransportConfig { 56 | root: Pubkey::default(), 57 | timeout: Duration::from_secs(60u64), 58 | confirm_transaction_initial_timeout: Duration::from_secs(5u64), 59 | retries: 2, 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/sequencer.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Account template sequence tracker for identity-based account chains. 3 | //! 4 | 5 | use kaizen::identity::program::Identity; 6 | use kaizen::prelude::*; 7 | use kaizen::result::Result; 8 | use std::sync::{ 9 | atomic::{AtomicU64, Ordering}, 10 | Arc, 11 | }; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct Sequencer { 15 | sequence: Arc, 16 | } 17 | 18 | impl Sequencer { 19 | pub fn new() -> Sequencer { 20 | Sequencer { 21 | sequence: Arc::new(AtomicU64::new(0)), 22 | } 23 | } 24 | 25 | pub fn create_from_identity(reference: &Arc) -> Result { 26 | let identity = reference.try_into_container::()?; 27 | let seq = identity.meta.borrow().get_pda_sequence(); 28 | Ok(Sequencer { 29 | sequence: Arc::new(AtomicU64::new(seq)), 30 | }) 31 | } 32 | 33 | pub fn load_from_identity(&self, reference: &Arc) -> Result<()> { 34 | let identity = reference.try_into_container::()?; 35 | let seq = identity.meta.borrow().get_pda_sequence(); 36 | self.sequence.store(seq, Ordering::SeqCst); 37 | Ok(()) 38 | } 39 | 40 | pub fn next(&self) -> u64 { 41 | self.sequence.fetch_add(1, Ordering::SeqCst) 42 | } 43 | 44 | pub fn advance(&self, n: usize) { 45 | self.sequence.fetch_add(n as u64, Ordering::SeqCst); 46 | } 47 | 48 | pub fn get(&self) -> u64 { 49 | self.sequence.load(Ordering::SeqCst) 50 | } 51 | } 52 | 53 | impl Default for Sequencer { 54 | fn default() -> Sequencer { 55 | Sequencer::new() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/wallet/foreign/native.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Native solana wallet interface (Solana SDK wallet) 3 | //! 4 | use async_trait::async_trait; 5 | use kaizen::result::Result; 6 | use solana_program::pubkey::Pubkey; 7 | use solana_sdk::signature::{read_keypair_file, Keypair}; 8 | use solana_sdk::signer::Signer; 9 | use std::path::Path; 10 | 11 | pub struct Wallet { 12 | keypair: Keypair, 13 | } 14 | 15 | impl Clone for Wallet { 16 | fn clone(&self) -> Self { 17 | Self { 18 | keypair: Keypair::from_bytes(&self.keypair.to_bytes()).unwrap(), 19 | } 20 | } 21 | } 22 | 23 | impl Wallet { 24 | pub fn try_new() -> Result { 25 | let home = home::home_dir().expect("Wallet: unable to get home directory"); 26 | let home = Path::new(&home); 27 | let payer_kp_path = home.join(".config/solana/id.json"); 28 | 29 | let keypair = read_keypair_file(payer_kp_path) 30 | .expect("Couldn't read authority keypair from '~/.config/solana/id.json'"); 31 | 32 | let wallet = Self { keypair }; 33 | 34 | Ok(wallet) 35 | } 36 | 37 | pub fn keypair(&self) -> &Keypair { 38 | &self.keypair 39 | } 40 | } 41 | 42 | #[async_trait(?Send)] 43 | impl super::WalletInterface for Wallet { 44 | fn is_connected(&self) -> bool { 45 | true 46 | } 47 | 48 | fn pubkey(&self) -> Result { 49 | Ok(self.keypair.pubkey()) 50 | } 51 | 52 | async fn get_adapter_list(&self) -> Result>> { 53 | Ok(None) 54 | } 55 | 56 | async fn connect(&self, _adapter: Option) -> Result<()> { 57 | Ok(()) 58 | } 59 | 60 | // async fn get_balance(&self) -> Result; 61 | } 62 | -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Platform-neutral Solana network and authority interface (Solana SDK on native and Web3 APIs in WASM-browser) 3 | //! 4 | 5 | mod lookup; 6 | use downcast::{downcast_sync, AnySync}; 7 | use kaizen::accounts::AccountDataReference; 8 | use kaizen::result::Result; 9 | use solana_program::instruction::Instruction; 10 | use solana_program::pubkey::Pubkey; 11 | use std::sync::Arc; 12 | use workflow_core::id::Id; 13 | use workflow_core::workflow_async_trait; 14 | 15 | #[workflow_async_trait] 16 | pub trait Interface: AnySync { 17 | fn get_authority_pubkey(&self) -> Result; 18 | 19 | async fn execute(&self, instr: &Instruction) -> Result<()>; 20 | async fn lookup(&self, pubkey: &Pubkey) -> Result>>; 21 | async fn lookup_local(&self, pubkey: &Pubkey) -> Result>>; 22 | async fn lookup_remote(&self, pubkey: &Pubkey) -> Result>>; 23 | async fn post(&self, tx: Arc) -> Result<()>; 24 | async fn discard_chain(&self, id: &Id) -> Result<()>; 25 | async fn post_multiple(&self, tx: Vec>) -> Result<()>; 26 | 27 | fn purge(&self, pubkey: Option<&Pubkey>) -> Result<()>; 28 | } 29 | 30 | downcast_sync!(dyn Interface); 31 | 32 | mod config; 33 | pub use config::*; 34 | 35 | mod loaders; 36 | pub use loaders::*; 37 | 38 | mod reflector; 39 | pub use reflector::*; 40 | 41 | mod transaction; 42 | pub use transaction::*; 43 | 44 | mod queue; 45 | pub use queue::*; 46 | 47 | mod observer; 48 | pub use observer::*; 49 | pub mod api; 50 | 51 | #[cfg(target_arch = "wasm32")] 52 | mod wasm; 53 | #[cfg(target_arch = "wasm32")] 54 | pub use wasm::*; 55 | 56 | #[cfg(not(target_arch = "wasm32"))] 57 | mod native; 58 | #[cfg(not(target_arch = "wasm32"))] 59 | pub use native::*; 60 | -------------------------------------------------------------------------------- /src/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Kaizen WASM32 browser initialization API 3 | //! 4 | use wasm_bindgen::prelude::*; 5 | use workflow_wasm::init::init_workflow; 6 | pub use workflow_wasm::init::{global, modules, workflow}; 7 | pub use workflow_wasm::panic::*; 8 | // mod solana_sys; 9 | // pub use solana_sys::*; 10 | use solana_web3_sys::solana::init_solana_web3_sys; 11 | 12 | pub fn init_kaizen( 13 | workflow: &JsValue, 14 | solana: &JsValue, 15 | mods: &JsValue, 16 | ) -> std::result::Result<(), JsValue> { 17 | init_workflow(workflow, mods)?; 18 | 19 | let g = global()?; 20 | js_sys::Reflect::set(&g, &"solana".into(), solana)?; 21 | 22 | init_solana_web3_sys(solana)?; 23 | 24 | Ok(()) 25 | } 26 | 27 | pub fn solana() -> std::result::Result { 28 | js_sys::Reflect::get(&global()?, &"solana".into()) 29 | } 30 | 31 | pub fn wallet_ready_state() -> std::result::Result { 32 | js_sys::Reflect::get(&modules()?, &"WalletReadyState".into()) 33 | } 34 | 35 | pub fn adapters() -> std::result::Result, JsValue> { 36 | let mut list = Vec::new(); 37 | let names = vec!["PhantomWalletAdapter", "SolflareWalletAdapter"]; 38 | let mods = modules()?; 39 | for name in names { 40 | match js_sys::Reflect::get(&mods, &name.into()) { 41 | Ok(adapter_ctr) => { 42 | //log_trace!("adapter_ctr: {:?}", adapter_ctr); 43 | match js_sys::Reflect::construct( 44 | &adapter_ctr.into(), 45 | &js_sys::Array::new_with_length(0), 46 | ) { 47 | Ok(adapter) => { 48 | list.push(adapter); 49 | } 50 | Err(_e) => { 51 | // 52 | } 53 | } 54 | } 55 | Err(_e) => { 56 | // 57 | } 58 | } 59 | } 60 | 61 | Ok(list) 62 | } 63 | -------------------------------------------------------------------------------- /src/container/string.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Segment-based raw UTF-8 String storage 3 | //! 4 | 5 | use crate::container::segment::Segment; 6 | use crate::result::Result; 7 | use std::rc::Rc; 8 | use std::string::*; 9 | // use kaizen::prelude::*; 10 | 11 | pub struct Utf8String<'info, 'refs> { 12 | pub segment: Rc>, 13 | } 14 | 15 | impl<'info, 'refs> Utf8String<'info, 'refs> { 16 | pub fn data_len_min() -> usize { 17 | 0 18 | } 19 | 20 | pub fn try_create_from_segment( 21 | segment: Rc>, 22 | ) -> Result> { 23 | Ok(Utf8String { segment }) 24 | } 25 | 26 | pub fn try_load_from_segment( 27 | segment: Rc>, 28 | ) -> Result> { 29 | Ok(Utf8String { segment }) 30 | } 31 | 32 | /// # Safety 33 | /// This function can shift the underlying account data layout. 34 | /// As such, any references to account data following this function 35 | /// call should be considered to be invalid and reaquired. 36 | #[inline] 37 | pub unsafe fn store(&self, text: &str) -> Result<()> { 38 | let bytes = text.as_bytes(); 39 | self.store_bytes(bytes)?; 40 | Ok(()) 41 | } 42 | 43 | /// # Safety 44 | /// This function can resize the underlying account data. 45 | /// As such, after its use, any references to segments 46 | /// within the account should be considered invalid. 47 | #[inline] 48 | pub unsafe fn store_bytes(&self, bytes: &[u8]) -> Result<()> { 49 | self.segment.try_resize(bytes.len(), false)?; 50 | self.segment.as_slice_mut().copy_from_slice(bytes); 51 | Ok(()) 52 | } 53 | } 54 | 55 | impl<'info, 'refs> ToString for Utf8String<'info, 'refs> { 56 | fn to_string(&self) -> String { 57 | let bytes = self.segment.as_slice::(); 58 | unsafe { String::from_utf8_unchecked(bytes.to_vec()) } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/emulator/interface.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Emulator interface trait 3 | //! 4 | use async_trait::async_trait; 5 | use borsh::{BorshDeserialize, BorshSerialize}; 6 | use downcast::{downcast_sync, AnySync}; 7 | use kaizen::accounts::{AccountDataReference, AccountDescriptorList}; 8 | use kaizen::result::Result; 9 | use serde::{Deserialize, Serialize}; 10 | use solana_program::instruction; 11 | use solana_program::pubkey::Pubkey; 12 | use std::sync::Arc; 13 | 14 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 15 | pub struct ExecutionResponse { 16 | pub error: Option, 17 | pub logs: Vec, 18 | } 19 | 20 | impl ExecutionResponse { 21 | pub fn new(error: Option, logs: Vec) -> Self { 22 | ExecutionResponse { error, logs } 23 | } 24 | } 25 | 26 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 27 | pub struct EmulatorConfig { 28 | execution_latency: u64, 29 | lookup_latency: u64, 30 | } 31 | 32 | #[async_trait] 33 | pub trait EmulatorInterface: AnySync { 34 | async fn lookup(&self, pubkey: &Pubkey) -> Result>>; 35 | async fn execute( 36 | &self, 37 | authority: &Pubkey, 38 | instruction: &instruction::Instruction, 39 | ) -> Result; 40 | 41 | /// funds account key from Pubkey::default() account. If account 'key' is not present, creates 42 | /// and funds this account. This fundtion requires presense of Pubkey::default() (SystemProgram) account 43 | /// that is sufficiently funded. 44 | /// Please not that in Unit Tests, authority account is automatically funded, i.e. unit tests do not 45 | /// require presense of SystemProgram account. 46 | async fn fund(&self, key: &Pubkey, owner: &Pubkey, lamports: u64) -> Result<()>; 47 | 48 | async fn list(&self) -> Result; 49 | 50 | async fn configure(&self, config: EmulatorConfig) -> Result<()>; 51 | } 52 | 53 | downcast_sync!(dyn EmulatorInterface); 54 | -------------------------------------------------------------------------------- /src/payload.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Solana OS Program Instruction Payload header. 3 | //! 4 | //! This header defines the data layout for [`Context`](crate::context::Context) deserialization. 5 | //! 6 | 7 | use kaizen::result::Result; 8 | 9 | pub const PAYLOAD_HAS_IDENTITY_ACCOUNT: u16 = 0x0001; 10 | 11 | #[derive(Copy, Clone)] 12 | #[repr(packed)] 13 | pub struct Payload { 14 | pub version: u8, 15 | pub flags: u16, 16 | 17 | pub system_accounts_len: u8, 18 | pub token_accounts_len: u8, 19 | pub index_accounts_len: u8, 20 | pub collection_accounts_len: u8, 21 | pub generic_template_accounts_len: u8, 22 | pub collection_template_accounts_len: u8, 23 | 24 | pub collection_data_offset: u16, 25 | pub instruction_data_offset: u16, 26 | 27 | pub interface_id: u16, 28 | pub handler_id: u16, 29 | } 30 | 31 | impl Payload { 32 | pub fn version() -> u8 { 33 | 1u8 34 | } 35 | 36 | pub fn total_accounts(&self) -> usize { 37 | self.system_accounts_len as usize 38 | + self.token_accounts_len as usize 39 | + self.index_accounts_len as usize 40 | + self.collection_accounts_len as usize 41 | + self.generic_template_accounts_len as usize 42 | + self.collection_template_accounts_len as usize 43 | } 44 | 45 | pub fn to_vec(&self) -> Vec { 46 | let payload = workflow_core::utils::struct_as_slice_u8(self); 47 | payload.to_vec() 48 | } 49 | 50 | pub fn try_from(data: &[u8]) -> Result<&Payload> { 51 | let payload = unsafe { std::mem::transmute(&data[0]) }; 52 | Ok(payload) 53 | } 54 | 55 | pub fn new>(interface_id: usize, program_instruction: T) -> Self { 56 | Payload { 57 | version: 1, 58 | flags: 0, 59 | system_accounts_len: 0, 60 | token_accounts_len: 0, 61 | index_accounts_len: 0, 62 | collection_accounts_len: 0, 63 | generic_template_accounts_len: 0, 64 | collection_template_accounts_len: 0, 65 | collection_data_offset: 0, 66 | instruction_data_offset: 0, 67 | interface_id: interface_id as u16, 68 | handler_id: program_instruction.into(), 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/identity/tests.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Identity interface testing 3 | //! 4 | #[cfg(test)] 5 | mod tests { 6 | use crate::identity::program::*; 7 | use kaizen::emulator::Simulator; 8 | use kaizen::prelude::*; 9 | use kaizen::result::Result; 10 | 11 | #[allow(unused_imports)] 12 | use std::str::FromStr; 13 | 14 | #[async_std::test] 15 | async fn identity_init() -> Result<()> { 16 | kaizen::container::registry::init()?; 17 | 18 | let program_id = generate_random_pubkey(); 19 | let simulator = Simulator::try_new_for_testing()? 20 | .with_mock_accounts(program_id, None) 21 | .await?; 22 | 23 | let config = InstructionBuilderConfig::new(simulator.program_id()) 24 | .with_authority(&simulator.authority()) 25 | .with_sequence(0u64); 26 | 27 | let builder = InstructionBuilder::new_with_config_for_testing(&config) 28 | .with_generic_account_templates_with_seeds(&[(AddressDomain::Authority, b"proxy")]) 29 | .with_generic_account_templates(1) 30 | .seal()?; 31 | 32 | let accounts = builder.generic_template_accounts(); 33 | let proxy = accounts[0].clone(); // PDA0 34 | let identity = accounts[1].clone(); 35 | 36 | simulator 37 | .execute_handler(builder, |ctx: &ContextReference| { 38 | log_trace!("create identity"); 39 | Identity::create(ctx)?; 40 | Ok(()) 41 | }) 42 | .await?; 43 | 44 | let proxy_pubkey = 45 | find_identity_proxy_pubkey(&simulator.program_id(), &simulator.authority())?; 46 | log_trace!( 47 | "validating proxy pubkey: {} vs {}", 48 | proxy.pubkey, 49 | proxy_pubkey 50 | ); 51 | assert_eq!(proxy.pubkey, proxy_pubkey); 52 | 53 | let config = config.with_identity(&identity.pubkey); 54 | 55 | // load test container 56 | let builder = InstructionBuilder::new_with_config_for_testing(&config).seal()?; 57 | 58 | simulator 59 | .execute_handler(builder, |ctx: &ContextReference| { 60 | log_trace!("testing authority presense in the identity"); 61 | let identity = ctx.try_identity()?; 62 | assert!(identity.try_has_authority(ctx.authority.key)?); 63 | Ok(()) 64 | }) 65 | .await?; 66 | 67 | Ok(()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Platform-neutral [`Instant`] implementation (based on `u64`) that uses Solana [`Clock`](solana_program::clock::Clock) 3 | //! when in Solana program environment. 4 | //! 5 | 6 | use borsh::*; 7 | use cfg_if::cfg_if; 8 | use kaizen::result::Result; 9 | use serde::*; 10 | use solana_program::clock::UnixTimestamp as SolanaUnixTimestamp; 11 | 12 | cfg_if! { 13 | if #[cfg(target_os = "solana")] { 14 | use solana_program::clock::Clock; 15 | use solana_program::sysvar::Sysvar; 16 | } else if #[cfg(target_arch = "wasm32")] { 17 | use js_sys::Date; 18 | } else { 19 | use std::time::SystemTime; 20 | } 21 | } 22 | 23 | /// Instant-like struct compatible with Native, Wasm32, BPF platforms. 24 | /// This structure keeps internal time in seconds and supports Borsh serialization. 25 | #[derive( 26 | BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, 27 | )] 28 | #[repr(transparent)] 29 | pub struct Instant(pub u64); 30 | 31 | impl Instant { 32 | // pub const ZERO: Instant = Instant(0); 33 | 34 | pub fn now() -> Result { 35 | cfg_if! { 36 | if #[cfg(target_os = "solana")] { 37 | let unix_timestamp = Clock::get()?.unix_timestamp; 38 | Ok(Instant(unix_timestamp as u64)) 39 | } else if #[cfg(target_arch = "wasm32")] { 40 | let unix_timestamp = Date::now() / 1000.0; 41 | Ok(Instant(unix_timestamp as u64)) 42 | } else { 43 | let unix_timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs(); 44 | Ok(Instant(unix_timestamp)) 45 | } 46 | } 47 | } 48 | 49 | pub fn elapsed_since(&self, timestamp: &Instant) -> Duration { 50 | Duration(timestamp.0 - self.0) 51 | } 52 | 53 | pub fn elapsed(&self) -> Result { 54 | Ok(self.elapsed_since(&Instant::now()?)) 55 | } 56 | } 57 | 58 | impl From for Instant { 59 | fn from(timestamp: SolanaUnixTimestamp) -> Self { 60 | Self(timestamp as u64) 61 | } 62 | } 63 | 64 | #[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, Eq, Clone, Copy)] 65 | #[repr(transparent)] 66 | pub struct Duration(pub u64); 67 | 68 | impl Duration { 69 | pub const HOUR: Duration = Duration(3600); 70 | pub const DAY: Duration = Duration(3600 * 24); 71 | pub const WEEK: Duration = Duration(3600 * 24 * 7); 72 | } 73 | -------------------------------------------------------------------------------- /macros/src/meta.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use std::convert::Into; 4 | use syn::{parse_macro_input, DeriveInput, Error, Expr}; 5 | 6 | pub fn derive_meta(input: TokenStream) -> TokenStream { 7 | let ast = parse_macro_input!(input as DeriveInput); 8 | let struct_name = &ast.ident; 9 | 10 | let fields = if let syn::Data::Struct(syn::DataStruct { 11 | fields: syn::Fields::Named(ref fields), 12 | .. 13 | }) = ast.data 14 | { 15 | fields 16 | } else { 17 | return Error::new_spanned( 18 | struct_name, 19 | format!("#[derive(Module)] supports only struct declarations"), 20 | ) 21 | .to_compile_error() 22 | .into(); 23 | }; 24 | 25 | let struct_name_string = quote! { #struct_name}.to_string(); //.to_lowercase(); 26 | let path = syn::parse_str::(&struct_name_string) 27 | .expect("Unable to parse strut name as expression"); 28 | 29 | meta_impl(path, fields).into() 30 | } 31 | 32 | fn meta_impl(meta_struct: Expr, fields: &syn::FieldsNamed) -> TokenStream { 33 | let mut field_names = Vec::new(); 34 | let mut get_field_names = Vec::new(); 35 | let mut set_field_names = Vec::new(); 36 | let mut field_types = Vec::new(); 37 | for field in fields.named.iter() { 38 | let ident = field.ident.clone().unwrap(); 39 | field_names.push(ident.clone()); 40 | 41 | let get_field = format!("get_{}", ident); 42 | let get_field_name = syn::Ident::new(&get_field, ident.span()); 43 | let set_field = format!("set_{}", ident); 44 | let set_field_name = syn::Ident::new(&set_field, ident.span()); 45 | get_field_names.push(get_field_name); 46 | set_field_names.push(set_field_name); 47 | field_types.push(field.ty.clone()); 48 | } 49 | 50 | (quote! { 51 | 52 | impl #meta_struct { 53 | 54 | #( 55 | #[inline(always)] 56 | pub fn #get_field_names(&self) -> #field_types { 57 | let unaligned = std::ptr::addr_of!(self.#field_names); 58 | unsafe { std::ptr::read_unaligned(unaligned) } 59 | } 60 | 61 | #[inline(always)] 62 | pub fn #set_field_names(&mut self, v : #field_types) { 63 | let unaligned = std::ptr::addr_of_mut!(self.#field_names); 64 | unsafe { std::ptr::write_unaligned(unaligned, v) }; 65 | } 66 | 67 | )* 68 | 69 | } 70 | 71 | }) 72 | .into() 73 | } 74 | -------------------------------------------------------------------------------- /src/store/memorystore.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! In-memory account store (used primarily by the emulator for unit tests). 3 | //! 4 | use super::*; 5 | use crate::accounts::AccountDescriptor; 6 | use ahash::AHashMap; 7 | use async_std::sync::RwLock; 8 | use kaizen::error::*; 9 | use solana_program::pubkey::Pubkey; 10 | use std::sync::Arc; 11 | 12 | #[derive(Clone)] 13 | pub struct MemoryStore { 14 | map: Arc>>>, 15 | } 16 | 17 | static mut STORE: Option = None; 18 | 19 | impl MemoryStore { 20 | pub fn new_global() -> Result { 21 | let cache = unsafe { STORE.as_ref() }; 22 | if cache.is_some() { 23 | return Err(error!("Store::new() already invoked")); 24 | } 25 | let store = MemoryStore { 26 | map: Arc::new(RwLock::new(AHashMap::default())), 27 | }; 28 | unsafe { 29 | STORE = Some(store.clone()); 30 | } 31 | Ok(store) 32 | } 33 | 34 | pub fn global() -> Result { 35 | let cache = unsafe { STORE.as_ref() }; 36 | match cache { 37 | Some(cache) => Ok(cache.clone()), 38 | None => Ok(MemoryStore::new_global()?), 39 | } 40 | } 41 | 42 | pub fn new_local() -> Result { 43 | let store = MemoryStore { 44 | map: Arc::new(RwLock::new(AHashMap::default())), 45 | }; 46 | 47 | Ok(store) 48 | } 49 | } 50 | 51 | #[async_trait] 52 | impl Store for MemoryStore { 53 | async fn list(&self) -> Result { 54 | let map = self.map.read().await; 55 | let mut account_descriptors = Vec::new(); 56 | for (_pubkey, reference) in map.iter() { 57 | let account_data = reference.account_data.lock()?; 58 | let descriptor: AccountDescriptor = (&*account_data).into(); 59 | account_descriptors.push(descriptor); 60 | } 61 | Ok(AccountDescriptorList::new(account_descriptors)) 62 | } 63 | 64 | async fn lookup(&self, pubkey: &Pubkey) -> Result>> { 65 | Ok(self.map.read().await.get(pubkey).cloned()) 66 | } 67 | async fn store(&self, reference: &Arc) -> Result<()> { 68 | self.map 69 | .write() 70 | .await 71 | .insert(*reference.key, reference.clone()); 72 | Ok(()) 73 | } 74 | async fn purge(&self, pubkey: &Pubkey) -> Result<()> { 75 | self.map.write().await.remove(pubkey); 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kaizen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/solana-kaizen/kaizen" 7 | keywords = ["wasm","framework","transport","solana","bpf","contract"] 8 | categories = [] 9 | exclude = ["/.*", "/test"] 10 | description = """ 11 | Solana OS Rust framework for industrial grade applications. 12 | """ 13 | 14 | [lib] 15 | crate-type = ["cdylib", "lib"] 16 | 17 | [features] 18 | default = ["full"] 19 | check-buffer-sizes = [] 20 | test = [] 21 | full = [ 22 | "check-buffer-sizes" 23 | ] 24 | 25 | [dependencies] 26 | borsh = "0.9.1" 27 | borsh-derive = "0.9.1" 28 | cfg-if = "1.0.0" 29 | kaizen-macros = { path = "macros" } 30 | num = "0.4.0" 31 | num-traits = "0.2.15" 32 | serde = { version = "1.0.152", features = ["derive"] } 33 | solana-program = "1.15.1" 34 | spl-token = { version = "3.5.0", features = [ "no-entrypoint" ] } 35 | workflow-core = "0.3.12" #{ path = "../workflow-rs/core" } 36 | workflow-log = "0.3.12" #{ path = "../workflow-rs/log", features = ["solana"] } 37 | 38 | [target.'cfg(not(target_os = "solana"))'.dependencies] 39 | ahash = "0.8.3" 40 | async-std = { version = "1.12.0", features = ['attributes'] } 41 | async-trait = "0.1.64" 42 | bincode = "1.3.3" 43 | caches = "0.2.3" 44 | chrono = "0.4.23" 45 | derivative = "2.2.0" 46 | downcast = "0.11.0" 47 | futures = "0.3.26" 48 | hexplay = "0.2.1" 49 | inventory = "0.3.3" 50 | js-sys = "0.3.61" 51 | manual_future = "0.1.1" 52 | owning_ref = "0.4.1" 53 | rand = "0.8.5" 54 | regex = "1.7.1" 55 | serde_json = "1.0.91" 56 | serde-wasm-bindgen = "0.4.5" 57 | smallvec = "1.10.0" 58 | solana-sdk = { version = "1.15.1", features = ['full'] } 59 | #serde_derive = "1.0.103" 60 | bs58= "0.4.0" 61 | base64 = "0.13.0" 62 | # solana-web3-sys = { path = "../solana-web3-sys", default-features = false } 63 | solana-web3-sys = { version = "0.1.1", default-features = false } 64 | thiserror = "1.0.38" 65 | wasm-bindgen = "0.2.84" 66 | wasm-bindgen-futures = "0.4.34" 67 | workflow-panic-hook = "0.3.12" 68 | workflow-rpc = "0.3.12" 69 | workflow-wasm = "0.3.12" 70 | # solana-web3-sys = { path = "../solana-web3-sys", default-features = false } 71 | # workflow-panic-hook = { path = "../workflow-rs/panic-hook" } 72 | # workflow-rpc = { path = "../workflow-rs/rpc" } 73 | # workflow-wasm = { path = "../workflow-rs/wasm" } 74 | 75 | [target.'cfg(not(any(target_os = "solana", target_arch = "wasm32")))'.dependencies] 76 | home = "0.5.4" 77 | lazy_static = "1.4.0" 78 | moka = { git = "http://github.com/aspectron/moka" } 79 | solana-client = "1.15.1" 80 | solana-rpc-client-api = "1.15.1" 81 | solana-account-decoder= "1.15.1" 82 | 83 | [target.'cfg(target_arch = "wasm32")'.dependencies] 84 | moka = { git = "http://github.com/aspectron/moka", default_features = false, features = ['js'] } 85 | 86 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Hasher for Pubkeys (uses first 8 bytes of Pubkey as a hash, directly) 3 | //! 4 | 5 | use borsh::{BorshDeserialize, BorshSerialize}; 6 | use solana_program::pubkey::Pubkey; 7 | use std::collections::HashMap; 8 | use std::hash::{BuildHasher, Hash}; 9 | use std::ops::{Deref, DerefMut}; 10 | 11 | #[derive(Clone, Default)] 12 | pub struct PubkeyHasher { 13 | state: u64, 14 | } 15 | 16 | impl std::hash::Hasher for PubkeyHasher { 17 | fn write(&mut self, bytes: &[u8]) { 18 | self.state = unsafe { 19 | std::mem::transmute::<[u8; 8], u64>( 20 | bytes[0..8].try_into().expect("pubkey hasher transmute"), 21 | ) 22 | }; 23 | } 24 | 25 | fn finish(&self) -> u64 { 26 | self.state 27 | } 28 | } 29 | 30 | impl std::hash::BuildHasher for PubkeyHasher { 31 | type Hasher = PubkeyHasher; 32 | fn build_hasher(&self) -> PubkeyHasher { 33 | PubkeyHasher { state: 0 } 34 | } 35 | } 36 | 37 | #[derive(Clone, BorshSerialize, BorshDeserialize)] 38 | pub struct PubkeyHashMap(std::collections::HashMap) 39 | where 40 | K: BorshSerialize + BorshDeserialize + Hash + Eq + PartialEq + PartialOrd, 41 | V: BorshSerialize + BorshDeserialize, 42 | S: Default + BuildHasher; 43 | 44 | impl Default for PubkeyHashMap 45 | where 46 | V: BorshSerialize + BorshDeserialize, 47 | { 48 | fn default() -> Self { 49 | Self::new() 50 | } 51 | } 52 | 53 | impl PubkeyHashMap 54 | where 55 | V: BorshSerialize + BorshDeserialize, 56 | { 57 | pub fn new() -> PubkeyHashMap { 58 | PubkeyHashMap(std::collections::HashMap::with_hasher( 59 | PubkeyHasher::default(), 60 | )) 61 | } 62 | 63 | #[inline] 64 | pub fn get(&self, k: &Pubkey) -> Option<&V> { 65 | self.0.get(k) 66 | } 67 | 68 | #[inline] 69 | pub fn get_mut(&mut self, k: &Pubkey) -> Option<&mut V> { 70 | self.0.get_mut(k) 71 | } 72 | 73 | #[inline] 74 | pub fn insert(&mut self, k: Pubkey, v: V) -> Option { 75 | self.0.insert(k, v) 76 | } 77 | 78 | #[inline] 79 | pub fn remove(&mut self, k: &Pubkey) -> Option { 80 | self.0.remove(k) 81 | } 82 | } 83 | 84 | impl Deref for PubkeyHashMap 85 | where 86 | V: BorshSerialize + BorshDeserialize, 87 | { 88 | type Target = HashMap; 89 | fn deref(&self) -> &Self::Target { 90 | &self.0 91 | } 92 | } 93 | 94 | impl DerefMut for PubkeyHashMap 95 | where 96 | V: BorshSerialize + BorshDeserialize, 97 | { 98 | fn deref_mut(&mut self) -> &mut Self::Target { 99 | &mut self.0 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/address.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Helper structures for PDA generation 3 | //! 4 | 5 | use crate::error::*; 6 | use crate::result::Result; 7 | #[allow(unused_imports)] 8 | use solana_program::instruction::AccountMeta; 9 | use workflow_log::*; 10 | 11 | #[derive(Debug, Clone, Eq, PartialEq)] 12 | pub enum AddressDomain { 13 | /// No domain - `[program_id,seed]` only 14 | None, 15 | /// auto-select identity if available, otherwise authority 16 | Default, 17 | /// explicitly select authority: `[program_id,authority,seed]` 18 | Authority, 19 | /// explicitly select identity: `[program_id,identity,seed]` 20 | Identity, 21 | // /// custom 22 | // Custom(&'seed [u8]) 23 | } 24 | 25 | impl AddressDomain { 26 | #[cfg(not(target_os = "solana"))] 27 | pub fn get_seed( 28 | &self, 29 | authority: Option<&AccountMeta>, 30 | identity: Option<&AccountMeta>, 31 | ) -> Result> { 32 | let seed_prefix = match self { 33 | AddressDomain::None => vec![], 34 | AddressDomain::Default => match identity.or(authority) { 35 | Some(meta) => meta.pubkey.to_bytes().to_vec(), 36 | None => { 37 | return Err(error!( 38 | "Missing identity or authority for default address domain" 39 | )) 40 | } 41 | }, 42 | AddressDomain::Authority => authority 43 | .ok_or(error!("Missing authority for address domain"))? 44 | .pubkey 45 | .to_bytes() 46 | .to_vec(), 47 | AddressDomain::Identity => identity 48 | .ok_or(error!("Missing identity for address domain"))? 49 | .pubkey 50 | .to_bytes() 51 | .to_vec(), 52 | // AddressDomain::Custom(seed) => seed.to_vec() 53 | }; 54 | 55 | Ok(seed_prefix) 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | pub struct ProgramAddressData<'data> { 61 | pub seed: &'data [u8], 62 | } 63 | 64 | impl<'data> ProgramAddressData<'data> { 65 | pub fn from_bytes(seed: &'data [u8]) -> Self { 66 | ProgramAddressData { seed } 67 | } 68 | 69 | pub fn try_from(data: &'data [u8]) -> Result<(ProgramAddressData<'data>, usize)> { 70 | if data.is_empty() { 71 | log_trace!("Error: ProgramAddressData is receiving data len {} (you are not supplying valid template accounts?)",data.len()); 72 | return Err(error_code!(ErrorCode::PADDataBufferSizeAvailable)); 73 | } 74 | let data_len = data[0] as usize; 75 | let bytes_used = data_len + 1; 76 | // log_trace!("| pda: data_len: {} full data len: {}", data_len, data.len()); 77 | let seed = &data[1..bytes_used]; 78 | let pad = ProgramAddressData { seed }; 79 | // log_trace!("| pda: consuming {} bytes", bytes_used); 80 | Ok((pad, bytes_used)) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Program and Application prelude containing general-purpose imports. 3 | //! 4 | 5 | pub use cfg_if::cfg_if; 6 | pub use std::cell::RefCell; 7 | pub use std::rc::Rc; 8 | pub use std::sync::Arc; 9 | 10 | pub use borsh::*; 11 | pub use solana_program::account_info::{AccountInfo, IntoAccountInfo}; 12 | pub use solana_program::entrypoint::ProcessInstruction; 13 | pub use solana_program::entrypoint::ProgramResult; 14 | pub use solana_program::instruction::{AccountMeta, Instruction}; 15 | pub use solana_program::program_error::ProgramError; 16 | pub use solana_program::pubkey::Pubkey; 17 | pub use solana_program::system_instruction::SystemInstruction; 18 | pub use std::convert::TryFrom; 19 | pub use std::convert::TryInto; 20 | 21 | pub use crate::accounts::{Access, AllocationPayer, IsSigner, LamportAllocation}; 22 | pub use crate::address::AddressDomain; 23 | pub use crate::container::array::Array; 24 | pub use crate::container::collection::{ 25 | PdaCollection, PdaCollectionInterface, PdaCollectionMeta, PdaCollectionReference, 26 | PdaProxyCollection, PdaProxyCollectionInterface, PdaProxyCollectionReference, PubkeyCollection, 27 | PubkeyCollectionMeta, PubkeyCollectionReference, PubkeyCollectionStore, 28 | }; 29 | pub use crate::container::segment::{Layout, Segment, SegmentStore}; 30 | pub use crate::container::ContainerHeader; 31 | pub use crate::context::{ 32 | AccountAllocationArgs, Context, ContextReference, HandlerFn, HandlerFnCPtr, 33 | }; 34 | pub use crate::date::*; 35 | pub use crate::hash::PubkeyHashMap; 36 | pub use crate::identity::program::Identity; 37 | pub use crate::payload::Payload; 38 | pub use crate::rent::RentCollector; 39 | 40 | pub use workflow_log; 41 | pub use workflow_log::{log_debug, log_error, log_info, log_trace, log_warning}; 42 | 43 | // pub use kaizen::error::ErrorCode; 44 | pub use kaizen::{error_code, program_error_code}; 45 | 46 | // #[cfg(not(target_os = "solana"))] 47 | // pub use crate::tokens::{get_tokens, get_tokens_info, get_tokens_info_array, TokenInfo}; 48 | 49 | pub use kaizen_macros::{ 50 | container, 51 | declare_handlers, 52 | declare_interface, 53 | declare_program, 54 | // seal, 55 | Meta, 56 | }; 57 | 58 | cfg_if::cfg_if! { 59 | if #[cfg(not(target_os = "solana"))] { 60 | 61 | pub use workflow_core::workflow_async_trait; 62 | 63 | pub use kaizen::builder::{ 64 | Gather, 65 | InstructionBuilder, 66 | InstructionBuilderConfig, 67 | }; 68 | pub use kaizen::pubkey::*; 69 | pub use kaizen::accounts::{AccountData,AccountDataReference}; 70 | pub use kaizen::transport::*; 71 | 72 | pub use kaizen::sequencer::Sequencer; 73 | pub use kaizen::client::Client; 74 | pub use kaizen::container::{ 75 | ContainerReference, 76 | }; 77 | pub use kaizen_macros::declare_client; 78 | pub use async_std; 79 | } 80 | 81 | } 82 | 83 | cfg_if! { 84 | if #[cfg(not(any(target_os = "solana",target_arch = "wasm32")))] { 85 | pub use kaizen::inventory; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/date.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Platform-neutral [`Date`] struct that carries date as the number of days since `0001-01` (proleptic Gregorian calendar) represented by `u32`. 3 | //! 4 | 5 | use borsh::*; 6 | use serde::*; 7 | #[derive( 8 | BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, 9 | )] 10 | #[repr(transparent)] 11 | pub struct Date(pub u32); 12 | 13 | impl From for u32 { 14 | fn from(date: Date) -> Self { 15 | date.0 16 | } 17 | } 18 | impl From for i32 { 19 | fn from(date: Date) -> Self { 20 | date.0 as i32 21 | } 22 | } 23 | 24 | use cfg_if::cfg_if; 25 | cfg_if! { 26 | if #[cfg(not(target_os = "solana"))] { 27 | use chrono::prelude::*; 28 | 29 | impl Date{ 30 | pub fn to_ymd(self)->String{ 31 | self.format("%Y-%m-%d") 32 | } 33 | pub fn format(self, str:&str)->String{ 34 | let n_date = NaiveDate::from_num_days_from_ce_opt(self.0 as i32).expect("the date is out of range"); 35 | n_date.format(str).to_string() 36 | } 37 | } 38 | //use crate::error::Error; 39 | impl From> for Date{ 40 | fn from(dt: DateTime::) -> Self { 41 | Self(dt.num_days_from_ce() as u32) 42 | } 43 | } 44 | 45 | impl From for DateTime::{ 46 | fn from(date: Date) -> Self { 47 | let n_date = NaiveDate::from_num_days_from_ce_opt(date.0 as i32).expect("the date is out of range"); 48 | let n_time = NaiveTime::from_hms_opt(0, 0, 0).unwrap(); 49 | let n_date_time = NaiveDateTime::new(n_date, n_time); 50 | DateTime::::from_utc(n_date_time, Utc) 51 | } 52 | } 53 | 54 | impl ToString for Date{ 55 | fn to_string(&self) -> String { 56 | String::from(*self) 57 | } 58 | } 59 | 60 | impl From for String{ 61 | fn from(date: Date) -> Self { 62 | DateTime::::from(date).format("%d/%m/%Y").to_string() 63 | } 64 | } 65 | 66 | impl TryFrom for Date{ 67 | type Error = String; 68 | fn try_from(value: String) -> Result { 69 | value.as_str().try_into() 70 | } 71 | } 72 | 73 | impl TryFrom<&str> for Date{ 74 | type Error = String;//Error 75 | fn try_from(str: &str) -> Result { 76 | let parts = str.split('-'); 77 | let mut ymd = Vec::new(); 78 | for v in parts{ 79 | if let Ok(v) = v.parse::(){ 80 | ymd.push(v) 81 | } 82 | } 83 | 84 | let err = format!("Unable to parse date `{str}`"); 85 | 86 | if ymd.len() < 3{ 87 | return Err(err); 88 | } 89 | 90 | let d = NaiveDate::from_ymd_opt(ymd[0] as i32, ymd[1], ymd[2]).expect("the date is out of range"); 91 | let t = NaiveTime::from_hms_milli_opt(0,0,0,0).unwrap(); 92 | 93 | let ndt = NaiveDateTime::new(d, t); 94 | Ok(DateTime::::from_utc(ndt, Utc).into()) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/emulator/rpc.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Emulator RPC data structures 3 | //! 4 | use crate::accounts::AccountDataStore; 5 | use borsh::{BorshDeserialize, BorshSerialize}; 6 | use serde::{Deserialize, Serialize}; 7 | use solana_program::instruction; 8 | use solana_program::pubkey::Pubkey; 9 | use workflow_core::u32_try_from; 10 | 11 | #[derive( 12 | Debug, Default, Eq, PartialEq, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, 13 | )] 14 | pub struct AccountMeta { 15 | pub pubkey: Pubkey, 16 | pub is_signer: bool, 17 | pub is_writable: bool, 18 | } 19 | 20 | impl From<&instruction::AccountMeta> for AccountMeta { 21 | fn from(meta: &instruction::AccountMeta) -> Self { 22 | Self { 23 | pubkey: meta.pubkey, 24 | is_signer: meta.is_signer, 25 | is_writable: meta.is_writable, 26 | } 27 | } 28 | } 29 | 30 | impl From<&AccountMeta> for instruction::AccountMeta { 31 | fn from(meta: &AccountMeta) -> Self { 32 | instruction::AccountMeta { 33 | pubkey: meta.pubkey, 34 | is_signer: meta.is_signer, 35 | is_writable: meta.is_writable, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 41 | pub struct ExecuteReq { 42 | pub program_id: Pubkey, 43 | pub accounts: Vec, 44 | pub instruction_data: Vec, 45 | pub authority: Pubkey, 46 | } 47 | 48 | impl From<(&Pubkey, instruction::Instruction)> for ExecuteReq { 49 | fn from((authority, instruction): (&Pubkey, instruction::Instruction)) -> Self { 50 | Self { 51 | program_id: instruction.program_id, 52 | accounts: instruction 53 | .accounts 54 | .iter() 55 | .map(|account| account.into()) 56 | .collect(), 57 | instruction_data: instruction.data.clone(), 58 | authority: *authority, 59 | } 60 | } 61 | } 62 | 63 | impl From for (Pubkey, instruction::Instruction) { 64 | fn from(req: ExecuteReq) -> Self { 65 | ( 66 | req.authority, 67 | instruction::Instruction { 68 | program_id: req.program_id, 69 | accounts: req.accounts.iter().map(|account| account.into()).collect(), 70 | data: req.instruction_data.clone(), 71 | }, 72 | ) 73 | } 74 | } 75 | 76 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 77 | pub struct LookupReq { 78 | pub pubkey: Pubkey, 79 | } 80 | 81 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 82 | pub struct LookupResp { 83 | pub account_data_store: Option, 84 | } 85 | 86 | #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 87 | pub struct FundReq { 88 | pub key: Pubkey, 89 | pub owner: Pubkey, 90 | pub lamports: u64, 91 | } 92 | 93 | u32_try_from! { 94 | #[derive(Clone, Debug, Hash, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] 95 | // #[repr(u32)] 96 | pub enum EmulatorOps { 97 | Lookup = 0, 98 | Execute, 99 | Fund, 100 | List, 101 | Configure, 102 | } 103 | } 104 | 105 | impl From for u32 { 106 | fn from(ops: EmulatorOps) -> u32 { 107 | ops as u32 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/wallet/foreign/wasm.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Web3 Wallet Adaptor WASM32-browser interface 3 | //! 4 | 5 | use crate::error; 6 | use crate::prelude::log_trace; 7 | use crate::transport::Interface; 8 | use crate::transport::Transport; 9 | use async_trait::async_trait; 10 | use js_sys; 11 | use kaizen::error::{js_error, parse_js_error}; 12 | use kaizen::{result::Result, wasm as wasm_utils}; 13 | use solana_program::pubkey::Pubkey; 14 | use wasm_bindgen_futures::JsFuture; 15 | use workflow_wasm::utils; 16 | 17 | #[derive(Clone)] 18 | pub struct Wallet {} 19 | 20 | impl Wallet { 21 | pub fn try_new() -> Result { 22 | let wallet = Self {}; 23 | 24 | Ok(wallet) 25 | } 26 | } 27 | 28 | #[async_trait(?Send)] 29 | impl super::WalletInterface for Wallet { 30 | fn is_connected(&self) -> bool { 31 | self.pubkey().is_ok() 32 | } 33 | 34 | fn pubkey(&self) -> Result { 35 | let pubkey = Transport::global()?.get_authority_pubkey()?; 36 | Ok(pubkey) 37 | } 38 | 39 | async fn get_adapter_list(&self) -> Result>> { 40 | let adapters = wasm_utils::adapters()?; 41 | let wallet_ready_state = 42 | wasm_utils::wallet_ready_state().expect("Wallet: unable to get wallet_ready_state."); 43 | let installed = utils::try_get_string_from_prop(&wallet_ready_state, "Installed") 44 | .expect("Wallet: unable to get Installed property from WalletReadyState."); 45 | let mut adapters_info = Vec::new(); 46 | for (index, adapter) in adapters.iter().enumerate() { 47 | let ready_state = utils::try_get_string_from_prop(adapter, "readyState")?; 48 | adapters_info.push(super::Adapter { 49 | icon: utils::try_get_string_from_prop(adapter, "icon")?, 50 | name: utils::try_get_string_from_prop(adapter, "name")?, 51 | index, 52 | detected: ready_state.eq(&installed), 53 | }); 54 | } 55 | 56 | Ok(Some(adapters_info)) 57 | } 58 | 59 | async fn connect(&self, adapter: Option) -> Result<()> { 60 | let adapters = wasm_utils::adapters()?; 61 | let mut adapter_selection = None; 62 | for (index, a) in adapters.iter().enumerate() { 63 | let name = utils::try_get_string_from_prop(a, "name")?; 64 | if let Some(adapter) = &adapter { 65 | if adapter.index == index && adapter.name.eq(&name) { 66 | adapter_selection = Some(a); 67 | } 68 | } else { 69 | adapter_selection = Some(a); 70 | break; 71 | } 72 | } 73 | 74 | if let Some(adapter_jsv) = adapter_selection { 75 | let promise_jsv = utils::apply_with_args0(adapter_jsv, "connect") 76 | .expect("Wallet: Unable to get 'connect' method from WalletAdapter Object"); 77 | let future = JsFuture::from(js_sys::Promise::from(promise_jsv)); 78 | log_trace!("Wallet: WalletAdapter.connect() ........"); 79 | let res = match future.await { 80 | Ok(r) => r, 81 | Err(e) => return Err(js_error! {e, "Wallet: WalletAdapter.connect() failed"}), 82 | }; 83 | log_trace!( 84 | "Wallet: WalletAdapter.connect() future.await success: {:?}", 85 | res 86 | ); 87 | Transport::global()?.with_wallet(adapter_jsv.clone())?; 88 | log_trace!("Wallet: WalletAdapter.connect() transport updated"); 89 | Ok(()) 90 | } else { 91 | Err(error!("Wallet: Unable to find wallet adapter.")) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/container/collection/proxy.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Collection proxy accounts (for dereferencing arbitrary account sets from vector-based collections) 3 | //! 4 | 5 | use kaizen::container::*; 6 | use kaizen::context::*; 7 | use kaizen::error::ErrorCode; 8 | use kaizen::prelude::*; 9 | use kaizen::result::Result; 10 | 11 | #[derive(Meta)] 12 | pub struct ProxyMeta { 13 | container_type: u32, 14 | reference: Pubkey, 15 | } 16 | 17 | pub struct Proxy<'info, 'refs> { 18 | account: &'refs AccountInfo<'info>, 19 | meta: &'info mut ProxyMeta, 20 | } 21 | 22 | impl<'info, 'refs> Proxy<'info, 'refs> { 23 | pub fn account(&self) -> &'refs AccountInfo<'info> { 24 | self.account 25 | } 26 | 27 | pub fn reference(&self) -> &Pubkey { 28 | &self.meta.reference 29 | } 30 | 31 | pub fn data_len() -> usize { 32 | std::mem::size_of::() 33 | } 34 | 35 | pub fn try_create(account: &'refs AccountInfo<'info>, reference: &Pubkey) -> Result { 36 | let mut data = account.data.borrow_mut(); 37 | let meta = unsafe { &mut *data.as_mut_ptr().cast::() }; 38 | meta.set_container_type(Containers::Proxy as u32); 39 | meta.set_reference(*reference); 40 | 41 | let proxy = Proxy { account, meta }; 42 | 43 | Ok(proxy) 44 | } 45 | 46 | pub fn try_load(account: &'refs AccountInfo<'info>) -> Result { 47 | let mut data = account.data.borrow_mut(); 48 | let meta = unsafe { &mut *data.as_mut_ptr().cast::() }; 49 | 50 | if meta.get_container_type() != Containers::Proxy as u32 { 51 | return Err(error_code!(ErrorCode::InvalidProxyContainerType)); 52 | } 53 | 54 | let proxy = Proxy { account, meta }; 55 | 56 | Ok(proxy) 57 | } 58 | 59 | // pub fn try_load_reference(&self,ctx: &ContextReference<'info,'refs,'_,'_>) 60 | // -> Result<>::T> 61 | // where T : Container<'info,'refs> 62 | // { 63 | 64 | // } 65 | } 66 | 67 | impl<'info, 'refs> Container<'info, 'refs> for Proxy<'info, 'refs> { 68 | type T = Self; 69 | // type T = #struct_name #struct_params; 70 | 71 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | // pub 73 | 74 | fn container_type() -> u32 { 75 | Containers::Proxy as u32 76 | } 77 | 78 | fn initial_data_len() -> usize { 79 | Proxy::data_len() 80 | } 81 | 82 | fn account(&self) -> &'refs solana_program::account_info::AccountInfo<'info> { 83 | self.account() 84 | } 85 | 86 | fn pubkey(&self) -> &solana_program::pubkey::Pubkey { 87 | self.account().key 88 | } 89 | 90 | fn try_allocate( 91 | _ctx: &ContextReference<'info, 'refs, '_, '_>, 92 | _allocation_args: &AccountAllocationArgs<'info, 'refs, '_>, 93 | _reserve_data_len: usize, 94 | ) -> kaizen::result::Result> { 95 | // #struct_name :: #struct_params :: try_allocate(ctx, allocation_args, reserve_data_len) 96 | 97 | // let account_info = ctx.try_create_pda(Proxy::data_len(),allocation_args)?; 98 | 99 | unimplemented!() 100 | } 101 | 102 | fn try_create(_account: &'refs AccountInfo<'info>) -> Result> { 103 | unimplemented!() 104 | //#struct_name :: #struct_params :: try_create(account) 105 | } 106 | 107 | fn try_load(account: &'refs AccountInfo<'info>) -> Result> { 108 | Self::try_load(account) 109 | } 110 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 111 | } 112 | -------------------------------------------------------------------------------- /src/container/serialized.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Segment-based Borsh-serialized store (for pre-defined or arbitrary data types) 3 | //! 4 | 5 | use crate::container::segment::Segment; 6 | use crate::result::Result; 7 | use borsh::{BorshDeserialize, BorshSerialize}; 8 | use std::rc::Rc; 9 | 10 | pub struct Serialized<'info, 'refs, T> 11 | where 12 | T: BorshSerialize + BorshDeserialize, 13 | { 14 | pub segment: Rc>, 15 | _t_: std::marker::PhantomData, 16 | } 17 | 18 | impl<'info, 'refs, T> Serialized<'info, 'refs, T> 19 | where 20 | T: BorshSerialize + BorshDeserialize, 21 | { 22 | pub fn data_len_min() -> usize { 23 | 0 24 | } 25 | 26 | pub fn try_create_from_segment( 27 | segment: Rc>, 28 | ) -> Result> { 29 | Ok(Serialized { 30 | segment, 31 | _t_: std::marker::PhantomData, 32 | }) 33 | } 34 | 35 | pub fn try_load_from_segment( 36 | segment: Rc>, 37 | ) -> Result> { 38 | Ok(Serialized { 39 | segment, 40 | _t_: std::marker::PhantomData, 41 | }) 42 | } 43 | 44 | #[inline] 45 | pub fn load(&self) -> Result>> { 46 | let mut src = self.segment.as_slice::(); 47 | if src.is_empty() { 48 | return Ok(None); 49 | } 50 | let v = BorshDeserialize::deserialize(&mut src)?; 51 | Ok(Some(Box::new(v))) 52 | } 53 | 54 | #[inline] 55 | pub fn load_or_default(&self) -> Result> 56 | where 57 | D: Default + BorshDeserialize, 58 | { 59 | let mut src = self.segment.as_slice::(); 60 | if src.is_empty() { 61 | Ok(Box::default()) 62 | } else { 63 | Ok(Box::new(BorshDeserialize::deserialize(&mut src)?)) 64 | } 65 | } 66 | 67 | #[inline] 68 | pub fn store(&self, v: &T) -> Result<()> { 69 | self.store_bytes(&v.try_to_vec()?)?; 70 | Ok(()) 71 | } 72 | 73 | #[inline] 74 | pub fn store_bytes(&self, vec: &[u8]) -> Result<()> { 75 | self.segment.try_resize(vec.len(), false)?; 76 | self.segment.as_slice_mut().copy_from_slice(vec); 77 | Ok(()) 78 | } 79 | 80 | pub fn is_empty(&self) -> bool { 81 | self.segment.get_data_len() == 0 82 | } 83 | } 84 | 85 | pub struct SerializedVariant<'info, 'refs> { 86 | pub segment: Rc>, 87 | } 88 | 89 | impl<'info, 'refs> SerializedVariant<'info, 'refs> { 90 | pub fn data_len_min() -> usize { 91 | 0 92 | } 93 | 94 | pub fn try_create_from_segment( 95 | segment: Rc>, 96 | ) -> Result> { 97 | Ok(SerializedVariant { segment }) 98 | } 99 | 100 | pub fn try_load_from_segment( 101 | segment: Rc>, 102 | ) -> Result> { 103 | Ok(SerializedVariant { segment }) 104 | } 105 | 106 | pub fn load(&self) -> Result 107 | where 108 | T: BorshDeserialize + BorshSerialize, 109 | { 110 | let mut src = self.segment.as_slice::(); 111 | let v = BorshDeserialize::deserialize(&mut src)?; 112 | Ok(v) 113 | } 114 | 115 | pub fn store(&self, v: &T) -> Result<()> 116 | where 117 | T: BorshDeserialize + BorshSerialize, 118 | { 119 | let vec = v.try_to_vec()?; 120 | self.segment.try_resize(vec.len(), false)?; 121 | self.segment.as_slice_mut().copy_from_slice(&vec); 122 | Ok(()) 123 | } 124 | 125 | #[inline] 126 | pub fn store_bytes(&self, vec: &[u8]) -> Result<()> { 127 | self.segment.try_resize(vec.len(), false)?; 128 | self.segment.as_slice_mut().copy_from_slice(vec); 129 | Ok(()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | [github](https://github.com/solana-kaizen/kaizen) 4 | [crates.io](https://crates.io/crates/kaizen) 5 | [docs.rs](https://docs.rs/kaizen) 6 | license 7 | 8 | 9 | 10 | 11 | 12 | Solana OS Rust framework for platform-neutral application development. 13 | 14 | */ 15 | 16 | pub use cfg_if::cfg_if; 17 | 18 | extern crate self as kaizen; 19 | 20 | pub mod macros { 21 | //! Macros available via the Kaizen framework 22 | pub use kaizen_macros::*; 23 | } 24 | 25 | pub mod accounts; 26 | pub mod address; 27 | pub mod container; 28 | pub mod context; 29 | pub mod date; 30 | pub mod error; 31 | pub mod hash; 32 | pub mod identity; 33 | pub mod instruction; 34 | pub mod payload; 35 | pub mod prelude; 36 | pub mod program; 37 | pub mod realloc; 38 | pub mod rent; 39 | pub mod result; 40 | pub mod time; 41 | pub mod utils; 42 | 43 | pub use solana_program::wasm_bindgen; 44 | 45 | // pub use utils::generate_random_pubkey; 46 | 47 | cfg_if! { 48 | if #[cfg(not(target_os = "solana"))] { 49 | pub mod wasm; 50 | pub mod pubkey; 51 | pub mod builder; 52 | pub mod sequencer; 53 | pub mod client; 54 | pub mod wallet; 55 | pub mod transport; 56 | pub mod store; 57 | pub mod cache; 58 | pub mod user; 59 | 60 | #[allow(unused_imports)] 61 | use wasm_bindgen::prelude::*; 62 | } 63 | } 64 | 65 | cfg_if! { 66 | if #[cfg(target_os = "solana")] { 67 | pub mod solana; 68 | pub use solana::{ 69 | allocate_pda, 70 | allocate_multiple_pda, 71 | transfer_sol, 72 | transfer_spl, 73 | }; 74 | } else { 75 | pub mod emulator; 76 | pub use emulator::{ 77 | allocate_pda, 78 | allocate_multiple_pda, 79 | transfer_sol, 80 | transfer_spl, 81 | }; 82 | } 83 | } 84 | 85 | cfg_if! { 86 | if #[cfg(target_arch = "wasm32")] { 87 | 88 | #[wasm_bindgen] 89 | pub fn init_kaizen(workflow: &JsValue, solana: &JsValue, mods:&JsValue) -> crate::result::Result<()> { 90 | 91 | crate::wasm::init_kaizen(workflow, solana, mods)?; 92 | crate::program::registry::wasm::load_program_registry(workflow)?; 93 | crate::container::registry::wasm::load_container_registry(workflow)?; 94 | 95 | Ok(()) 96 | } 97 | 98 | } else if #[cfg(not(target_os = "solana"))] { 99 | 100 | pub fn init() -> crate::result::Result<()> { 101 | 102 | crate::program::registry::init()?; 103 | crate::container::registry::init()?; 104 | 105 | Ok(()) 106 | } 107 | 108 | } 109 | } 110 | 111 | cfg_if! { 112 | if #[cfg(not(any(target_os = "solana",target_arch = "wasm32")))] { 113 | /// [`inventory`] is used to register application containers in a client-side or kaizen emulator environment. 114 | pub use inventory; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/identity/client.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Identity client-side helper functions 3 | //! 4 | 5 | use borsh::*; 6 | use kaizen::prelude::*; 7 | use kaizen::result::Result; 8 | use solana_program::pubkey::Pubkey; 9 | use workflow_log::*; 10 | 11 | use crate::emulator::Simulator; 12 | use crate::identity::program::*; 13 | 14 | pub async fn locate_identity_pubkey( 15 | transport: &Arc, 16 | program_id: &Pubkey, 17 | authority: &Pubkey, 18 | ) -> Result> { 19 | let proxy_pubkey = find_identity_proxy_pubkey(program_id, authority)?; 20 | if let Some(proxy_ref) = transport.lookup(&proxy_pubkey).await? { 21 | let mut proxy_account_data = proxy_ref.account_data.lock()?; 22 | let proxy_account_info = proxy_account_data.into_account_info(); 23 | let proxy = IdentityProxy::try_load(&proxy_account_info)?; 24 | let identity_pubkey = proxy.meta.borrow().get_identity_pubkey(); 25 | Ok(Some(identity_pubkey)) 26 | } else { 27 | log_warning!("Identity: missing identity proxy account {}", proxy_pubkey); 28 | Ok(None) 29 | } 30 | } 31 | 32 | pub async fn load_identity( 33 | program_id: &Pubkey, 34 | authority: &Pubkey, 35 | ) -> Result>> { 36 | let transport = kaizen::transport::Transport::global()?; 37 | if let Some(identity_pubkey) = locate_identity_pubkey(&transport, program_id, authority).await? 38 | { 39 | Ok(transport.lookup(&identity_pubkey).await?) 40 | } else { 41 | log_trace!("ERROR: identity pubkey not found!"); 42 | Ok(None) 43 | } 44 | } 45 | 46 | pub async fn reload_identity( 47 | program_id: &Pubkey, 48 | authority: &Pubkey, 49 | ) -> Result>> { 50 | let transport = kaizen::transport::Transport::global()?; 51 | if let Some(identity_pubkey) = locate_identity_pubkey(&transport, program_id, authority).await? 52 | { 53 | Ok(transport.lookup_remote(&identity_pubkey).await?) 54 | } else { 55 | log_trace!("ERROR: identity pubkey not found!"); 56 | Ok(None) 57 | } 58 | } 59 | 60 | pub async fn create_identity( 61 | program_id: &Pubkey, 62 | authority: &Pubkey, 63 | interface_id: usize, 64 | handler_id: usize, 65 | instructions: Instr, 66 | ) -> Result { 67 | let instruction_data = instructions.try_to_vec()?; 68 | 69 | let builder = InstructionBuilder::new(program_id, interface_id, handler_id as u16) 70 | .with_authority(authority) 71 | .with_generic_account_templates_with_seeds(&[(AddressDomain::Authority, b"proxy")]) 72 | .with_generic_account_templates(1 + instructions.get_collection_count()) 73 | .with_sequence(0u64) 74 | .with_instruction_data(&instruction_data) 75 | .seal()?; 76 | 77 | let accounts = builder.gather_accounts(None, None)?; 78 | 79 | let transaction = Transaction::new_with_accounts( 80 | format!("Creating generic container {}", accounts[0]).as_str(), 81 | accounts, 82 | builder.try_into()?, 83 | ); 84 | 85 | Ok(TransactionList::new(vec![transaction])) 86 | } 87 | 88 | pub async fn create_identity_for_unit_tests( 89 | simulator: &Simulator, 90 | authority: &Pubkey, 91 | program_id: &Pubkey, 92 | ) -> Result { 93 | let config = InstructionBuilderConfig::new(*program_id) 94 | .with_authority(authority) 95 | .with_sequence(0u64); 96 | 97 | let builder = InstructionBuilder::new_with_config_for_testing(&config) 98 | .with_generic_account_templates_with_seeds(&[(AddressDomain::Authority, b"proxy")]) 99 | .with_generic_account_templates(2) 100 | .seal()?; 101 | 102 | let accounts = builder.generic_template_accounts(); 103 | let identity = accounts[1].clone(); 104 | 105 | simulator 106 | .execute_handler(builder, |ctx: &ContextReference| { 107 | log_trace!("create identity"); 108 | Identity::create(ctx)?; 109 | Ok(()) 110 | }) 111 | .await?; 112 | 113 | Ok(identity.pubkey) 114 | } 115 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Application-level in-memory account data cache backed by [`Moka Cache`](moka::sync::Cache). 3 | //! 4 | 5 | use crate::accounts::AccountDataReference; 6 | use crate::result::Result; 7 | use cfg_if::cfg_if; 8 | use solana_program::pubkey::Pubkey; 9 | use std::sync::Arc; 10 | use workflow_log::log_trace; 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | use std::sync::Mutex; 14 | 15 | cfg_if! { 16 | if #[cfg(target_arch = "wasm32")] { 17 | use moka::unsync::Cache as MokaCache; 18 | } else { 19 | use moka::sync::Cache as MokaCache; 20 | } 21 | } 22 | 23 | const DEFAULT_CAPACITY: u64 = 1024u64 * 1024u64 * 64u64; // 64 megabytes 24 | // const DEFAULT_CAPACITY : u64 = 1024u64 * 1024u64 * 256u64; // 256 megabytes 25 | 26 | cfg_if! { 27 | if #[cfg(target_arch = "wasm32")] { 28 | pub struct Cache { 29 | cache_impl : Arc>>> 30 | } 31 | } else { 32 | pub struct Cache { 33 | cache_impl : MokaCache> 34 | } 35 | } 36 | } 37 | 38 | impl Cache { 39 | pub fn new_with_capacity(capacity: u64) -> Cache { 40 | log_trace!( 41 | "init account data cache with {} MiB capacity", 42 | capacity / 1024 / 1024 43 | ); 44 | let cache_impl = MokaCache::builder() 45 | .weigher(|_key, reference: &Arc| -> u32 { 46 | reference.data_len as u32 47 | }) 48 | .max_capacity(capacity) 49 | .build(); 50 | 51 | cfg_if! { 52 | if #[cfg(target_arch = "wasm32")] { 53 | Self { cache_impl : Arc::new(Mutex::new(cache_impl)) } 54 | } else { 55 | Self { cache_impl } 56 | } 57 | } 58 | } 59 | 60 | pub fn new_with_default_capacity() -> Self { 61 | Self::new_with_capacity(DEFAULT_CAPACITY) 62 | } 63 | 64 | cfg_if! { 65 | if #[cfg(target_arch = "wasm32")] { 66 | 67 | #[inline(always)] 68 | pub fn lookup(&self, pubkey: &Pubkey) -> Result>> { 69 | Ok(self.cache_impl.lock()?.get(pubkey).cloned()) 70 | } 71 | 72 | #[inline(always)] 73 | pub fn store(&self, reference : &Arc) -> Result<()> { 74 | self.cache_impl.lock()?.insert(*reference.key,reference.clone()); 75 | Ok(()) 76 | } 77 | 78 | #[inline(always)] 79 | pub fn purge(&self, pubkey : Option<&Pubkey>) -> Result<()> { 80 | match pubkey { 81 | Some(pubkey) => { 82 | self.cache_impl.lock()?.invalidate(pubkey); 83 | Ok(()) 84 | }, 85 | None => { 86 | self.cache_impl.lock()?.invalidate_all(); 87 | Ok(()) 88 | }, 89 | } 90 | } 91 | 92 | } else { 93 | 94 | #[inline(always)] 95 | pub fn lookup(&self, pubkey: &Pubkey) -> Result>> { 96 | Ok(self.cache_impl.get(pubkey)) 97 | } 98 | 99 | #[inline(always)] 100 | pub fn store(&self, reference : &Arc) -> Result<()> { 101 | self.cache_impl.insert(*reference.key,reference.clone()); 102 | Ok(()) 103 | } 104 | 105 | #[inline(always)] 106 | pub fn purge(&self, pubkey: Option<&Pubkey>) -> Result<()> { 107 | match pubkey { 108 | Some(pubkey) => { 109 | self.cache_impl.invalidate(pubkey); 110 | } 111 | None => { 112 | self.cache_impl.invalidate_all(); 113 | }, 114 | } 115 | Ok(()) 116 | } 117 | 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! General-purpose in-program and client-side utilities 3 | //! 4 | 5 | use solana_program::account_info::AccountInfo; 6 | use solana_program::pubkey::Pubkey; 7 | 8 | pub fn shorten_pubkey(pubkey: &Pubkey) -> String { 9 | let key_str = pubkey.to_string(); 10 | shorten_pubkey_str(key_str.as_str()) 11 | } 12 | 13 | pub fn shorten_pubkey_str(key_str: &str) -> String { 14 | let key_str = format!( 15 | "{}....{}", 16 | &key_str[0..4], 17 | &key_str[key_str.len() - 4..key_str.len()] 18 | ); 19 | key_str 20 | } 21 | 22 | pub const LAMPORTS_PER_SOL: u64 = 1000000000; 23 | #[inline(always)] 24 | pub fn lamports_to_sol(lamports: u64) -> f64 { 25 | lamports as f64 / LAMPORTS_PER_SOL as f64 26 | } 27 | #[inline(always)] 28 | pub fn sol_to_lamports(sol: f64) -> u64 { 29 | (sol * LAMPORTS_PER_SOL as f64) as u64 30 | } 31 | #[inline(always)] 32 | pub fn u64sol_to_lamports(sol: u64) -> u64 { 33 | sol * LAMPORTS_PER_SOL 34 | } 35 | 36 | pub fn pubkey_from_slice(slice: &[u8]) -> crate::result::Result { 37 | let pubkey = Pubkey::new_from_array(<[u8; 32]>::try_from(<&[u8]>::clone( 38 | &slice, //&utils::try_get_vec_from_bn_prop(&response, "owner")?.as_slice() 39 | ))?); 40 | Ok(pubkey) 41 | } 42 | 43 | #[inline(always)] 44 | pub fn fill_buffer_u8(buffer: &mut [u8], v: u8) { 45 | for ptr in buffer.iter_mut() { 46 | *ptr = v 47 | } 48 | } 49 | 50 | #[inline(always)] 51 | pub fn fill_account_buffer_u8(account: &AccountInfo, range: std::ops::Range, v: u8) { 52 | let mut buffer = account.data.borrow_mut(); 53 | fill_buffer_u8(&mut buffer[range], v) 54 | } 55 | 56 | pub fn account_buffer_as_struct_ref<'info, T>( 57 | account: &AccountInfo<'info>, 58 | byte_offset: usize, 59 | ) -> &'info T { 60 | let data = account.data.borrow(); 61 | // unsafe { std::mem::transmute::<_, &T>(data.as_ptr().add(byte_offset)) } 62 | unsafe { &*data.as_ptr().add(byte_offset).cast::() } 63 | } 64 | 65 | pub fn account_buffer_as_struct_mut<'info, T>( 66 | account: &AccountInfo<'info>, 67 | byte_offset: usize, 68 | ) -> &'info mut T { 69 | let mut data = account.data.borrow_mut(); 70 | unsafe { &mut *data.as_mut_ptr().add(byte_offset).cast::() } 71 | } 72 | 73 | pub fn account_buffer_as_slice<'info, T>( 74 | // account: &'refs AccountInfo<'info>, 75 | account: &AccountInfo<'info>, 76 | byte_offset: usize, 77 | elements: usize, 78 | ) -> &'info [T] { 79 | let data = account.data.borrow(); 80 | unsafe { 81 | std::slice::from_raw_parts::( 82 | std::mem::transmute::<_, *const T>(data.as_ptr().add(byte_offset)), 83 | elements, 84 | ) 85 | } 86 | } 87 | 88 | pub fn account_buffer_as_slice_mut<'info, T>( 89 | account: &AccountInfo<'info>, 90 | byte_offset: usize, 91 | elements: usize, 92 | ) -> &'info mut [T] { 93 | let mut data = account.data.borrow_mut(); 94 | unsafe { 95 | std::slice::from_raw_parts_mut::( 96 | std::mem::transmute::<_, *mut T>(data.as_mut_ptr().add(byte_offset)), 97 | elements, 98 | ) 99 | } 100 | } 101 | 102 | pub trait FromU64 { 103 | fn from_u64(v: u64) -> Self; 104 | } 105 | 106 | macro_rules! impl_from_u64 { 107 | ($($ty:ty)*) => { 108 | $( 109 | impl FromU64 for $ty { 110 | #[inline] 111 | fn from_u64(v: u64) -> $ty { 112 | v as $ty 113 | } 114 | } 115 | )* 116 | } 117 | } 118 | 119 | impl_from_u64!(u8 u16 u32 u64 usize); 120 | 121 | pub trait FromUsize { 122 | fn from_usize(v: usize) -> Self; 123 | fn as_usize(v: Self) -> usize; 124 | } 125 | 126 | macro_rules! impl_from_usize { 127 | ($($ty:ty)*) => { 128 | $( 129 | impl FromUsize for $ty { 130 | #[inline] 131 | fn from_usize(v: usize) -> $ty { 132 | v as $ty 133 | } 134 | #[inline] 135 | fn as_usize(v:$ty) -> usize { 136 | v as usize 137 | } 138 | 139 | // fn 140 | } 141 | )* 142 | } 143 | } 144 | 145 | impl_from_usize!(u8 u16 u32 u64 usize); 146 | -------------------------------------------------------------------------------- /src/realloc.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Account data resizing functions used when converting account templates into accounts. 3 | //! 4 | 5 | #[allow(unused_imports)] 6 | use kaizen::error::*; 7 | use kaizen::result::Result; 8 | use solana_program::{ 9 | account_info::AccountInfo, entrypoint::MAX_PERMITTED_DATA_INCREASE, program_memory::sol_memset, 10 | }; 11 | 12 | // ^ WARNING: This code is lifted from Solana SDK 13 | #[cfg(target_pointer_width = "64")] 14 | pub fn account_info_headers(account_info: &AccountInfo) -> Result<(u64, u64)> { 15 | unsafe { 16 | // First set new length in the serialized data 17 | let ptr = account_info.try_borrow_mut_data()?.as_mut_ptr().offset(-8) as *mut u64; 18 | let serialized_len = *ptr; 19 | 20 | // Then set the new length in the local slice 21 | let ptr = &mut *(((account_info.data.as_ptr() as *const u64).offset(1) as u64) as *mut u64); 22 | // *ptr = new_len as u64; 23 | let slice_len = *ptr; 24 | 25 | Ok((serialized_len, slice_len)) 26 | } 27 | } 28 | 29 | #[cfg(target_pointer_width = "64")] 30 | pub fn account_info_realloc( 31 | account_info: &AccountInfo, 32 | new_len: usize, 33 | zero_init: bool, 34 | is_alloc: bool, 35 | ) -> Result<()> { 36 | let orig_len = account_info.data_len(); 37 | 38 | if !is_alloc && new_len > orig_len && new_len - orig_len > MAX_PERMITTED_DATA_INCREASE { 39 | #[cfg(target_os = "solana")] 40 | return Err(error_code!(ErrorCode::MaxPermittedAccountDataIncrease)); 41 | #[cfg(not(target_os = "solana"))] 42 | panic!("maximum permitted account data increase - orig len: {orig_len} new len: {new_len}",); 43 | } 44 | 45 | unsafe { 46 | // First set new length in the serialized data 47 | let ptr = account_info.try_borrow_mut_data()?.as_mut_ptr().offset(-8) as *mut u64; 48 | *ptr = new_len as u64; 49 | 50 | // Then set the new length in the local slice 51 | let ptr = &mut *(((account_info.data.as_ptr() as *const u64).offset(1) as u64) as *mut u64); 52 | *ptr = new_len as u64; 53 | } 54 | 55 | // zero-init if requested 56 | if zero_init && new_len > orig_len { 57 | sol_memset( 58 | &mut account_info.try_borrow_mut_data()?[orig_len..], 59 | 0, 60 | new_len.saturating_sub(orig_len), 61 | ); 62 | } 63 | 64 | Ok(()) 65 | } 66 | 67 | #[cfg(target_pointer_width = "32")] 68 | pub fn account_info_headers(account_info: &AccountInfo) -> Result<(u64, u64)> { 69 | unsafe { 70 | // First set new length in the serialized data 71 | let ptr = account_info.try_borrow_mut_data()?.as_mut_ptr().offset(-4) as *mut u32; 72 | let serialized_len = *ptr; 73 | 74 | // Then set the new length in the local slice 75 | let ptr = &mut *(((account_info.data.as_ptr() as *const u32).offset(1) as u32) as *mut u32); 76 | // *ptr = new_len as u64; 77 | let slice_len = *ptr; 78 | 79 | Ok((serialized_len as u64, slice_len as u64)) 80 | } 81 | } 82 | 83 | #[cfg(target_pointer_width = "32")] 84 | pub fn account_info_realloc( 85 | account_info: &AccountInfo, 86 | new_len: usize, 87 | zero_init: bool, 88 | is_alloc: bool, 89 | ) -> Result<()> { 90 | let orig_len = account_info.data_len(); 91 | 92 | if !is_alloc && (new_len > orig_len && new_len - orig_len > MAX_PERMITTED_DATA_INCREASE) { 93 | #[cfg(target_os = "solana")] 94 | return Err(error_code!(ErrorCode::MaxPermittedAccountDataIncrease)); 95 | #[cfg(not(target_os = "solana"))] 96 | panic!("maximum permitted account data increase - orig len: {orig_len} new len: {new_len}"); 97 | } 98 | 99 | unsafe { 100 | // First set new length in the serialized data 101 | let ptr = account_info.try_borrow_mut_data()?.as_mut_ptr().offset(-4) as *mut u32; 102 | *ptr = new_len as u32; 103 | 104 | // Then set the new length in the local slice 105 | let ptr = &mut *(((account_info.data.as_ptr() as *const u32).offset(1) as u32) as *mut u32); 106 | *ptr = new_len as u32; 107 | } 108 | 109 | // zero-init if requested 110 | if zero_init && new_len > orig_len { 111 | sol_memset( 112 | &mut account_info.try_borrow_mut_data()?[orig_len..], 113 | 0, 114 | new_len.saturating_sub(orig_len), 115 | ); 116 | } 117 | 118 | Ok(()) 119 | } 120 | -------------------------------------------------------------------------------- /macros/src/interface.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use std::convert::Into; 4 | use syn::{ 5 | parse::{Parse, ParseStream}, 6 | parse_macro_input, 7 | punctuated::Punctuated, 8 | Error, Expr, ExprArray, PathArguments, Result, Token, 9 | }; 10 | 11 | #[derive(Debug)] 12 | struct Primitive { 13 | handler_struct_decl: String, 14 | handler_lifetimes: Option, 15 | handler_methods: ExprArray, 16 | } 17 | 18 | impl Parse for Primitive { 19 | fn parse(input: ParseStream) -> Result { 20 | let parsed = Punctuated::::parse_terminated(input).unwrap(); 21 | if parsed.len() != 2 { 22 | return Err(Error::new_spanned( 23 | parsed, 24 | format!("usage: declare_handlers!(,[, ..])"), 25 | )); 26 | } 27 | 28 | let handler_struct_expr = parsed.first().unwrap().clone(); 29 | let mut handler_struct = match handler_struct_expr { 30 | Expr::Path(path) => path, 31 | _ => { 32 | return Err(Error::new_spanned( 33 | handler_struct_expr, 34 | // format!("unsupported segment attribute: {}, supported attributes are {}", name, SEGMENT_ATTRIBUTES.join(", ")) 35 | format!("first argument should be a struct name (and an optional lifetime)"), 36 | )); 37 | } 38 | }; 39 | 40 | let mut target = handler_struct.path.segments.last_mut().unwrap(); 41 | let handler_lifetimes = match &target.arguments { 42 | PathArguments::AngleBracketed(params) => { 43 | let mut ts = proc_macro2::TokenStream::new(); 44 | params.args.clone().to_tokens(&mut ts); 45 | let lifetimes = ts.to_string(); 46 | target.arguments = PathArguments::None; 47 | Some(lifetimes) 48 | } 49 | _ => None, 50 | }; 51 | 52 | let mut ts = proc_macro2::TokenStream::new(); 53 | handler_struct.to_tokens(&mut ts); 54 | let handler_struct_decl = ts.to_string(); 55 | 56 | let handler_methods_ = parsed.last().unwrap().clone(); 57 | let handler_methods = match handler_methods_ { 58 | Expr::Array(array) => array, 59 | _ => { 60 | return Err(Error::new_spanned( 61 | handler_methods_, 62 | format!("second argument must be an array of static functions"), 63 | )); 64 | } 65 | }; 66 | 67 | let handlers = Primitive { 68 | handler_struct_decl, 69 | handler_lifetimes, 70 | handler_methods, 71 | }; 72 | Ok(handlers) 73 | } 74 | } 75 | 76 | // #[proc_macro] 77 | pub fn declare_interface(input: TokenStream) -> TokenStream { 78 | let primitive = parse_macro_input!(input as Primitive); 79 | let handler_struct_name = primitive.handler_struct_decl.to_string(); 80 | let handler_methods = primitive.handler_methods; 81 | let len = handler_methods.elems.len(); 82 | let impl_str = match &primitive.handler_lifetimes { 83 | Some(lifetimes) => format!( 84 | "impl<{}> {}<{}>", 85 | lifetimes, primitive.handler_struct_decl, lifetimes 86 | ), 87 | None => format!("impl {}", primitive.handler_struct_decl), 88 | }; 89 | let impl_ts: proc_macro2::TokenStream = impl_str.parse().unwrap(); 90 | let handler_struct_path: proc_macro2::TokenStream = 91 | primitive.handler_struct_decl.parse().unwrap(); 92 | 93 | let output = quote! { 94 | 95 | #impl_ts { 96 | 97 | pub const INTERFACE_HANDLERS : [HandlerFn;#len] = #handler_methods; 98 | 99 | pub fn handler_id(handler_fn: HandlerFn) -> u16 { 100 | #handler_struct_path::INTERFACE_HANDLERS.iter() 101 | .position(|&hfn| hfn as HandlerFnCPtr == handler_fn as HandlerFnCPtr ) 102 | .expect("invalid primitive handler") 103 | as u16 104 | } 105 | 106 | pub fn program(ctx:&kaizen::context::ContextReference) -> solana_program::entrypoint::ProgramResult { 107 | if ctx.handler_id >= #handler_struct_path::INTERFACE_HANDLERS.len() { 108 | workflow_log::log_error!("Error - invalid handler id {} for interface {}", ctx.handler_id, #handler_struct_name); 109 | return Err(solana_program::program_error::ProgramError::InvalidArgument); 110 | } 111 | #handler_struct_path::INTERFACE_HANDLERS[ctx.handler_id](ctx) 112 | } 113 | } 114 | }; 115 | 116 | output.into() 117 | } 118 | -------------------------------------------------------------------------------- /src/emulator/client.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Kaizen Emulator RPC client 3 | //! 4 | 5 | use crate::accounts::AccountDescriptorList; 6 | use async_trait::async_trait; 7 | use kaizen::accounts::AccountDataReference; 8 | use kaizen::error::*; 9 | use kaizen::result::Result; 10 | use regex::Regex; 11 | use solana_program::instruction::Instruction; 12 | use solana_program::pubkey::Pubkey; 13 | use std::sync::Arc; 14 | use workflow_core::trigger::Listener; 15 | use workflow_log::*; 16 | use workflow_rpc::client::prelude::{Encoding, Interface, RpcClient, RpcClientOptions}; 17 | use workflow_rpc::client::result::Result as RpcResult; 18 | 19 | use super::interface::{EmulatorConfig, EmulatorInterface, ExecutionResponse}; 20 | use super::rpc::*; 21 | 22 | #[derive(Clone)] 23 | pub struct EmulatorRpcClient { 24 | rpc: Arc>, 25 | } 26 | 27 | impl EmulatorRpcClient { 28 | pub fn new(url: &str) -> RpcResult { 29 | let re = Regex::new(r"^rpc").unwrap(); 30 | let url = re.replace(url, "ws"); 31 | log_trace!("Emulator RPC client url: {}", url); 32 | 33 | let interface = Interface::::new(); 34 | 35 | let options = RpcClientOptions { 36 | url: &url, 37 | ..RpcClientOptions::default() 38 | }; 39 | let client = EmulatorRpcClient { 40 | rpc: Arc::new(RpcClient::new_with_encoding( 41 | Encoding::Borsh, 42 | interface.into(), 43 | options, 44 | )?), 45 | }; 46 | 47 | Ok(client) 48 | } 49 | 50 | pub async fn connect(&self, block: bool) -> Result> { 51 | self.rpc.connect(block).await.map_err(|e| error!("{}", e)) 52 | } 53 | 54 | pub fn connect_as_task(self: &Arc) -> Result<()> { 55 | let self_ = self.clone(); 56 | workflow_core::task::spawn(async move { 57 | self_.rpc.connect(false).await.ok(); 58 | }); 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[async_trait] 64 | impl EmulatorInterface for EmulatorRpcClient { 65 | async fn lookup(&self, pubkey: &Pubkey) -> Result>> { 66 | let message = LookupReq { pubkey: *pubkey }; 67 | let resp: Result = self 68 | .rpc 69 | .call(EmulatorOps::Lookup, message) 70 | .await 71 | .map_err(|err| err.into()); 72 | match resp { 73 | Ok(LookupResp { account_data_store }) => { 74 | Ok(account_data_store.map(|account_data_store| { 75 | Arc::new(AccountDataReference::from(&account_data_store)) 76 | })) 77 | } 78 | Err(err) => Err(err), 79 | } 80 | } 81 | 82 | async fn execute( 83 | &self, 84 | authority: &Pubkey, 85 | instruction: &Instruction, 86 | ) -> Result { 87 | // we try to re-use existing data types from Solana but 88 | // these do not implement Borsh serialization 89 | let message = ExecuteReq { 90 | program_id: instruction.program_id, 91 | accounts: instruction 92 | .accounts 93 | .iter() 94 | .map(|account| account.into()) 95 | .collect(), 96 | instruction_data: instruction.data.clone(), 97 | authority: *authority, 98 | }; 99 | let resp: Result = self 100 | .rpc 101 | .call(EmulatorOps::Execute, message) 102 | .await 103 | .map_err(|err| err.into()); 104 | if let Ok(resp) = &resp { 105 | // TODO setup verbose flag somewhere in configuration 106 | for line in resp.logs.iter() { 107 | for l in line.split('\n') { 108 | log_trace!("| {}", l); 109 | } 110 | } 111 | } 112 | 113 | resp 114 | } 115 | 116 | async fn fund(&self, key: &Pubkey, owner: &Pubkey, lamports: u64) -> Result<()> { 117 | let message = FundReq { 118 | key: *key, 119 | owner: *owner, 120 | lamports, 121 | }; 122 | let resp: Result<()> = self 123 | .rpc 124 | .call(EmulatorOps::Fund, message) 125 | .await 126 | .map_err(|err| err.into()); 127 | resp 128 | } 129 | 130 | async fn list(&self) -> Result { 131 | let resp: Result = self 132 | .rpc 133 | .call(EmulatorOps::List, ()) 134 | .await 135 | .map_err(|err| err.into()); 136 | resp 137 | } 138 | 139 | async fn configure(&self, config: EmulatorConfig) -> Result<()> { 140 | let resp: Result<()> = self 141 | .rpc 142 | .call(EmulatorOps::Configure, config) 143 | .await 144 | .map_err(|err| err.into()); 145 | resp 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/user.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! User account data structure (client-side [`Identity`] binding) for identity-based applications. 3 | //! 4 | 5 | use kaizen::prelude::*; 6 | use kaizen::result::Result; 7 | use kaizen::transport::TransportMode; 8 | use std::sync::Mutex; 9 | 10 | use crate::error; 11 | 12 | #[derive(Clone)] 13 | pub enum IdentityState { 14 | Unknown, 15 | Missing, 16 | Present, 17 | } 18 | 19 | pub struct Inner { 20 | authority_pubkey: Option, 21 | identity_pubkey: Option, 22 | identity_state: IdentityState, 23 | transport_mode: Option, 24 | } 25 | 26 | impl Default for Inner { 27 | fn default() -> Self { 28 | Self::new() 29 | } 30 | } 31 | 32 | impl Inner { 33 | pub fn new() -> Self { 34 | Self { 35 | authority_pubkey: None, 36 | identity_pubkey: None, 37 | identity_state: IdentityState::Unknown, 38 | transport_mode: None, 39 | } 40 | } 41 | 42 | pub fn new_with_args( 43 | transport_mode: &TransportMode, 44 | authority_pubkey: &Pubkey, 45 | identity_pubkey: &Pubkey, 46 | ) -> Self { 47 | Self { 48 | authority_pubkey: Some(*authority_pubkey), 49 | identity_pubkey: Some(*identity_pubkey), 50 | identity_state: IdentityState::Present, 51 | transport_mode: Some(transport_mode.clone()), 52 | } 53 | } 54 | } 55 | 56 | #[derive(Clone)] 57 | pub struct User { 58 | inner: Arc>, 59 | sequencer: Sequencer, 60 | } 61 | 62 | impl Default for User { 63 | fn default() -> Self { 64 | Self::new() 65 | } 66 | } 67 | 68 | impl User { 69 | pub fn new() -> Self { 70 | User { 71 | inner: Arc::new(Mutex::new(Inner::new())), 72 | sequencer: Sequencer::default(), 73 | } 74 | } 75 | 76 | pub fn new_with_args( 77 | transport_mode: &TransportMode, 78 | authority_pubkey: &Pubkey, 79 | identity_pubkey: &Pubkey, 80 | sequencer: &Sequencer, 81 | ) -> Self { 82 | User { 83 | inner: Arc::new(Mutex::new(Inner::new_with_args( 84 | transport_mode, 85 | authority_pubkey, 86 | identity_pubkey, 87 | ))), 88 | sequencer: sequencer.clone(), 89 | } 90 | } 91 | 92 | pub fn transport_mode(&self) -> Option { 93 | self.inner.lock().unwrap().transport_mode.as_ref().cloned() 94 | } 95 | 96 | pub fn identity(&self) -> Pubkey { 97 | self.inner 98 | .lock() 99 | .unwrap() 100 | .identity_pubkey 101 | .expect("User::identity() missing identity pubkey") 102 | } 103 | 104 | pub fn authority(&self) -> Pubkey { 105 | self.inner 106 | .lock() 107 | .unwrap() 108 | .authority_pubkey 109 | .expect("User::authority() missing authority pubkey") 110 | } 111 | 112 | pub fn sequencer(&self) -> Sequencer { 113 | self.sequencer.clone() 114 | } 115 | 116 | pub fn builder_args(&self) -> Result<(Pubkey, Pubkey, Sequencer)> { 117 | let sequencer = self.sequencer.clone(); 118 | let inner = self.inner.lock().unwrap(); 119 | let authority = inner 120 | .authority_pubkey 121 | .ok_or_else(|| error!("User record is missing authority"))?; 122 | let identity = inner 123 | .identity_pubkey 124 | .ok_or_else(|| error!("User record is missing identity"))?; 125 | Ok((authority, identity, sequencer)) 126 | } 127 | 128 | pub async fn load_identity( 129 | &self, 130 | program_id: &Pubkey, 131 | authority: &Pubkey, 132 | ) -> Result>> { 133 | match kaizen::identity::client::load_identity(program_id, authority).await? { 134 | Some(identity) => { 135 | let transport_mode = Transport::global()?.mode(); 136 | self.sequencer.load_from_identity(&identity)?; 137 | let mut inner = self.inner.lock()?; 138 | inner.identity_state = IdentityState::Present; 139 | inner.identity_pubkey = Some(*identity.pubkey()); 140 | inner.authority_pubkey = Some(*authority); 141 | inner.transport_mode = Some(transport_mode); 142 | Ok(Some(identity)) 143 | } 144 | None => { 145 | let mut inner = self.inner.lock()?; 146 | inner.identity_state = IdentityState::Missing; 147 | inner.identity_pubkey = None; 148 | Ok(None) 149 | } 150 | } 151 | } 152 | 153 | pub fn is_present(&self) -> Result { 154 | let inner = self.inner.lock()?; 155 | match inner.identity_state { 156 | IdentityState::Present => Ok(true), 157 | _ => Ok(false), 158 | } 159 | } 160 | 161 | pub fn is_checked(&self) -> Result { 162 | let inner = self.inner.lock()?; 163 | match inner.identity_state { 164 | IdentityState::Present => Ok(true), 165 | IdentityState::Missing => Ok(true), 166 | _ => Ok(false), 167 | } 168 | } 169 | 170 | pub fn state(&self) -> Result { 171 | let inner = self.inner.lock()?; 172 | Ok(inner.identity_state.clone()) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/program.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Solana OS client-side program registry (for client-side program invocation, primarily for unit testing) 3 | //! 4 | 5 | #[cfg(not(target_os = "solana"))] 6 | pub mod registry { 7 | use ahash::AHashMap; 8 | use derivative::Derivative; 9 | use kaizen::prelude::*; 10 | use kaizen::result::Result; 11 | use std::sync::Arc; 12 | use std::sync::RwLock; 13 | use wasm_bindgen::prelude::*; 14 | use workflow_log::log_trace; 15 | 16 | #[derive(Derivative)] 17 | #[derivative(Clone, Debug)] 18 | pub struct EntrypointDeclaration { 19 | pub program_id: Pubkey, 20 | pub name: &'static str, 21 | #[derivative(Debug = "ignore")] 22 | pub entrypoint_fn: ProcessInstruction, 23 | } 24 | 25 | impl EntrypointDeclaration { 26 | pub const fn new( 27 | program_id: Pubkey, 28 | name: &'static str, 29 | entrypoint_fn: ProcessInstruction, 30 | ) -> Self { 31 | EntrypointDeclaration { 32 | program_id, 33 | name, 34 | entrypoint_fn, 35 | } 36 | } 37 | } 38 | 39 | impl std::fmt::Display for EntrypointDeclaration { 40 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 41 | write!(f, "{:>20} {}", self.program_id.to_string(), self.name) 42 | } 43 | } 44 | 45 | #[cfg(not(target_arch = "wasm32"))] 46 | inventory::collect!(EntrypointDeclaration); 47 | 48 | pub type EntrypointDeclarationRegistry = Arc>>; 49 | 50 | static mut ENTRYPOINT_REGISTRY: Option = None; 51 | 52 | pub fn global() -> EntrypointDeclarationRegistry { 53 | let registry = unsafe { ENTRYPOINT_REGISTRY.as_ref() }; 54 | match registry { 55 | Some(registry) => registry.clone(), 56 | None => { 57 | let registry = Arc::new(RwLock::new(AHashMap::new())); 58 | unsafe { 59 | ENTRYPOINT_REGISTRY = Some(registry.clone()); 60 | } 61 | registry 62 | } 63 | } 64 | } 65 | 66 | pub fn lookup(program_id: &Pubkey) -> Result> { 67 | Ok(global().read()?.get(program_id).cloned()) 68 | } 69 | 70 | #[cfg(not(target_arch = "wasm32"))] 71 | pub fn init() -> Result<()> { 72 | // log_trace!("program entrypoing init"); 73 | let registry = global(); 74 | let mut map = registry.write()?; 75 | if map.len() != 0 { 76 | panic!("entrypoint type registry is already initialized"); 77 | } 78 | 79 | for entrypoint_declaration in 80 | inventory::iter:: 81 | { 82 | if let Some(previous_declaration) = map.insert( 83 | entrypoint_declaration.program_id, 84 | entrypoint_declaration.clone(), 85 | ) { 86 | panic!( 87 | "duplicate entrypoint declaration for program {} - {}:\n{:#?}\n~vs~\n{:#?}", 88 | entrypoint_declaration.program_id, 89 | entrypoint_declaration.name, 90 | entrypoint_declaration, 91 | previous_declaration 92 | ); 93 | } 94 | } 95 | 96 | Ok(()) 97 | } 98 | 99 | pub fn register_entrypoint_declaration( 100 | entrypoint_declaration: EntrypointDeclaration, 101 | ) -> Result<()> { 102 | if let Some(_previous_declaration) = global().write()?.insert( 103 | entrypoint_declaration.program_id, 104 | entrypoint_declaration.clone(), 105 | ) { 106 | panic!( 107 | "duplicate entrypoint declaration for program {}:\n{:#?}\n~vs~\n{:#?}", 108 | entrypoint_declaration.program_id, entrypoint_declaration, _previous_declaration 109 | ); 110 | } 111 | Ok(()) 112 | } 113 | 114 | #[wasm_bindgen] 115 | pub fn list_entrypoints() -> Result<()> { 116 | let registry = global(); 117 | let map = registry.read()?; 118 | for (_, entrypoint) in map.iter() { 119 | log_trace!("[program] {}", entrypoint); 120 | } 121 | Ok(()) 122 | } 123 | 124 | #[cfg(target_arch = "wasm32")] 125 | pub mod wasm { 126 | 127 | use super::*; 128 | use js_sys::Array; 129 | 130 | pub fn load_program_registry(pkg: &JsValue) -> Result<()> { 131 | let mut fn_names = Vec::new(); 132 | let keys = js_sys::Reflect::own_keys(pkg)?; 133 | let keys_vec = keys.to_vec(); 134 | // for idx in 0..keys_vec.len() { 135 | // let name: String = keys_vec[idx].as_string().unwrap_or("".into()); 136 | // if name.starts_with("entrypoint_declaration_register_") { 137 | // fn_names.push(keys_vec[idx].clone()); 138 | // } 139 | // } 140 | for key in keys_vec.iter() { 141 | let name: String = key.as_string().unwrap_or("".into()); 142 | if name.starts_with("entrypoint_declaration_register_") { 143 | fn_names.push(key.clone()); 144 | } 145 | } 146 | 147 | if fn_names.is_empty() { 148 | panic!("kaizen::entrypoint::registry::with_entrypoints(): no registered entrypoints found!"); 149 | } 150 | 151 | for fn_name in fn_names.iter() { 152 | let fn_jsv = js_sys::Reflect::get(pkg, fn_name)?; 153 | let args = Array::new(); 154 | let _ret_jsv = js_sys::Reflect::apply(&fn_jsv.into(), pkg, &args)?; 155 | } 156 | 157 | Ok(()) 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/store/filestore.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! File-store (used by the Emulator in multi-user testing) 3 | //! 4 | 5 | use crate::accounts::AccountDataStore; 6 | use crate::accounts::AccountDescriptor; 7 | 8 | use super::*; 9 | use async_std::fs; 10 | use async_std::path::Path; 11 | use async_std::path::PathBuf; 12 | use async_trait::async_trait; 13 | use borsh::*; 14 | use kaizen::accounts::AccountData; 15 | use kaizen::cache::Cache; 16 | use kaizen::result::Result; 17 | use std::sync::Arc; 18 | use workflow_log::log_error; 19 | use workflow_log::*; 20 | 21 | #[derive(Clone)] 22 | pub struct FileStore { 23 | data_folder: PathBuf, 24 | cache: Option>, 25 | } 26 | 27 | impl FileStore { 28 | pub fn default_data_folder() -> PathBuf { 29 | let home_dir: PathBuf = home::home_dir().unwrap().into(); 30 | Path::new(&home_dir).join("workflow").join("accounts") 31 | } 32 | 33 | pub fn try_new() -> Result { 34 | Self::try_new_with_folder_and_cache(None, None) 35 | } 36 | pub fn try_new_with_cache(cache: Arc) -> Result { 37 | Self::try_new_with_folder_and_cache(None, Some(cache)) 38 | } 39 | pub fn try_new_with_folder_and_cache( 40 | data_folder: Option, 41 | cache: Option>, 42 | ) -> Result { 43 | let data_folder = match data_folder { 44 | Some(data_folder) => data_folder, 45 | None => Self::default_data_folder(), 46 | }; 47 | log_trace!( 48 | "init FileStore at {}", 49 | data_folder.clone().into_os_string().into_string()? 50 | ); 51 | std::fs::create_dir_all(&data_folder)?; 52 | 53 | Ok(FileStore { data_folder, cache }) 54 | } 55 | } 56 | 57 | #[async_trait] 58 | impl Store for FileStore { 59 | async fn list(&self) -> Result { 60 | let mut entries = std::fs::read_dir(&self.data_folder)? 61 | .map(|res| res.map(|e| e.path())) 62 | .collect::, std::io::Error>>()?; 63 | 64 | // The order in which `read_dir` returns entries is not guaranteed. If reproducible 65 | // ordering is required the entries should be explicitly sorted. 66 | 67 | entries.sort(); 68 | 69 | let mut account_descriptors = Vec::new(); 70 | for entry in entries { 71 | let data = fs::read(entry).await?; 72 | let account_data_store = AccountDataStore::try_from_slice(&data)?; 73 | let account_data = AccountData::from(&account_data_store); 74 | let descriptor: AccountDescriptor = account_data.into(); 75 | account_descriptors.push(descriptor); 76 | } 77 | 78 | Ok(AccountDescriptorList::new(account_descriptors)) 79 | } 80 | 81 | async fn lookup(&self, pubkey: &Pubkey) -> Result>> { 82 | if let Some(cache) = &self.cache { 83 | if let Ok(Some(reference)) = cache.lookup(pubkey) { 84 | // { 85 | // log_trace!("~~~ lookup "); 86 | // let account_data = &reference.account_data.read().await; 87 | // trace_hex(&*account_data.data); 88 | // log_trace!("~~~ lookup "); 89 | // } 90 | 91 | return Ok(Some(reference)); 92 | } 93 | } 94 | 95 | let filename = self.data_folder.join(pubkey.to_string()); 96 | if filename.exists().await { 97 | let data = fs::read(&self.data_folder.join(pubkey.to_string())).await?; 98 | let account_data_store = AccountDataStore::try_from_slice(&data)?; 99 | let account_data = AccountData::from(&account_data_store); 100 | 101 | // log_trace!("~~~ load data {}",account_data.key); 102 | // let account_data = &reference.account_data.read().await; 103 | // trace_hex(&account_data.data); 104 | // log_trace!("~~~ load data"); 105 | 106 | let reference = Arc::new(AccountDataReference::new(account_data)); 107 | 108 | if let Some(cache) = &self.cache { 109 | cache.store(&reference)?; 110 | } 111 | 112 | Ok(Some(reference)) 113 | } else { 114 | Ok(None) 115 | } 116 | } 117 | async fn store(&self, reference: &Arc) -> Result<()> { 118 | // { 119 | // log_trace!("~~~ store {}",reference.key); 120 | // let account_data = &reference.account_data.read().await; 121 | // trace_hex(&*account_data.data); 122 | // log_trace!("~~~ store"); 123 | // } 124 | 125 | if let Some(cache) = &self.cache { 126 | cache.store(reference)?; 127 | } 128 | 129 | let data = AccountDataStore::from(&*reference.account_data.lock()?); 130 | let data_vec = data.try_to_vec()?; 131 | //log_error!("AccountDataStore: {:?}\nVec: {:02x?}", data, data_vec); 132 | //log_trace!("storing: {}",reference.key); 133 | 134 | data.log_trace()?; 135 | //trace_hex(&data_vec); 136 | 137 | fs::write(&self.data_folder.join(reference.key.to_string()), data_vec).await?; 138 | Ok(()) 139 | } 140 | async fn purge(&self, pubkey: &Pubkey) -> Result<()> { 141 | if let Some(cache) = &self.cache { 142 | cache.purge(Some(pubkey))?; 143 | } 144 | 145 | let filename = self.data_folder.join(pubkey.to_string()); 146 | match fs::remove_file(&filename).await { 147 | Ok(_) => Ok(()), 148 | Err(e) => { 149 | log_error!( 150 | "unable to remove file '{}': {}", 151 | filename.into_os_string().into_string()?, 152 | e 153 | ); 154 | Ok(()) 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/transport/loaders.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Client-side container and reference account data loaders 3 | //! 4 | 5 | use futures::future::join_all; 6 | use kaizen::container::Container; 7 | use kaizen::prelude::*; 8 | use kaizen::result::Result; 9 | use wasm_bindgen::prelude::*; 10 | 11 | pub async fn with_loaded_container<'this, C>( 12 | pubkey: Pubkey, 13 | callback: impl Fn(Option>) -> Result<()>, 14 | ) -> Result<()> 15 | where 16 | C: Container<'this, 'this>, 17 | { 18 | if let Some(res) = load_container::(&pubkey).await? { 19 | callback(Some(res))?; 20 | } else { 21 | callback(None)?; 22 | } 23 | 24 | Ok(()) 25 | } 26 | 27 | pub async fn with_reloaded_container<'this, C>( 28 | pubkey: Pubkey, 29 | callback: impl Fn(Option>) -> Result<()>, 30 | ) -> Result<()> 31 | where 32 | C: Container<'this, 'this>, 33 | { 34 | if let Some(res) = reload_container::(&pubkey).await? { 35 | callback(Some(res))?; 36 | } else { 37 | callback(None)?; 38 | } 39 | 40 | Ok(()) 41 | } 42 | 43 | pub async fn load_container<'this, T>( 44 | pubkey: &Pubkey, 45 | ) -> Result>> 46 | where 47 | T: kaizen::container::Container<'this, 'this>, 48 | { 49 | let transport = Transport::global()?; 50 | load_container_with_transport::(&transport, pubkey).await 51 | } 52 | 53 | pub async fn load_containers<'this, T>( 54 | pubkeys: &[Pubkey], 55 | ) -> Result>>>> 56 | where 57 | T: kaizen::container::Container<'this, 'this>, 58 | { 59 | let transport = Transport::global()?; 60 | 61 | let mut lookups = Vec::new(); 62 | for pubkey in pubkeys.iter() { 63 | lookups.push(load_container_with_transport::(&transport, pubkey)); 64 | } 65 | 66 | Ok(join_all(lookups).await) 67 | } 68 | 69 | pub async fn load_container_with_transport<'this, T>( 70 | transport: &Arc, 71 | pubkey: &Pubkey, 72 | ) -> Result>> 73 | where 74 | T: kaizen::container::Container<'this, 'this>, 75 | { 76 | let account_data_reference = match transport.lookup(pubkey).await? { 77 | Some(account_data_reference) => account_data_reference, 78 | None => return Ok(None), 79 | }; 80 | 81 | let container = account_data_reference.try_into_container::()?; 82 | Ok(Some(container)) 83 | } 84 | 85 | pub async fn reload_container<'this, T>( 86 | pubkey: &Pubkey, 87 | ) -> Result>> 88 | where 89 | T: kaizen::container::Container<'this, 'this>, 90 | { 91 | let transport = Transport::global()?; 92 | reload_container_with_transport::(&transport, pubkey).await 93 | } 94 | 95 | pub async fn reload_containers<'this, T>( 96 | pubkeys: &[Pubkey], 97 | ) -> Result>>>> 98 | where 99 | T: kaizen::container::Container<'this, 'this>, 100 | { 101 | let transport = Transport::global()?; 102 | 103 | let mut lookups = Vec::new(); 104 | for pubkey in pubkeys.iter() { 105 | lookups.push(reload_container_with_transport::(&transport, pubkey)); 106 | } 107 | 108 | Ok(join_all(lookups).await) 109 | } 110 | 111 | pub async fn reload_container_with_transport<'this, T>( 112 | transport: &Arc, 113 | pubkey: &Pubkey, 114 | ) -> Result>> 115 | where 116 | T: kaizen::container::Container<'this, 'this>, 117 | { 118 | // log_trace!("... reloading container {}", pubkey); 119 | transport.purge(Some(pubkey))?; 120 | let account_data_reference = match transport.lookup(pubkey).await? { 121 | Some(account_data_reference) => account_data_reference, 122 | None => return Ok(None), 123 | }; 124 | 125 | let container = account_data_reference.try_into_container::()?; 126 | Ok(Some(container)) 127 | } 128 | 129 | // ~ 130 | 131 | pub async fn load_reference(pubkey: &Pubkey) -> Result>> { 132 | let transport = Transport::global()?; 133 | load_reference_with_transport(&transport, pubkey).await 134 | } 135 | 136 | pub async fn load_references( 137 | pubkeys: &[Pubkey], 138 | ) -> Result>>>> { 139 | let transport = Transport::global()?; 140 | 141 | let mut lookups = Vec::new(); 142 | for pubkey in pubkeys.iter() { 143 | lookups.push(load_reference_with_transport(&transport, pubkey)); 144 | } 145 | 146 | Ok(join_all(lookups).await) 147 | } 148 | 149 | pub async fn load_reference_with_transport( 150 | transport: &Arc, 151 | pubkey: &Pubkey, 152 | ) -> Result>> { 153 | transport.lookup(pubkey).await 154 | } 155 | 156 | pub async fn reload_reference(pubkey: &Pubkey) -> Result>> { 157 | let transport = Transport::global()?; 158 | transport.purge(Some(pubkey))?; 159 | load_reference_with_transport(&transport, pubkey).await 160 | } 161 | 162 | pub async fn reload_references( 163 | pubkeys: &[Pubkey], 164 | ) -> Result>>>> { 165 | let transport = Transport::global()?; 166 | 167 | let mut lookups = Vec::new(); 168 | for pubkey in pubkeys.iter() { 169 | transport.purge(Some(pubkey))?; 170 | lookups.push(load_reference_with_transport(&transport, pubkey)); 171 | } 172 | 173 | Ok(join_all(lookups).await) 174 | } 175 | 176 | pub fn purge_reference(pubkey: &Pubkey) -> Result<()> { 177 | let transport = Transport::global()?; 178 | transport.purge(Some(pubkey))?; 179 | Ok(()) 180 | } 181 | 182 | pub fn purge_references(pubkeys: &[Pubkey]) -> Result<()> { 183 | let transport = Transport::global()?; 184 | for pubkey in pubkeys.iter() { 185 | transport.purge(Some(pubkey))?; 186 | } 187 | Ok(()) 188 | } 189 | 190 | #[wasm_bindgen] 191 | pub fn purge_cache(pubkey: &Pubkey) -> Result<()> { 192 | purge_reference(pubkey) 193 | } 194 | -------------------------------------------------------------------------------- /src/emulator/simulator.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Base emulation environment. 3 | //! 4 | use crate::accounts::AccountDescriptorList; 5 | use kaizen::accounts::{AccountData, AccountDataReference}; 6 | use kaizen::builder::{InstructionBuilder, InstructionBuilderConfig}; 7 | use kaizen::context::SimulationHandlerFn; 8 | use kaizen::result::Result; 9 | use kaizen::store; 10 | use solana_program::instruction::Instruction; 11 | use solana_program::pubkey::Pubkey; 12 | use std::str::FromStr; 13 | use std::sync::Arc; 14 | 15 | use super::interface::{EmulatorConfig, EmulatorInterface, ExecutionResponse}; 16 | use super::mockdata::InProcMockData; 17 | use super::Emulator; 18 | use async_trait::async_trait; 19 | 20 | pub struct Simulator { 21 | pub inproc_mock_data: Option, 22 | pub store: Arc, 23 | pub emulator: Arc, 24 | } 25 | 26 | impl Simulator { 27 | pub fn new(store: &Arc) -> Simulator { 28 | let emulator = Arc::new(Emulator::new(store.clone())); 29 | Simulator { 30 | store: store.clone(), 31 | emulator, 32 | inproc_mock_data: None, 33 | } 34 | } 35 | 36 | pub fn try_new_with_store() -> Result { 37 | let store = Arc::new(store::MemoryStore::new_local()?); 38 | let emulator = Arc::new(Emulator::new(store.clone())); 39 | let simulator = Simulator { 40 | store, 41 | emulator, 42 | inproc_mock_data: None, 43 | }; 44 | Ok(simulator) 45 | } 46 | 47 | pub fn try_new_for_testing() -> Result { 48 | let store = Arc::new(store::MemoryStore::new_local()?); 49 | let emulator = Arc::new(Emulator::new(store.clone())); 50 | 51 | let simulator = Simulator { 52 | store, 53 | emulator, 54 | inproc_mock_data: None, 55 | }; 56 | 57 | Ok(simulator) 58 | } 59 | 60 | pub async fn with_mock_accounts( 61 | mut self, 62 | program_id: Pubkey, 63 | authority: Option, 64 | ) -> Result { 65 | let lamports = crate::utils::u64sol_to_lamports(500); 66 | 67 | let authority = match authority { 68 | Some(authority) => authority, 69 | // FIXME should user always supply a pubkey? 70 | None => Pubkey::from_str("42bML5qB3WkMwfa2cosypjUrN7F2PLQm4qhxBdRDyW7f")?, //generate_random_pubkey(); 71 | }; 72 | 73 | let system_account_data = 74 | AccountData::new_static(Pubkey::default(), program_id).with_lamports(lamports); 75 | 76 | self.store 77 | .store(&Arc::new(AccountDataReference::new(system_account_data))) 78 | .await?; 79 | 80 | let authority_account_data = 81 | AccountData::new_static(authority, program_id).with_lamports(lamports); 82 | 83 | self.store 84 | .store(&Arc::new(AccountDataReference::new(authority_account_data))) 85 | .await?; 86 | 87 | let mock_data = InProcMockData::new( 88 | &authority, 89 | // &identity, 90 | &program_id, 91 | ); 92 | 93 | self.inproc_mock_data = Some(mock_data); 94 | Ok(self) 95 | } 96 | 97 | pub fn inproc_mock_data(&self) -> &InProcMockData { 98 | self.inproc_mock_data 99 | .as_ref() 100 | .expect("simulator missing inproc mock account data") 101 | } 102 | 103 | pub fn program_id(&self) -> Pubkey { 104 | self.inproc_mock_data().program_id 105 | } 106 | 107 | pub fn authority(&self) -> Pubkey { 108 | self.inproc_mock_data().authority 109 | } 110 | 111 | pub fn new_instruction_builder_config(&self) -> InstructionBuilderConfig { 112 | let InProcMockData { 113 | program_id, 114 | authority, 115 | } = self.inproc_mock_data(); 116 | InstructionBuilderConfig::new(*program_id).with_authority(authority) 117 | } 118 | 119 | pub fn new_instruction_builder(&self) -> Arc { 120 | let InProcMockData { 121 | program_id, 122 | authority, 123 | } = self.inproc_mock_data(); 124 | 125 | InstructionBuilder::new(program_id, 0, 0u16).with_authority(authority) 126 | } 127 | 128 | pub async fn execute_handler( 129 | &self, 130 | builder: Arc, 131 | handler: SimulationHandlerFn, 132 | ) -> Result<()> { 133 | self.emulator 134 | .clone() 135 | .execute_handler(builder, handler) 136 | .await 137 | } 138 | } 139 | 140 | #[async_trait] 141 | impl EmulatorInterface for Simulator { 142 | async fn lookup(&self, pubkey: &Pubkey) -> Result>> { 143 | match self.emulator.lookup(pubkey).await? { 144 | Some(reference) => { 145 | // IMPORTANT: 146 | // We intentionally return account replicas 147 | // in order to decouple account_data mutexes 148 | // which can create deadlocks if held by client 149 | // while executing programs. 150 | // Update: due to potential of client-side 151 | // deadlocks, client-side ContainerReferences 152 | // now replicate data during try_into_container() call. 153 | Ok(Some(reference.replicate()?)) 154 | } 155 | None => { 156 | return Ok(None); 157 | } 158 | } 159 | } 160 | 161 | async fn execute( 162 | &self, 163 | authority: &Pubkey, 164 | instruction: &Instruction, 165 | ) -> Result { 166 | self.emulator.execute(authority, instruction).await 167 | } 168 | 169 | async fn fund(&self, key: &Pubkey, owner: &Pubkey, lamports: u64) -> Result<()> { 170 | self.emulator.fund(key, owner, lamports).await 171 | } 172 | 173 | async fn list(&self) -> Result { 174 | self.emulator.list().await 175 | } 176 | 177 | async fn configure(&self, config: EmulatorConfig) -> Result<()> { 178 | self.emulator.configure(config).await 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/emulator/stubs.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Emulator interface functions for account creation. 3 | //! 4 | 5 | use kaizen::accounts::*; 6 | use kaizen::address::ProgramAddressData; 7 | use kaizen::error::*; 8 | use kaizen::realloc::account_info_realloc; 9 | use kaizen::result::Result; 10 | use solana_program::pubkey::Pubkey; 11 | use solana_program::sysvar::slot_history::AccountInfo; 12 | use workflow_log::*; 13 | 14 | pub fn allocate_pda<'info>( 15 | payer: &AccountInfo<'info>, 16 | program_id: &Pubkey, 17 | tpl_seeds: &[&[u8]], 18 | tpl_account_info: &AccountInfo<'info>, 19 | space: usize, 20 | lamports: u64, 21 | validate_pda: bool, 22 | ) -> Result<()> { 23 | if space > ACCOUNT_DATA_TEMPLATE_SIZE { 24 | panic!( 25 | "create_pda() account size is too large (current limit is: {ACCOUNT_DATA_TEMPLATE_SIZE} bytes", 26 | ); 27 | } 28 | 29 | // log_trace!("* * * program pda seed: {:?}", tpl_adderss_data.seed); 30 | // let seeds = [user_seed, tpl_adderss_data.seed].concat(); 31 | // let seeds_hex = crate::utils::hex(&seeds[..]); 32 | // log_trace!("* * * program pda seeds:\n{}\n", seeds_hex); 33 | 34 | if validate_pda { 35 | match Pubkey::create_program_address(tpl_seeds, program_id) { 36 | Ok(address) => { 37 | if address != *tpl_account_info.key { 38 | // log_trace!("| pda: PDA ADDRESS MISMATCH {} vs {}", address, tpl_account_info.key); 39 | return Err(error_code!(ErrorCode::PDAAddressMatch)); 40 | } 41 | // log_trace!("| pda: PDA ADDRESS OK"); 42 | } 43 | Err(_e) => { 44 | // log_trace!("| pda: PDA ADDRESS MATCH failure"); 45 | return Err(error_code!(ErrorCode::PDAAddressCreate)); 46 | } 47 | }; 48 | } 49 | 50 | // log_trace!("| pda: account realloc - buffer: {} slice: {} target: {}",buffer_size,tpl_account_info.data_len(),space); 51 | account_info_realloc(tpl_account_info, space, true, true)?; 52 | // log_trace!("+ pda: simulator realloc done"); 53 | 54 | let mut ref_payer_lamports = payer.lamports.borrow_mut(); 55 | let mut payer_lamports = **ref_payer_lamports; 56 | 57 | log_trace!( 58 | "allocate_pda() lamports - need: {} payer has: {}", 59 | lamports, 60 | payer_lamports 61 | ); 62 | if payer_lamports < lamports { 63 | log_trace!( 64 | "allocate_pda() insufficient lamports - need: {} payer has: {}", 65 | lamports, 66 | payer_lamports 67 | ); 68 | return Err(error_code!(ErrorCode::InsufficientAllocBalance)); 69 | } 70 | 71 | payer_lamports = payer_lamports.saturating_sub(lamports); 72 | **ref_payer_lamports = payer_lamports; 73 | 74 | let mut ref_tpl_account_info_lamports = tpl_account_info.lamports.borrow_mut(); 75 | **ref_tpl_account_info_lamports = (**ref_tpl_account_info_lamports).saturating_add(lamports); 76 | 77 | Ok(()) 78 | } 79 | 80 | // FIXME - fix allocate_multiple_pda! 81 | // WARNING: This function is not currently operational! 82 | // pub fn allocate_multiple_pda<'info, 'refs, 'payer_info, 'payer_refs, 'pid>( 83 | pub fn allocate_multiple_pda<'info, 'refs>( 84 | _payer: &AccountInfo<'_>, 85 | _program_id: &Pubkey, 86 | _user_seed: &[u8], 87 | account_templates: &[(&ProgramAddressData, &'refs AccountInfo<'info>)], 88 | // account_templates: &[AccountInfoTemplate<'info, 'refs>], 89 | settings: &[(usize, u64)], 90 | ) -> Result>> { 91 | if account_templates.len() < settings.len() { 92 | log_trace!("======================================================"); 93 | log_trace!( 94 | "Not enough account templates: {} vs settings: {} ...", 95 | account_templates.len(), 96 | settings.len() 97 | ); 98 | log_trace!("======================================================"); 99 | // return Err(Error::ErrorCode(ErrorCode::NotEnoughAccountTemplates)); 100 | return Err(program_error_code!(ErrorCode::NotEnoughAccountTemplates)); 101 | } 102 | 103 | let mut list = Vec::new(); 104 | for idx in 0..settings.len() { 105 | let (space, _lamports) = settings[idx]; 106 | let (_tpl_address_data, tpl_account_info) = account_templates[idx]; 107 | 108 | { 109 | let buffer_size = unsafe { 110 | let ptr = tpl_account_info 111 | .try_borrow_mut_data() 112 | .ok() 113 | .unwrap() 114 | .as_mut_ptr() 115 | .offset(-8) as *mut u64; 116 | *ptr 117 | }; 118 | log_trace!( 119 | "| pda realloc - buffer: {} slice: {} target: {}", 120 | buffer_size, 121 | tpl_account_info.data_len(), 122 | space 123 | ); 124 | } 125 | 126 | log_trace!("{}", style("in allocate_multiple_pda...").white().on_red()); 127 | account_info_realloc(tpl_account_info, space, true, true)?; 128 | 129 | list.push(tpl_account_info); 130 | } 131 | 132 | Ok(list) 133 | } 134 | 135 | pub fn transfer_sol<'info>( 136 | from: &AccountInfo<'info>, 137 | to: &AccountInfo<'info>, 138 | authority: &AccountInfo<'info>, 139 | _system_program_account: &AccountInfo<'info>, 140 | amount: u64, 141 | ) -> Result<()> { 142 | let mut lamports_src = from.lamports.borrow_mut(); 143 | if **lamports_src < amount { 144 | return Err(program_error_code!(ErrorCode::InsufficientBalance)); 145 | } 146 | 147 | let mut lamports_dest = to.lamports.borrow_mut(); 148 | **lamports_dest = lamports_dest.saturating_add(amount); 149 | **lamports_src = lamports_src.saturating_sub(amount); 150 | 151 | // TODO: validate authority authority 152 | log_trace!( 153 | "\ntransfer_sol:\n\tfrom: {}\n\tto: {}\n\tauthority: {}\n\tamount: {}\n\n", 154 | from.key, 155 | to.key, 156 | authority.key, 157 | amount 158 | ); 159 | 160 | Ok(()) 161 | } 162 | 163 | pub fn transfer_spl<'info>( 164 | token_program: &AccountInfo<'info>, 165 | from: &AccountInfo<'info>, 166 | to: &AccountInfo<'info>, 167 | authority: &AccountInfo<'info>, 168 | amount: u64, 169 | _signers: &[&[&[u8]]], 170 | ) -> Result<()> { 171 | log_trace!( 172 | "\n--: transfer_tokens:\nprogram: {}\n\tfrom: {}\n\tto: {}\n\tauthority: {}\n\tamount: {}\n\n", 173 | token_program.key, 174 | from.key, 175 | to.key, 176 | authority.key, 177 | amount 178 | ); 179 | 180 | Ok(()) 181 | } 182 | -------------------------------------------------------------------------------- /macros/src/client.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Ident, Span}; 3 | use quote::{quote, ToTokens}; 4 | use std::convert::Into; 5 | use syn::{ 6 | parse::{Parse, ParseStream}, 7 | parse_macro_input, 8 | punctuated::Punctuated, 9 | Error, Expr, ExprPath, PathArguments, PathSegment, Result, Token, 10 | }; 11 | 12 | #[derive(Debug)] 13 | struct Execution { 14 | target_primitive_path: ExprPath, 15 | interface_dispatch_method: ExprPath, 16 | client_struct_decl: TokenStream, 17 | client_lifetimes: Option, 18 | } 19 | 20 | impl Parse for Execution { 21 | fn parse(input: ParseStream) -> Result { 22 | let parsed = Punctuated::::parse_terminated(input).unwrap(); 23 | if parsed.len() != 2 { 24 | return Err(Error::new_spanned( 25 | parsed.clone(), 26 | format!("usage: declare_handlers!(,[, ..])"), 27 | )); 28 | } 29 | 30 | let handler_struct_expr = parsed.first().unwrap().clone(); 31 | let target_primitive_path = match handler_struct_expr { 32 | Expr::Path(path) => path, 33 | _ => { 34 | return Err(Error::new_spanned( 35 | handler_struct_expr.clone(), 36 | format!("first argument should be a struct name (and an optional lifetime)"), 37 | )); 38 | } 39 | }; 40 | 41 | let mut interface_dispatch_method = target_primitive_path.clone(); 42 | 43 | let ident = Ident::new("program", Span::call_site()); 44 | let path_segment = PathSegment { 45 | ident, 46 | arguments: PathArguments::None, 47 | }; 48 | interface_dispatch_method 49 | .path 50 | .segments 51 | .push_punct(Token![::](Span::call_site())); 52 | interface_dispatch_method 53 | .path 54 | .segments 55 | .push_value(path_segment); 56 | 57 | let client_struct_expr = parsed.last().unwrap().clone(); 58 | let mut client_struct = match client_struct_expr { 59 | Expr::Path(path) => path, 60 | _ => { 61 | return Err(Error::new_spanned( 62 | client_struct_expr.clone(), 63 | format!("last argument should be a struct name (and an optional lifetime)"), 64 | )); 65 | } 66 | }; 67 | 68 | let mut target = client_struct.path.segments.last_mut().unwrap(); 69 | let client_lifetimes = match &target.arguments { 70 | PathArguments::AngleBracketed(params) => { 71 | let mut ts = proc_macro2::TokenStream::new(); 72 | params.args.clone().to_tokens(&mut ts); 73 | let lifetimes = ts.to_string(); 74 | target.arguments = PathArguments::None; 75 | Some(lifetimes) 76 | } 77 | _ => None, 78 | }; 79 | 80 | let mut ts = proc_macro2::TokenStream::new(); 81 | client_struct.to_tokens(&mut ts); 82 | let client_struct_decl: TokenStream = ts.into(); 83 | 84 | let execution = Execution { 85 | target_primitive_path, 86 | interface_dispatch_method, 87 | client_struct_decl, 88 | client_lifetimes, 89 | }; 90 | Ok(execution) 91 | } 92 | } 93 | 94 | // #[proc_macro] 95 | pub fn declare_client(input: TokenStream) -> TokenStream { 96 | let execution = parse_macro_input!(input as Execution); 97 | 98 | let target_primitive_path = execution.target_primitive_path; 99 | let interface_dispatch_method = execution.interface_dispatch_method; 100 | let client_struct_name = execution.client_struct_decl.to_string(); 101 | 102 | let impl_wasm_str = match &execution.client_lifetimes { 103 | Some(lifetimes) => format!( 104 | "impl<{}> {}<{}>", 105 | lifetimes, execution.client_struct_decl, lifetimes 106 | ), 107 | None => format!("impl {}", execution.client_struct_decl), 108 | }; 109 | let impl_wasm_ts: proc_macro2::TokenStream = impl_wasm_str.parse().unwrap(); 110 | 111 | let impl_client_str = match &execution.client_lifetimes { 112 | Some(lifetimes) => format!( 113 | "impl Client {}<{}>", 114 | execution.client_struct_decl, lifetimes 115 | ), 116 | None => format!("impl Client for {}", execution.client_struct_decl), 117 | }; 118 | let impl_client_ts: proc_macro2::TokenStream = impl_client_str.parse().unwrap(); 119 | 120 | let out = quote! { 121 | 122 | #impl_client_ts { 123 | #[cfg(not(target_os = "solana"))] 124 | fn handler_id(handler_fn: HandlerFn) -> usize { 125 | #target_primitive_path::INTERFACE_HANDLERS.iter() 126 | .position(|&hfn| hfn as HandlerFnCPtr == handler_fn as HandlerFnCPtr ) 127 | .expect("invalid primitive handler") 128 | } 129 | 130 | #[cfg(not(target_os = "solana"))] 131 | fn execution_context_for(handler: HandlerFn) -> Arc { 132 | let interface_id = crate::interface_id(#interface_dispatch_method); 133 | let handler_id = Self::handler_id(handler); 134 | 135 | InstructionBuilder::new( 136 | // program_id, 137 | &crate::program_id(), 138 | interface_id, 139 | handler_id as u16 140 | ) 141 | } 142 | } 143 | 144 | #impl_wasm_ts { 145 | 146 | #[cfg(not(target_os = "solana"))] 147 | pub fn bind() -> &'static str { #client_struct_name } 148 | 149 | #[cfg(not(target_os = "solana"))] 150 | pub async fn execute( 151 | instruction : solana_program::instruction::Instruction 152 | ) -> kaizen::result::Result<()> { 153 | use kaizen::transport::Interface; 154 | let transport = kaizen::transport::Transport::global()?; 155 | transport.execute(&instruction).await?; 156 | Ok(()) 157 | } 158 | 159 | #[cfg(not(target_os = "solana"))] 160 | pub async fn execute_with_transport( 161 | transport : &Arc, 162 | instruction : solana_program::instruction::Instruction 163 | ) -> kaizen::result::Result<()> { 164 | use kaizen::transport::Interface; 165 | transport.execute(&instruction).await?; 166 | Ok(()) 167 | } 168 | } 169 | }; 170 | 171 | out.into() 172 | } 173 | -------------------------------------------------------------------------------- /src/transport/lookup.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Account lookup synchronizer combining multiple pending async lookups for the same account into a single future. 3 | //! 4 | 5 | use crate::result::Result; 6 | use ahash::AHashMap; 7 | use async_std::sync::Mutex; 8 | use std::cmp::Eq; 9 | use std::fmt::Display; 10 | use std::hash::Hash; 11 | use std::sync::atomic::{AtomicUsize, Ordering}; 12 | use std::sync::Arc; 13 | use workflow_core::channel::*; 14 | pub type LookupResult = Result>; 15 | pub enum RequestType { 16 | New(Receiver>), 17 | Pending(Receiver>), 18 | } 19 | 20 | pub type SenderList = Vec>>; 21 | 22 | pub struct LookupHandler { 23 | pub map: Arc>>>, 24 | pending: AtomicUsize, 25 | } 26 | 27 | impl Default for LookupHandler 28 | where 29 | T: Clone, 30 | K: Clone + Eq + Hash + Display, 31 | { 32 | fn default() -> Self { 33 | LookupHandler::::new() 34 | } 35 | } 36 | 37 | impl LookupHandler 38 | where 39 | T: Clone, 40 | K: Clone + Eq + Hash + Display, 41 | { 42 | pub fn new() -> Self { 43 | LookupHandler { 44 | map: Arc::new(Mutex::new(AHashMap::new())), 45 | pending: AtomicUsize::new(0), 46 | } 47 | } 48 | 49 | pub fn pending(&self) -> usize { 50 | self.pending.load(Ordering::SeqCst) 51 | } 52 | 53 | pub async fn queue(&self, key: &K) -> RequestType { 54 | let mut pending = self.map.lock().await; 55 | let (sender, receiver) = oneshot::>(); 56 | 57 | if let Some(list) = pending.get_mut(key) { 58 | list.push(sender); 59 | RequestType::Pending(receiver) 60 | } else { 61 | let list = vec![sender]; 62 | pending.insert(key.clone(), list); 63 | self.pending.fetch_add(1, Ordering::Relaxed); 64 | RequestType::New(receiver) 65 | } 66 | } 67 | 68 | pub async fn complete(&self, key: &K, result: LookupResult) { 69 | let mut pending = self.map.lock().await; 70 | 71 | if let Some(list) = pending.remove(key) { 72 | self.pending.fetch_sub(1, Ordering::Relaxed); 73 | for sender in list { 74 | sender 75 | .send(result.clone()) 76 | .await 77 | .expect("Unable to complete lookup result"); 78 | } 79 | } else { 80 | panic!("Lookup handler failure while processing account `{key}`") 81 | } 82 | } 83 | } 84 | 85 | #[cfg(not(target_os = "solana"))] 86 | #[cfg(any(test, feature = "test"))] 87 | mod tests { 88 | use super::LookupHandler; 89 | use super::RequestType; 90 | use std::sync::Arc; 91 | use std::sync::Mutex; 92 | use std::time::Duration; 93 | 94 | use super::Result; 95 | use ahash::AHashMap; 96 | use async_std::task::sleep; 97 | use futures::join; 98 | use wasm_bindgen::prelude::*; 99 | use workflow_log::log_trace; 100 | 101 | #[derive(Debug, Eq, PartialEq)] 102 | enum RequestTypeTest { 103 | New = 0, 104 | Pending = 1, 105 | } 106 | 107 | struct LookupHandlerTest { 108 | pub lookup_handler: LookupHandler, 109 | pub map: Arc>>, 110 | pub request_types: Arc>>, 111 | } 112 | 113 | impl LookupHandlerTest { 114 | pub fn new() -> Self { 115 | Self { 116 | lookup_handler: LookupHandler::new(), 117 | map: Arc::new(Mutex::new(AHashMap::new())), 118 | request_types: Arc::new(Mutex::new(Vec::new())), 119 | } 120 | } 121 | 122 | pub fn insert(self: &Arc, key: u32, value: u32) -> Result<()> { 123 | let mut map = self.map.lock()?; 124 | map.insert(key, value); 125 | Ok(()) 126 | } 127 | 128 | pub async fn lookup_remote_impl(self: &Arc, key: &u32) -> Result> { 129 | log_trace!("[lh] lookup sleep..."); 130 | sleep(Duration::from_millis(100)).await; 131 | log_trace!("[lh] lookup wake..."); 132 | let map = self.map.lock()?; 133 | Ok(map.get(&key).cloned()) 134 | } 135 | 136 | pub async fn lookup_handler_request(self: &Arc, key: &u32) -> Result> { 137 | let request_type = self.lookup_handler.queue(key).await; 138 | match request_type { 139 | RequestType::New(receiver) => { 140 | self.request_types 141 | .lock() 142 | .unwrap() 143 | .push(RequestTypeTest::New); 144 | log_trace!("[lh] new request"); 145 | let response = self.lookup_remote_impl(key).await; 146 | log_trace!("[lh] completing initial request"); 147 | self.lookup_handler.complete(key, response).await; 148 | receiver.recv().await? 149 | } 150 | RequestType::Pending(receiver) => { 151 | self.request_types 152 | .lock() 153 | .unwrap() 154 | .push(RequestTypeTest::Pending); 155 | log_trace!("[lh] pending request"); 156 | receiver.recv().await? 157 | } 158 | } 159 | } 160 | } 161 | 162 | #[wasm_bindgen] 163 | pub async fn lookup_handler_test() -> Result<()> { 164 | let lht = Arc::new(LookupHandlerTest::new()); 165 | lht.insert(0xc0fee, 0xdecaf)?; 166 | 167 | let v0 = lht.lookup_handler_request(&0xc0fee); 168 | let v1 = lht.lookup_handler_request(&0xc0fee); 169 | let v2 = lht.lookup_handler_request(&0xc0fee); 170 | let f = join!(v0, v1, v2); 171 | 172 | log_trace!("[lh] results: {:?}", f); 173 | let f = ( 174 | f.0.unwrap().unwrap(), 175 | f.1.unwrap().unwrap(), 176 | f.2.unwrap().unwrap(), 177 | ); 178 | assert_eq!(f, (0xdecaf, 0xdecaf, 0xdecaf)); 179 | 180 | let request_types = lht.request_types.lock().unwrap(); 181 | log_trace!("[lh] request types: {:?}", request_types); 182 | assert_eq!( 183 | request_types[..], 184 | [ 185 | RequestTypeTest::New, 186 | RequestTypeTest::Pending, 187 | RequestTypeTest::Pending 188 | ] 189 | ); 190 | log_trace!("all looks good ... 😎"); 191 | 192 | Ok(()) 193 | } 194 | 195 | #[cfg(not(any(target_arch = "wasm32", target_os = "solana")))] 196 | #[cfg(test)] 197 | mod tests { 198 | use super::*; 199 | 200 | #[async_std::test] 201 | pub async fn lookup_handler_test() -> Result<()> { 202 | super::lookup_handler_test().await 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/emulator/server.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Kaizen Emulator RPC server 3 | //! 4 | #![allow(unused_imports)] 5 | use crate::accounts::AccountDataStore; 6 | use async_trait::async_trait; 7 | use borsh::{BorshDeserialize, BorshSerialize}; 8 | use kaizen::cache::Cache; 9 | use kaizen::emulator::interface::EmulatorInterface; 10 | use kaizen::emulator::rpc::*; 11 | use kaizen::result::Result; 12 | use kaizen::store::FileStore; 13 | use solana_program::instruction::Instruction; 14 | use solana_program::pubkey::Pubkey; 15 | use std::sync::Arc; 16 | use workflow_rpc::result::ServerResult; 17 | use workflow_rpc::server::prelude::*; 18 | use workflow_rpc::server::RpcHandler; 19 | use workflow_rpc::server::ServerError; 20 | 21 | use super::interface::EmulatorConfig; 22 | use super::Emulator; 23 | use workflow_log::*; 24 | 25 | use thiserror::Error; 26 | #[derive(Debug, Error)] 27 | pub enum Error { 28 | #[error("error")] 29 | SomeError, 30 | } 31 | 32 | impl From for ServerError { 33 | fn from(err: Error) -> Self { 34 | ServerError::Text(err.to_string()) 35 | } 36 | } 37 | 38 | const DEFAULT_CAPACITY: u64 = 1024u64 * 1024u64 * 256u64; // 256 megabytes 39 | 40 | #[derive(Clone)] 41 | pub struct Server { 42 | pub emulator: Arc, 43 | } 44 | 45 | impl Server { 46 | // #[allow(dead_code)] 47 | pub fn try_new() -> Result { 48 | let cache = Arc::new(Cache::new_with_capacity(DEFAULT_CAPACITY)); 49 | let store = Arc::new(FileStore::try_new_with_cache(cache)?); 50 | let emulator = Arc::new(Emulator::new(store)); 51 | 52 | let server = Server { emulator }; 53 | 54 | Ok(server) 55 | } 56 | 57 | pub async fn init(&self) -> Result<()> { 58 | self.emulator.init().await 59 | } 60 | 61 | pub fn interface(self: Arc) -> Interface, (), EmulatorOps> { 62 | let mut interface = Interface::, (), EmulatorOps>::new(self); 63 | 64 | interface.method( 65 | EmulatorOps::Lookup, 66 | method!( 67 | |server: Arc, _connection, req: LookupReq| async move { 68 | let reference = server.emulator.clone().lookup(&req.pubkey).await?; 69 | let resp = match reference { 70 | Some(reference) => { 71 | let account_data_store = 72 | AccountDataStore::from(&*reference.account_data.lock()?); 73 | LookupResp { 74 | account_data_store: Some(account_data_store), 75 | } 76 | } 77 | None => LookupResp { 78 | account_data_store: None, 79 | }, 80 | }; 81 | Ok(resp) 82 | } 83 | ), 84 | ); 85 | 86 | interface.method( 87 | EmulatorOps::Execute, 88 | method!( 89 | |server: Arc, _connection, req: ExecuteReq| async move { 90 | let (authority, instruction): (Pubkey, Instruction) = req.into(); 91 | let resp = server.emulator.execute(&authority, &instruction).await?; 92 | Ok(resp) 93 | } 94 | ), 95 | ); 96 | 97 | interface.method( 98 | EmulatorOps::Fund, 99 | method!( 100 | |server: Arc, _connection, req: FundReq| async move { 101 | server 102 | .emulator 103 | .fund(&req.key, &req.owner, req.lamports) 104 | .await?; 105 | Ok(()) 106 | } 107 | ), 108 | ); 109 | 110 | interface.method( 111 | EmulatorOps::List, 112 | method!(|server: Arc, _connection, _req: ()| async move { 113 | let resp = server.emulator.list().await?; 114 | Ok(resp) 115 | }), 116 | ); 117 | 118 | interface.method( 119 | EmulatorOps::Configure, 120 | method!( 121 | |_server: Arc, _connection, _req: EmulatorConfig| async move { Ok(()) } 122 | ), 123 | ); 124 | 125 | interface 126 | } 127 | } 128 | 129 | #[async_trait] 130 | // impl RpcHandlerBorsh for Server 131 | // impl RpcHandler for Server { 132 | impl RpcHandler for Server { 133 | type Context = (); 134 | 135 | async fn handshake( 136 | self: Arc, 137 | _peer: &SocketAddr, 138 | _sender: &mut WebSocketSender, 139 | _receiver: &mut WebSocketReceiver, 140 | _messenger: Arc, 141 | ) -> WebSocketResult { 142 | Ok(()) 143 | } 144 | /* 145 | async fn handle_request(self: Arc, op: EmulatorOps, data: &[u8]) -> RpcResult { 146 | match op { 147 | EmulatorOps::Lookup => { 148 | let req = LookupReq::try_from_slice(data)?; 149 | let reference = self.emulator.clone().lookup(&req.pubkey).await?; 150 | let resp = match reference { 151 | Some(reference) => { 152 | let account_data_store = 153 | AccountDataStore::from(&*reference.account_data.lock()?); 154 | LookupResp { 155 | account_data_store: Some(account_data_store), 156 | } 157 | } 158 | None => LookupResp { 159 | account_data_store: None, 160 | }, 161 | }; 162 | Ok(resp.try_to_vec()?) 163 | } 164 | EmulatorOps::Execute => { 165 | let req = ExecuteReq::try_from_slice(data)?; 166 | let (authority, instruction): (Pubkey, Instruction) = req.into(); 167 | let resp = self.emulator.execute(&authority, &instruction).await?; 168 | Ok(resp.try_to_vec()?) 169 | } 170 | EmulatorOps::Fund => { 171 | let req = FundReq::try_from_slice(data)?; 172 | self.emulator 173 | .fund(&req.key, &req.owner, req.lamports) 174 | .await?; 175 | log_trace!("fundinng done..."); 176 | Ok(().try_to_vec()?) 177 | } 178 | EmulatorOps::List => { 179 | let resp = self.emulator.list().await?; 180 | Ok(resp.try_to_vec()?) 181 | } 182 | EmulatorOps::Configure => { 183 | let _config = EmulatorConfig::try_from_slice(data)?; 184 | Ok(().try_to_vec()?) 185 | } 186 | } 187 | } 188 | */ 189 | } 190 | -------------------------------------------------------------------------------- /src/tokens.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! SPL Token data API (under development) 3 | //! 4 | use std::str::FromStr; 5 | use crate::{trace, transport::Transport}; 6 | use solana_program::pubkey::Pubkey; 7 | use metaplex_meta_decoder::{Metadata, borsh, get_metadata_pda }; 8 | use crate::result::Result; 9 | use crate::error::*; 10 | use std::collections::HashMap; 11 | use serde_json; 12 | use serde::{Deserialize, Serialize}; 13 | use crate::transport::Interface; 14 | 15 | const TOKEN_BYTES:&str = include_str!("../../root/tokens.json"); 16 | 17 | static mut TOKENS : Option = None; 18 | 19 | 20 | #[derive(Debug, Deserialize, Serialize)] 21 | pub struct Token{ 22 | #[serde(alias = "a", rename(serialize="a"))] 23 | pub address:String, 24 | #[serde(alias = "chainId", rename(serialize="c", deserialize="c"))] 25 | pub chain_id:usize, 26 | #[serde(alias = "d", rename(serialize="d"))] 27 | pub decimals:usize, 28 | #[serde(alias = "n", rename(serialize="n"))] 29 | pub name:String, 30 | #[serde(alias = "s", rename(serialize="s"))] 31 | pub symbol:String, 32 | #[serde(alias = "logoURI", rename(serialize="l", deserialize="l"))] 33 | pub logo_uri:String, 34 | 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub tags:Option>, 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub extensions:Option> 39 | } 40 | 41 | impl std::fmt::Display for Token{ 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 43 | write!(f, 44 | "Token:({}\t{:10}\t{})", 45 | self.address, self.symbol, self.name 46 | ) 47 | } 48 | } 49 | 50 | #[derive(Debug, Deserialize, Serialize)] 51 | pub struct Tokens{ 52 | #[serde(alias = "tokens")] 53 | pub list:Vec 54 | } 55 | 56 | impl std::fmt::Display for Tokens{ 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { 58 | write!(f, "Tokens:\n")?; 59 | for token in &self.list{ 60 | write!(f, "{}\n", token)?; 61 | } 62 | Ok(()) 63 | } 64 | } 65 | 66 | #[derive(Debug, Clone)] 67 | pub struct TokenInfo{ 68 | pub address:Pubkey, 69 | pub name:String, 70 | pub symbol:String, 71 | pub logo_uri:String 72 | } 73 | 74 | impl TokenInfo{ 75 | pub fn new(address:Pubkey, name:String, symbol:String, logo_uri:String)->Self{ 76 | Self { address, name, symbol, logo_uri } 77 | } 78 | } 79 | 80 | #[cfg(target_arch = "wasm32")] 81 | impl TryFrom for wasm_bindgen::JsValue{ 82 | type Error = Error; 83 | fn try_from(info: TokenInfo) -> std::result::Result { 84 | let obj = js_sys::Object::new(); 85 | js_sys::Reflect::set(&obj, &"address".into(), &info.address.to_string().into())?; 86 | js_sys::Reflect::set(&obj, &"name".into(), &info.name.into())?; 87 | js_sys::Reflect::set(&obj, &"symbol".into(), &info.symbol.into())?; 88 | js_sys::Reflect::set(&obj, &"logoURI".into(), &info.logo_uri.into())?; 89 | 90 | Ok(obj.into()) 91 | } 92 | } 93 | 94 | pub fn get_tokens_list()->std::result::Result<&'static Tokens, serde_json::Error>{ 95 | let tokens:&Tokens = unsafe{ 96 | if let Some(_tokens) = &TOKENS{ 97 | TOKENS.as_ref().unwrap() 98 | }else{ 99 | let tokens:Tokens = serde_json::from_str(TOKEN_BYTES)?; 100 | TOKENS = Some(tokens); 101 | TOKENS.as_ref().unwrap() 102 | } 103 | }; 104 | 105 | Ok(tokens) 106 | } 107 | 108 | pub fn get_tokens()->Vec{ 109 | let mut pubkeys:Vec = Vec::new(); 110 | 111 | pubkeys.push(Pubkey::from_str("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB").unwrap()); 112 | pubkeys.push(Pubkey::from_str("4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R").unwrap()); 113 | pubkeys.push(Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap()); 114 | pubkeys.push(Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap()); 115 | pubkeys.push(Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap()); 116 | pubkeys.push(Pubkey::from_str("9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A").unwrap()); 117 | 118 | pubkeys 119 | } 120 | 121 | 122 | pub async fn get_tokens_info(keys:Vec)->Result>{ 123 | let mut list:Vec = vec![]; 124 | 125 | let transport = Transport::global()?; 126 | 127 | 128 | for pubkey in keys{ 129 | log_trace!("get_metadata_pda for: {:?} .....", pubkey); 130 | let metakey = get_metadata_pda(pubkey); 131 | let account_data = match transport.clone().lookup(&metakey).await { 132 | Ok(account_data) => account_data.unwrap(),//.read().await, 133 | Err(err) => { 134 | let error = format!("tokens::get_tokens_info() error in Transport::get_account_data() while fetching {}: err:{:?}", pubkey.to_string(), err); 135 | //return Err(JsValue::from(error)); 136 | log_trace!("error: {}", error); 137 | continue; 138 | } 139 | }; 140 | 141 | let account_data = account_data.write().await; 142 | 143 | let meta:Metadata = match borsh::BorshDeserialize::deserialize(&mut account_data.data.as_slice()){ 144 | Ok(meta)=>{ 145 | meta 146 | } 147 | Err(err)=>{ 148 | return Err(error!("meta_deser:error: {:?}", err).into()) 149 | } 150 | }; 151 | 152 | log_trace!("meta: {:#?}", meta); 153 | 154 | let name = meta.data.name.replace("\x00", ""); 155 | let symbol = meta.data.symbol.replace("\x00", ""); 156 | let uri = meta.data.uri.replace("\x00", ""); 157 | 158 | log_trace!("meta.data.name: {}", name); 159 | log_trace!("meta.data.symbol: {}", symbol); 160 | log_trace!("meta.data.uri: {}", uri); 161 | 162 | let logo_uri = format!("https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/{}/logo.png", pubkey.to_string()); 163 | list.push( 164 | TokenInfo::new( 165 | pubkey, 166 | name, 167 | symbol, 168 | logo_uri 169 | ) 170 | ); 171 | } 172 | 173 | Ok(list) 174 | } 175 | 176 | #[cfg(target_arch = "wasm32")] 177 | pub async fn get_tokens_info_array(keys:Vec)->Result{ 178 | 179 | let infos = get_tokens_info(keys).await?; 180 | let list = js_sys::Array::new(); 181 | for info in infos{ 182 | match info.try_into(){ 183 | Ok(obj)=>{ 184 | list.push(&obj); 185 | }, 186 | Err(err)=>{ 187 | log_trace!("tokens::get_tokens_info_array(), error in parsing info:{:?}", err); 188 | } 189 | }; 190 | 191 | } 192 | 193 | Ok(list) 194 | } 195 | 196 | #[cfg(not(target_arch = "wasm32"))] 197 | pub async fn get_tokens_info_array(keys:Vec)->Result>{ 198 | Ok(get_tokens_info(keys).await?) 199 | } 200 | -------------------------------------------------------------------------------- /src/solana.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Solana program interface functions for account creation 3 | //! 4 | 5 | use crate::address::ProgramAddressData; 6 | use crate::result::Result; 7 | use crate::{error::*, error_code, program_error}; 8 | //use anchor_spl::token::{self, Transfer}; 9 | use solana_program::account_info::AccountInfo; 10 | use solana_program::program::{invoke, invoke_signed}; 11 | use solana_program::pubkey::Pubkey; 12 | use solana_program::system_instruction::create_account; 13 | use solana_program::{msg, system_instruction}; 14 | 15 | pub fn allocate_pda<'info, 'refs, 'payer, 'pid>( 16 | payer: &'payer AccountInfo<'info>, 17 | program_id: &'pid Pubkey, 18 | tpl_seeds: &[&[u8]], 19 | tpl_account_info: &'refs AccountInfo<'info>, 20 | space: usize, 21 | lamports: u64, 22 | validate_pda: bool, 23 | ) -> Result<&'refs AccountInfo<'info>> { 24 | // msg!("| pda: inside solana allocate_pda()"); 25 | // msg!("| pda: executing create_program_address()"); 26 | if validate_pda { 27 | match Pubkey::create_program_address(tpl_seeds, &program_id) { 28 | Ok(address) => { 29 | if address != *tpl_account_info.key { 30 | // msg!("| pda: PDA ADDRESS MISMATCH {} vs {}", address, tpl_account_info.key); 31 | return Err(error_code!(ErrorCode::PDAAddressMatch)); 32 | } 33 | // msg!("| pda: PDA ADDRESS OK"); 34 | } 35 | Err(_e) => { 36 | // msg!("| pda: PDA ADDRESS MATCH failure"); 37 | //TODO handle this pubkey error 38 | return Err(error_code!(ErrorCode::PDAAddressMatch)); 39 | } 40 | }; 41 | } 42 | // msg!("creating: {:?}", tpl_account_info.key); 43 | // msg!("payer.key: {:?}", payer.key); 44 | // msg!("program_id: {:?}", program_id); 45 | // msg!("system_program id: {:?}", solana_program::system_program::id()); 46 | // msg!("seed: {:?}", tpl_address_data.seed); 47 | // msg!("seed_suffix: {:?}", seed_suffix); 48 | // msg!("info.bump: {:?}", tpl_address_data.bump); 49 | let ins = create_account( 50 | payer.key, 51 | tpl_account_info.key, 52 | lamports, 53 | space as u64, 54 | // info.space, 55 | program_id, 56 | ); 57 | msg!("system ins : {:?}", ins); 58 | let result = invoke_signed( 59 | &ins, 60 | &[payer.clone(), tpl_account_info.clone()], 61 | &[tpl_seeds], 62 | ); 63 | // msg!("invoke_signed:result: {:?}", result); 64 | match result { 65 | Ok(_r) => Ok(tpl_account_info), 66 | Err(e) => { 67 | // msg!("allocate_pda:AllocatorError"); 68 | return Err(program_error!(e)); 69 | } 70 | } 71 | } 72 | 73 | pub fn allocate_multiple_pda<'info, 'refs, 'payer, 'pid, 'instr>( 74 | payer: &'payer AccountInfo<'info>, 75 | program_id: &'pid Pubkey, 76 | user_seed: &[u8], 77 | account_templates: &[(&ProgramAddressData, &'refs AccountInfo<'info>)], 78 | settings: &[(usize, u64)], 79 | ) -> Result>> { 80 | let mut vec: Vec<&AccountInfo<'info>> = Vec::new(); 81 | for idx in 0..settings.len() { 82 | let (tpl_address_data, tpl_account_info) = account_templates[idx]; 83 | let (space, lamports) = settings[idx]; // as u64; 84 | //let program_id = ctx.program_id; 85 | // let seed = &tpl_address_data.seed; 86 | // let seed_suffix = &tpl_address_data.seed_suffix; 87 | // let seed_suffix = &seed_suffix_bytes; 88 | //let seed = b"acc-5"; 89 | match Pubkey::create_program_address(&[user_seed, tpl_address_data.seed], &program_id) { 90 | Ok(address) => { 91 | if &address != tpl_account_info.key { 92 | return Err(error_code!(ErrorCode::PDAAddressMatch)); 93 | } 94 | } 95 | Err(_e) => { 96 | //TODO handle this pubkey error 97 | return Err(error_code!(ErrorCode::PDAAddressMatch)); 98 | } 99 | }; 100 | msg!("creating: {:?}", tpl_account_info.key); 101 | msg!("payer.key: {:?}", payer.key); 102 | msg!("program_id: {:?}", program_id); 103 | msg!("seed: {:?}", tpl_address_data.seed); 104 | // msg!("seed_suffix: {:?}", seed_suffix); 105 | // msg!("info.bump: {:?}", tpl_address_data.bump); 106 | let ins = create_account( 107 | payer.key, 108 | tpl_account_info.key, 109 | lamports, 110 | space as u64, 111 | // info.space, 112 | program_id, 113 | ); 114 | msg!("system ins : {:?}", ins); 115 | let result = invoke_signed( 116 | &ins, 117 | &[payer.clone(), tpl_account_info.clone()], 118 | // A slice of seed slices, each seed slice being the set 119 | // of seeds used to generate one of the PDAs required by the 120 | // callee program, the final seed being a single-element slice 121 | // containing the `u8` bump seed. 122 | &[&[ 123 | user_seed, 124 | tpl_address_data.seed, 125 | // seed_suffix, 126 | //payer.key.as_ref(), 127 | // &[tpl_address_data.bump] 128 | ]], 129 | ); 130 | msg!("invoke_signed:result: {:?}", result); 131 | match result { 132 | Ok(_r) => { 133 | vec.push(tpl_account_info); //.clone()); 134 | } 135 | Err(e) => { 136 | // msg!("allocate_multiple_pda:AllocatorError"); 137 | return Err(program_error!(e)); 138 | } 139 | }; 140 | } 141 | 142 | Ok(vec) 143 | } 144 | 145 | pub fn transfer_sol<'info>( 146 | source: &AccountInfo<'info>, 147 | destination: &AccountInfo<'info>, 148 | _authority: &AccountInfo<'info>, // TODO: is this needed? 149 | system_program_account: &AccountInfo<'info>, 150 | lamports: u64, 151 | ) -> Result<()> { 152 | msg!( 153 | "transfer_sol:sol transfering from {} to: {}", 154 | source.key, 155 | destination.key 156 | ); 157 | //let signers : &[&[&[u8]]] = &[]; 158 | 159 | let ix = system_instruction::transfer(source.key, destination.key, lamports); 160 | 161 | let result = invoke( 162 | &ix, 163 | //signers, 164 | &[ 165 | source.clone(), 166 | destination.clone(), 167 | system_program_account.clone(), 168 | ], 169 | ); 170 | 171 | match result { 172 | Ok(res) => { 173 | msg!("invoke():success: {:?}", res); 174 | } 175 | Err(err) => { 176 | msg!("invoke():err: {:?}", err); 177 | return Err(err.into()); 178 | } 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | pub fn transfer_spl<'info>( 185 | token_program: &AccountInfo<'info>, 186 | source: &AccountInfo<'info>, 187 | destination: &AccountInfo<'info>, 188 | authority: &AccountInfo<'info>, 189 | amount: u64, 190 | signers: &[&[&[u8]]], 191 | ) -> Result<()> { 192 | let ix = spl_token::instruction::transfer( 193 | token_program.key, 194 | source.key, 195 | destination.key, 196 | authority.key, 197 | &[&authority.key], 198 | amount, 199 | )?; 200 | invoke_signed( 201 | &ix, 202 | &[ 203 | source.clone(), 204 | destination.clone(), 205 | authority.clone(), 206 | token_program.clone(), 207 | ], 208 | signers, 209 | )?; 210 | 211 | Ok(()) 212 | } 213 | -------------------------------------------------------------------------------- /macros/src/program.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Ident, Span}; 3 | use quote::quote; 4 | use std::convert::Into; 5 | use syn::{ 6 | parse::{Parse, ParseStream}, 7 | parse_macro_input, 8 | punctuated::Punctuated, 9 | Error, Expr, ExprArray, ExprLit, Lit, PathArguments, PathSegment, Result, Token, 10 | }; 11 | 12 | #[derive(Debug)] 13 | struct Program { 14 | program_id_string: String, 15 | program_name: ExprLit, 16 | program_id: ExprLit, 17 | primitive_handlers: ExprArray, 18 | } 19 | 20 | impl Parse for Program { 21 | fn parse(input: ParseStream) -> Result { 22 | let parsed = Punctuated::::parse_terminated(input).unwrap(); 23 | if parsed.len() != 3 { 24 | return Err(Error::new_spanned( 25 | parsed, 26 | format!("usage: declare_handlers!(,,[, ..])") 27 | )); 28 | } 29 | 30 | let mut iter = parsed.iter(); 31 | let program_name_expr = iter.next().clone().unwrap().clone(); 32 | let program_name = match program_name_expr { 33 | Expr::Lit(lit) => lit, 34 | _ => { 35 | return Err(Error::new_spanned( 36 | program_name_expr, 37 | format!("the first argument should be the program_name)"), 38 | )); 39 | } 40 | }; 41 | 42 | let program_id_expr = iter.next().clone().unwrap().clone(); 43 | let program_id = match &program_id_expr { 44 | Expr::Lit(lit) => lit.clone(), 45 | _ => { 46 | return Err(Error::new_spanned( 47 | program_id_expr, 48 | format!("the second argument should be the program_id)"), 49 | )); 50 | } 51 | }; 52 | 53 | let program_id_string = match &program_id.lit { 54 | Lit::Str(lit) => lit.value().to_string(), 55 | _ => { 56 | return Err(Error::new_spanned( 57 | program_id_expr, 58 | format!("handlers should contain path to struct"), 59 | )); 60 | } 61 | }; 62 | 63 | let primitive_handlers_ = iter.next().clone().unwrap().clone(); 64 | let mut primitive_handlers = match primitive_handlers_ { 65 | Expr::Array(array) => array, 66 | _ => { 67 | return Err(Error::new_spanned( 68 | primitive_handlers_, 69 | format!("the third argument must be an array of static functions"), 70 | )); 71 | } 72 | }; 73 | 74 | for ph in primitive_handlers.elems.iter_mut() { 75 | match ph { 76 | Expr::Path(path) => { 77 | let ident = Ident::new("program", Span::call_site()); 78 | let path_segment = PathSegment { 79 | ident, 80 | arguments: PathArguments::None, 81 | }; 82 | path.path.segments.push_punct(Token![::](Span::call_site())); 83 | path.path.segments.push_value(path_segment); 84 | } 85 | _ => { 86 | return Err(Error::new_spanned( 87 | ph, 88 | format!("handlers should contain path to struct"), 89 | )); 90 | } 91 | } 92 | } 93 | 94 | let handlers = Program { 95 | program_id_string, 96 | program_name, 97 | program_id, 98 | primitive_handlers, 99 | }; 100 | Ok(handlers) 101 | } 102 | } 103 | 104 | // #[proc_macro] 105 | pub fn declare_program(input: TokenStream) -> TokenStream { 106 | let program = parse_macro_input!(input as Program); 107 | let primitive_handlers = program.primitive_handlers; 108 | let len = primitive_handlers.elems.len(); 109 | let program_id = program.program_id; 110 | let program_name = program.program_name; 111 | let program_id_string = program.program_id_string; 112 | let entrypoint_declaration_register_ = Ident::new( 113 | &format!("entrypoint_declaration_register_{}", program_id_string), 114 | Span::call_site(), 115 | ); 116 | 117 | let output = quote! { 118 | pub static PROGRAM_HANDLERS : [kaizen::context::HandlerFn;#len] = #primitive_handlers; 119 | 120 | solana_program::declare_id!(#program_id); 121 | solana_program::entrypoint!(process_instruction); 122 | 123 | #[inline(never)] 124 | pub fn init() -> solana_program::pubkey::Pubkey { id() } 125 | 126 | #[inline(always)] 127 | pub fn program_id() -> solana_program::pubkey::Pubkey { id() } 128 | 129 | #[cfg(not(target_os = "solana"))] 130 | pub fn program_name() -> &'static str { #program_name } 131 | 132 | #[inline(always)] 133 | pub fn program_handlers() -> &'static [kaizen::context::HandlerFn] { &PROGRAM_HANDLERS[..] } 134 | 135 | #[cfg(not(target_os = "solana"))] 136 | pub fn interface_id(handler_fn: kaizen::context::HandlerFn) -> usize { 137 | PROGRAM_HANDLERS.iter() 138 | .position(|&hfn| hfn as kaizen::context::HandlerFnCPtr == handler_fn as kaizen::context::HandlerFnCPtr ) 139 | .expect("Unknown interface handler! (check declare_program!())") 140 | } 141 | 142 | pub fn program(ctx:&kaizen::context::ContextReference) -> solana_program::entrypoint::ProgramResult { 143 | if ctx.interface_id >= PROGRAM_HANDLERS.len() { 144 | println!("Error - invalid interface id"); 145 | return Err(solana_program::program_error::ProgramError::InvalidArgument); 146 | } 147 | // Ok(PROGRAM_HANDLERS[ctx.interface_id](ctx)?) 148 | PROGRAM_HANDLERS[ctx.interface_id](ctx) 149 | } 150 | 151 | #[cfg(not(feature = "no-entrypoint"))] 152 | pub fn process_instruction( 153 | program_id: &solana_program::pubkey::Pubkey, 154 | accounts: &[solana_program::account_info::AccountInfo], 155 | instruction_data: &[u8], 156 | ) -> solana_program::entrypoint::ProgramResult { 157 | // solana_program::msg!("program_id: {}", program_id); 158 | // solana_program::msg!("accounts: {:?}", accounts); 159 | // solana_program::msg!("instruction_data: {:?}", instruction_data); 160 | match kaizen::context::Context::try_from((program_id,accounts,instruction_data)) { 161 | Err(err) => { 162 | #[cfg(not(target_os = "solana"))] 163 | workflow_log::log_error!("Fatal: unable to load Context: {}", err); 164 | return Err(err.into()); 165 | }, 166 | Ok(mut ctx) => { 167 | PROGRAM_HANDLERS[ctx.interface_id](&mut std::rc::Rc::new(std::boxed::Box::new(ctx)))?; 168 | } 169 | } 170 | 171 | Ok(()) 172 | } 173 | 174 | #[cfg(not(any(target_os = "solana",target_arch = "wasm32")))] 175 | ::inventory::submit! { 176 | kaizen::program::registry::EntrypointDeclaration::new( 177 | ID, 178 | #program_name, 179 | process_instruction 180 | ) 181 | } 182 | 183 | #[cfg(target_arch = "wasm32")] 184 | #[macro_use] 185 | mod wasm { 186 | #[cfg(target_arch = "wasm32")] 187 | #[wasm_bindgen::prelude::wasm_bindgen] 188 | pub fn #entrypoint_declaration_register_() -> kaizen::result::Result<()> { 189 | kaizen::program::registry::register_entrypoint_declaration( 190 | kaizen::program::registry::EntrypointDeclaration::new( 191 | super::ID, 192 | super::program_name(), 193 | super::process_instruction 194 | ) 195 | )?; 196 | Ok(()) 197 | } 198 | } 199 | }; 200 | 201 | output.into() 202 | } 203 | -------------------------------------------------------------------------------- /src/transport/reflector.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Client-side Transport activity tracker (for transactions, wallet and emulator updates). 3 | //! 4 | use crate::error::Error; 5 | use crate::result::Result; 6 | use ahash::HashMap; 7 | use futures::{select, FutureExt}; 8 | use js_sys::{Function, Object}; 9 | use solana_program::pubkey::Pubkey; 10 | use std::sync::atomic::{AtomicBool, Ordering}; 11 | use std::sync::{Arc, Mutex}; 12 | use wasm_bindgen::prelude::*; 13 | use workflow_core::channel::{unbounded, DuplexChannel, Receiver, Sender}; 14 | use workflow_core::id::Id; 15 | use workflow_core::task::*; 16 | use workflow_log::log_error; 17 | use workflow_wasm::prelude::*; 18 | 19 | use super::Transport; 20 | 21 | #[derive(Clone, Debug, Eq, PartialEq)] 22 | pub enum Event { 23 | PendingLookups(usize), 24 | PendingTransactions(usize), 25 | WalletRefresh(String, Pubkey), 26 | WalletBalance(String, Pubkey, u64), 27 | EmulatorLogs(Vec), 28 | Halt, 29 | } 30 | 31 | impl TryFrom<&Event> for JsValue { 32 | type Error = Error; 33 | fn try_from(event: &Event) -> std::result::Result { 34 | let object = Object::new(); 35 | match event { 36 | Event::PendingLookups(count) => { 37 | object.set("event", &"pending-lookups".into())?; 38 | object.set("count", &JsValue::from_f64(*count as f64))?; 39 | } 40 | Event::PendingTransactions(count) => { 41 | object.set("event", &"pending-transactions".into())?; 42 | object.set("count", &JsValue::from_f64(*count as f64))?; 43 | } 44 | Event::WalletRefresh(token, authority) => { 45 | object.set("event", &"wallet-refresh".into())?; 46 | object.set("token", &token.into())?; 47 | object.set("authority", &(*authority).into())?; 48 | } 49 | Event::WalletBalance(token, authority, balance) => { 50 | object.set("event", &"wallet-balance".into())?; 51 | object.set("token", &token.into())?; 52 | object.set("authority", &(*authority).into())?; 53 | object.set("balance", &JsValue::from_f64(*balance as f64))?; 54 | } 55 | Event::EmulatorLogs(logs) => { 56 | object.set("event", &"emulator-logs".into())?; 57 | let logs = logs 58 | .iter() 59 | .map(|log| JsValue::from(log.as_str())) 60 | .collect::>(); 61 | object.set_vec("logs", &logs)?; 62 | } 63 | Event::Halt => { 64 | object.set("event", &"halt".into())?; 65 | } 66 | } 67 | Ok(object.into()) 68 | } 69 | } 70 | 71 | #[derive(Clone)] 72 | pub struct Reflector { 73 | pub channels: Arc>>>, 74 | } 75 | 76 | impl Default for Reflector { 77 | fn default() -> Self { 78 | Self::new() 79 | } 80 | } 81 | 82 | impl Reflector { 83 | pub fn new() -> Reflector { 84 | Reflector { 85 | channels: Arc::new(Mutex::new(HashMap::default())), 86 | } 87 | } 88 | 89 | pub fn register_event_channel(&self) -> (Id, Sender, Receiver) { 90 | let (sender, receiver) = unbounded(); 91 | let id = Id::new(); 92 | self.channels.lock().unwrap().insert(id, sender.clone()); 93 | (id, sender, receiver) 94 | } 95 | 96 | pub fn unregister_event_channel(&self, id: Id) { 97 | self.channels.lock().unwrap().remove(&id); 98 | } 99 | 100 | pub fn reflect(&self, event: Event) { 101 | let channels = self.channels.lock().unwrap(); 102 | for (_, sender) in channels.iter() { 103 | match sender.try_send(event.clone()) { 104 | Ok(_) => {} 105 | Err(err) => { 106 | log_error!( 107 | "Transport Reflector: error reflecting event {:?}: {:?}", 108 | event, 109 | err 110 | ); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | /// 118 | /// [`ReflectorClient`] is an object meant to be use in WASM environment to 119 | /// process [`Transport`] events. [`ReflectorClient`] auto-registers with the 120 | /// global [`Transport`] when event processing task starts and unregisters 121 | /// when the event processing stops. 122 | /// 123 | #[wasm_bindgen] 124 | pub struct ReflectorClient { 125 | callback: Arc>>>, 126 | task_running: AtomicBool, 127 | task_ctl: DuplexChannel, 128 | } 129 | 130 | impl Default for ReflectorClient { 131 | fn default() -> Self { 132 | ReflectorClient::new() 133 | } 134 | } 135 | 136 | #[wasm_bindgen] 137 | impl ReflectorClient { 138 | #[wasm_bindgen(constructor)] 139 | pub fn new() -> ReflectorClient { 140 | ReflectorClient { 141 | callback: Arc::new(Mutex::new(None)), 142 | task_running: AtomicBool::new(false), 143 | task_ctl: DuplexChannel::oneshot(), 144 | } 145 | } 146 | 147 | #[wasm_bindgen(js_name = "setHandler")] 148 | pub fn set_handler(&self, callback: JsValue) -> Result<()> { 149 | if callback.is_function() { 150 | let fn_callback: Function = callback.into(); 151 | self.callback.lock().unwrap().replace(fn_callback.into()); 152 | 153 | // self.start_notification_task()?; 154 | } else { 155 | self.remove_handler()?; 156 | } 157 | Ok(()) 158 | } 159 | 160 | /// `removeHandler` must be called when releasing ReflectorClient 161 | /// to stop the background event processing task 162 | #[wasm_bindgen(js_name = "removeHandler")] 163 | pub fn remove_handler(&self) -> Result<()> { 164 | *self.callback.lock().unwrap() = None; 165 | Ok(()) 166 | } 167 | 168 | #[wasm_bindgen(js_name = "start")] 169 | pub async fn start_notification_task(&self) -> Result<()> { 170 | if self.task_running.load(Ordering::SeqCst) { 171 | panic!("ReflectorClient task is already running"); 172 | } 173 | let ctl_receiver = self.task_ctl.request.receiver.clone(); 174 | let ctl_sender = self.task_ctl.response.sender.clone(); 175 | let callback = self.callback.clone(); 176 | self.task_running.store(true, Ordering::SeqCst); 177 | 178 | let transport = Transport::global().unwrap_or_else(|err| { 179 | panic!("ReflectorClient - missing global transport: {err}"); 180 | }); 181 | let (channel_id, _, receiver) = transport.reflector().register_event_channel(); 182 | 183 | spawn(async move { 184 | loop { 185 | select! { 186 | _ = ctl_receiver.recv().fuse() => { 187 | break; 188 | }, 189 | msg = receiver.recv().fuse() => { 190 | // log_info!("notification: {:?}",msg); 191 | if let Ok(notification) = &msg { 192 | if let Some(callback) = callback.lock().unwrap().as_ref() { 193 | if let Ok(event) = JsValue::try_from(notification) { 194 | if let Err(err) = callback.0.call1(&JsValue::undefined(), &event) { 195 | log_error!("Error while executing notification callback: {:?}", err); 196 | } 197 | } 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | transport.reflector().unregister_event_channel(channel_id); 205 | ctl_sender.send(()).await.ok(); 206 | }); 207 | 208 | Ok(()) 209 | } 210 | 211 | #[wasm_bindgen(js_name = "stop")] 212 | pub async fn stop_notification_task(&self) -> Result<()> { 213 | if self.task_running.load(Ordering::SeqCst) { 214 | self.task_running.store(false, Ordering::SeqCst); 215 | self.task_ctl 216 | .signal(()) 217 | .await 218 | .map_err(|err| JsError::new(&err.to_string()))?; 219 | } 220 | Ok(()) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/transport/api/components.rs: -------------------------------------------------------------------------------- 1 | use crate::result::Result; 2 | use cfg_if::cfg_if; 3 | use solana_sdk::{clock::Slot, commitment_config::CommitmentConfig}; 4 | //use workflow_log::log_trace; 5 | 6 | cfg_if! { 7 | if #[cfg(target_arch = "wasm32")] { 8 | use js_sys::{Array, Object, Reflect}; 9 | use solana_web3_sys::prelude::*; 10 | use wasm_bindgen::prelude::*; 11 | }else{ 12 | pub use { 13 | solana_account_decoder::{ 14 | UiAccountEncoding as RpcAccountEncoding, UiDataSliceConfig as RpcDataSliceConfig, 15 | }, 16 | solana_client::{ 17 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 18 | rpc_filter::Memcmp, 19 | }, 20 | solana_rpc_client_api::filter::{MemcmpEncodedBytes, RpcFilterType}, 21 | }; 22 | } 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub enum AccountEncoding { 27 | Base58, 28 | Base64, 29 | } 30 | 31 | impl From for RpcAccountEncoding { 32 | fn from(value: AccountEncoding) -> Self { 33 | match value { 34 | AccountEncoding::Base58 => RpcAccountEncoding::Base58, 35 | AccountEncoding::Base64 => RpcAccountEncoding::Base64, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | pub struct AccountDataSliceConfig { 42 | pub offset: usize, 43 | pub length: usize, 44 | } 45 | 46 | impl From for RpcDataSliceConfig { 47 | fn from(value: AccountDataSliceConfig) -> Self { 48 | Self { 49 | offset: value.offset, 50 | length: value.length, 51 | } 52 | } 53 | } 54 | 55 | pub type AccountCommitmentConfig = CommitmentConfig; 56 | 57 | #[derive(Debug, Clone, Default)] 58 | pub struct GetProgramAccountsConfig { 59 | pub filters: Option>, 60 | pub encoding: Option, 61 | pub data_slice: Option, 62 | pub commitment: Option, 63 | pub min_context_slot: Option, 64 | pub with_context: Option, 65 | } 66 | 67 | impl GetProgramAccountsConfig { 68 | pub fn new() -> Self { 69 | Self { 70 | ..Default::default() 71 | } 72 | } 73 | 74 | pub fn add_filters(mut self, filters: Vec) -> Result { 75 | //log_trace!("filters: {filters:?}"); 76 | self.filters = Some(filters); 77 | Ok(self) 78 | } 79 | 80 | pub fn encoding(mut self, encoding: AccountEncoding) -> Result { 81 | self.encoding = Some(encoding); 82 | Ok(self) 83 | } 84 | 85 | pub fn data_slice(mut self, data_slice: AccountDataSliceConfig) -> Result { 86 | self.data_slice = Some(data_slice); 87 | Ok(self) 88 | } 89 | 90 | pub fn commitment(mut self, commitment: AccountCommitmentConfig) -> Result { 91 | self.commitment = Some(commitment); 92 | Ok(self) 93 | } 94 | 95 | pub fn min_context_slot(mut self, min_context_slot: Slot) -> Result { 96 | self.min_context_slot = Some(min_context_slot); 97 | Ok(self) 98 | } 99 | 100 | pub fn with_context(mut self, with_context: bool) -> Result { 101 | self.with_context = Some(with_context); 102 | Ok(self) 103 | } 104 | } 105 | 106 | #[cfg(target_arch = "wasm32")] 107 | impl TryFrom for RpcProgramAccountsConfig { 108 | type Error = crate::error::Error; 109 | fn try_from(this: GetProgramAccountsConfig) -> Result { 110 | let mut config = RpcProgramAccountsConfig::new(); 111 | if let Some(filters) = this.filters { 112 | let list = Array::new(); 113 | for filter in filters { 114 | list.push(&filter.try_into()?); 115 | } 116 | 117 | config = config.add_filters(list)?; 118 | }; 119 | 120 | if let Some(value) = this.encoding { 121 | config = config.encoding(value.into())?; 122 | } 123 | if let Some(data_slice) = this.data_slice { 124 | config = config.data_slice(data_slice.into())?; 125 | } 126 | if let Some(commitment) = this.commitment { 127 | config = config.commitment(commitment)?; 128 | } 129 | if let Some(min_context_slot) = this.min_context_slot { 130 | config = config.min_context_slot(min_context_slot)?; 131 | } 132 | 133 | Ok(config) 134 | } 135 | } 136 | 137 | #[cfg(not(target_arch = "wasm32"))] 138 | impl TryFrom for RpcProgramAccountsConfig { 139 | type Error = crate::error::Error; 140 | fn try_from(this: GetProgramAccountsConfig) -> Result { 141 | let filters = match this.filters { 142 | Some(filters) => { 143 | let mut list = vec![]; 144 | for filter in filters { 145 | list.push(filter.try_into()?); 146 | } 147 | 148 | Some(list) 149 | } 150 | None => None, 151 | }; 152 | 153 | let config = RpcProgramAccountsConfig { 154 | filters, 155 | account_config: RpcAccountInfoConfig { 156 | encoding: this.encoding.map(|e| e.into()), 157 | data_slice: this.data_slice.map(|e| e.into()), 158 | commitment: this.commitment, 159 | min_context_slot: this.min_context_slot, 160 | }, 161 | ..Default::default() 162 | }; 163 | 164 | Ok(config) 165 | } 166 | } 167 | 168 | #[derive(Debug, Clone)] 169 | pub enum AccountFilter { 170 | /// Memory comparison filter using offset and base58 encoded string 171 | MemcmpEncodedBase58(usize, String), 172 | 173 | /// Memory comparison filter using offset and base64 encoded string 174 | MemcmpEncodedBase64(usize, String), 175 | 176 | /// Memory comparison filter using offset and bytes which will be encoded as base58 177 | MemcmpEncodeBase58(usize, Vec), 178 | 179 | /// Memory comparison filter using offset and bytes which will be encoded as base64 180 | MemcmpEncodeBase64(usize, Vec), 181 | 182 | /// Data size comparison filter 183 | DataSize(usize), 184 | } 185 | 186 | cfg_if! { 187 | if #[cfg(target_arch = "wasm32")] { 188 | fn create_memcmp_filter( 189 | holder: &Object, 190 | offset: usize, 191 | data: String, 192 | encoding: &str, 193 | ) -> Result<()> { 194 | let memcmp = Object::new(); 195 | Reflect::set(&memcmp, &JsValue::from("offset"), &JsValue::from(offset))?; 196 | Reflect::set(&memcmp, &JsValue::from("bytes"), &JsValue::from(data))?; 197 | Reflect::set( 198 | &memcmp, 199 | &JsValue::from("encoding"), 200 | &JsValue::from(encoding), 201 | )?; 202 | Reflect::set(holder, &JsValue::from("memcmp"), &memcmp.into())?; 203 | 204 | Ok(()) 205 | } 206 | 207 | impl TryFrom for JsValue { 208 | type Error = crate::error::Error; 209 | fn try_from(value: AccountFilter) -> Result { 210 | let obj = Object::new(); 211 | match value { 212 | AccountFilter::MemcmpEncodedBase58(offset, data) => { 213 | create_memcmp_filter(&obj, offset, data, "base58")?; 214 | } 215 | AccountFilter::MemcmpEncodedBase64(offset, data) => { 216 | create_memcmp_filter(&obj, offset, data, "base64")?; 217 | } 218 | AccountFilter::MemcmpEncodeBase58(offset, bytes) => { 219 | let data = bs58::encode(bytes).into_string(); 220 | create_memcmp_filter(&obj, offset, data, "base58")?; 221 | } 222 | AccountFilter::MemcmpEncodeBase64(offset, bytes) => { 223 | let data = base64::encode(bytes); 224 | create_memcmp_filter(&obj, offset, data, "base64")?; 225 | } 226 | AccountFilter::DataSize(data_size) => { 227 | Reflect::set(&obj, &JsValue::from("dataSize"), &JsValue::from(data_size))?; 228 | } 229 | } 230 | 231 | Ok(obj.into()) 232 | } 233 | } 234 | }else{ 235 | impl TryFrom for RpcFilterType { 236 | type Error = crate::error::Error; 237 | fn try_from(filter: AccountFilter) -> std::result::Result { 238 | Ok(match filter { 239 | AccountFilter::MemcmpEncodedBase58(offset, encoded_string) => { 240 | //log_trace!("encoded_string: {encoded_string:?}"); 241 | let bytes = MemcmpEncodedBytes::Base58(encoded_string); 242 | RpcFilterType::Memcmp(Memcmp::new(offset, bytes)) 243 | } 244 | AccountFilter::MemcmpEncodedBase64(offset, encoded_string) => { 245 | //log_trace!("encoded_string: {encoded_string:?}"); 246 | let bytes = MemcmpEncodedBytes::Base64(encoded_string); 247 | RpcFilterType::Memcmp(Memcmp::new(offset, bytes)) 248 | } 249 | 250 | AccountFilter::MemcmpEncodeBase58(offset, bytes) => { 251 | //log_trace!("data: {bytes:?}"); 252 | RpcFilterType::Memcmp(Memcmp::new_base58_encoded(offset, &bytes)) 253 | } 254 | 255 | AccountFilter::MemcmpEncodeBase64(offset, bytes) => { 256 | //log_trace!("bytes: {bytes:?}"); 257 | let bytes = MemcmpEncodedBytes::Base64(base64::encode(bytes)); 258 | RpcFilterType::Memcmp(Memcmp::new(offset, bytes)) 259 | } 260 | 261 | AccountFilter::DataSize(data_size) => { 262 | RpcFilterType::DataSize(data_size as u64) 263 | } 264 | }) 265 | } 266 | } 267 | 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/container/collection/reference.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Proxied collection for an arbitrary set of pubkeys based on a segment-defined seed vector. 3 | //! 4 | //! `Seed Vector -> Proxy Accounts -> Pubkey` 5 | //! 6 | 7 | use cfg_if::cfg_if; 8 | // use crate::address::ProgramAddressData; 9 | use super::meta::*; 10 | use super::proxy::Proxy; 11 | use crate::container::Container; 12 | use crate::result::Result; 13 | use kaizen::error::ErrorCode; 14 | use kaizen::prelude::*; 15 | 16 | pub type PdaProxyCollection<'info, 'refs> = 17 | PdaProxyCollectionInterface<'info, PdaCollectionSegmentInterface<'info, 'refs>>; 18 | pub type PdaProxyCollectionReference<'info> = 19 | PdaProxyCollectionInterface<'info, PdaCollectionMetaInterface<'info>>; 20 | 21 | pub struct PdaProxyCollectionInterface<'info, M> { 22 | pub domain: &'info [u8], 23 | meta: M, 24 | } 25 | 26 | impl<'info, M> PdaProxyCollectionInterface<'info, M> 27 | where 28 | M: CollectionMeta, 29 | { 30 | fn try_create_impl( 31 | domain: &'info [u8], 32 | mut meta: M, 33 | // seed : &[u8], 34 | // container_type : Option 35 | ) -> Result { 36 | meta.try_create()?; //seed,container_type)?; 37 | Ok(Self { domain, meta }) 38 | } 39 | 40 | fn try_load_impl(domain: &'info [u8], mut meta: M) -> Result { 41 | meta.try_load()?; 42 | Ok(Self { domain, meta }) 43 | } 44 | 45 | pub fn data_len_min() -> usize { 46 | M::min_data_len() 47 | } 48 | 49 | pub fn try_create_from_meta( 50 | data: &'info mut PdaCollectionMeta, 51 | account_info: &AccountInfo<'info>, 52 | seed: &'static [u8], 53 | container_type: Option, 54 | ) -> Result>> { 55 | PdaProxyCollectionInterface::::try_create_impl( 56 | account_info.key.as_ref(), 57 | PdaCollectionMetaInterface::new(data, seed, container_type), 58 | // seed, 59 | // container_type, 60 | ) 61 | } 62 | 63 | pub fn try_load_from_meta( 64 | data: &'info mut PdaCollectionMeta, 65 | account_info: &AccountInfo<'info>, 66 | seed: &'static [u8], 67 | container_type: Option, 68 | ) -> Result>> { 69 | PdaProxyCollectionInterface::::try_load_impl( 70 | account_info.key.as_ref(), 71 | PdaCollectionMetaInterface::new(data, seed, container_type), 72 | ) 73 | } 74 | 75 | pub fn try_create_from_segment_with_collection_args<'refs>( 76 | segment: Rc>, 77 | seed: &'static [u8], 78 | container_type: Option, 79 | ) -> Result>> 80 | { 81 | PdaProxyCollectionInterface::::try_load_impl( 82 | segment.account().key.as_ref(), 83 | PdaCollectionSegmentInterface::new(segment, seed, container_type), 84 | ) 85 | } 86 | 87 | pub fn try_load_from_segment_with_collection_args<'refs>( 88 | segment: Rc>, 89 | seed: &'static [u8], 90 | container_type: Option, 91 | ) -> Result>> 92 | { 93 | PdaProxyCollectionInterface::::try_load_impl( 94 | segment.account().key.as_ref(), 95 | PdaCollectionSegmentInterface::new(segment, seed, container_type), 96 | ) 97 | } 98 | 99 | pub fn try_create( 100 | &mut self, 101 | // seed : &[u8], 102 | // container_type : Option, 103 | ) -> Result<()> { 104 | self.meta.try_create() //seed, container_type) 105 | } 106 | 107 | pub fn len(&self) -> usize { 108 | self.meta.get_len() as usize 109 | } 110 | 111 | pub fn is_empty(&self) -> bool { 112 | self.meta.get_len() == 0 113 | } 114 | 115 | pub fn get_proxy_seed_at<'seed>( 116 | &'seed self, 117 | idx: &u64, 118 | suffix: Option<&'seed [u8]>, 119 | ) -> Vec<&'seed [u8]> { 120 | // let index_bytes: &[u8; 8] = unsafe { std::mem::transmute(idx as *const u64) }; 121 | let index_bytes = unsafe { &*(idx as *const u64 as *const [u8; 8]) }; 122 | // let index_bytes: &[u8; 8] = unsafe { std::mem::transmute(idx as *const u64) }; 123 | if let Some(suffix) = suffix { 124 | vec![self.domain, self.meta.get_seed(), index_bytes, suffix] 125 | } else { 126 | vec![self.domain, self.meta.get_seed(), index_bytes] 127 | } 128 | } 129 | 130 | // pub fn get_proxy_seed_at(&self, idx : &u64, suffix : Option) -> Vec<&[u8]> { 131 | // let index_bytes: &[u8;8] = unsafe { std::mem::transmute(idx as * const u64) }; 132 | // if let Some(suffix) = suffix { 133 | // vec![self.domain, &self.meta.get_seed(), index_bytes, &[suffix]] 134 | // } else { 135 | // vec![self.domain, &self.meta.get_seed(), index_bytes] 136 | // } 137 | // } 138 | 139 | // pub fn get_proxy_seed_at(&self, idx : u64) -> Vec { 140 | // let domain = self.domain; 141 | // let index_bytes: [u8; 8] = unsafe { std::mem::transmute(idx.to_le()) }; 142 | // [domain, &self.meta.get_seed(),&index_bytes].concat() 143 | // } 144 | 145 | pub fn try_insert_reference<'refs, T>( 146 | &mut self, 147 | ctx: &ContextReference<'info, 'refs, '_, '_>, 148 | bump: u8, 149 | container: &T, 150 | ) -> Result<()> 151 | where 152 | T: Container<'info, 'refs>, 153 | { 154 | if let Some(container_type) = self.meta.get_container_type() { 155 | if T::container_type() != container_type { 156 | return Err(error_code!(ErrorCode::ContainerTypeMismatch)); 157 | } 158 | } 159 | 160 | let next_index = self.meta.get_len() + 1; 161 | // let tpl_seeds = self.get_proxy_seed_at(&next_index,Some(&[bump])); 162 | let bump = &[bump]; 163 | let tpl_seeds = self.get_proxy_seed_at(&next_index, Some(bump)); 164 | // program_address_data_bytes.push(bump); 165 | 166 | // let tpl_program_address_data = ProgramAddressData::from_bytes(program_address_data_bytes.as_slice()); 167 | let pda = Pubkey::create_program_address( 168 | // &[tpl_program_address_data.seed], 169 | &tpl_seeds, 170 | ctx.program_id, 171 | )?; 172 | 173 | let tpl_account_info = match ctx.locate_index_account(&pda) { 174 | Some(account_info) => account_info, 175 | None => return Err(error_code!(ErrorCode::AccountCollectionNotFound)), 176 | }; 177 | 178 | let allocation_args = AccountAllocationArgs::new(AddressDomain::None); 179 | // let account_info = 180 | ctx.try_create_pda_with_args( 181 | Proxy::data_len(), 182 | &allocation_args, 183 | &tpl_seeds, 184 | // tpl_program_address_data, 185 | tpl_account_info, 186 | false, 187 | )?; 188 | 189 | Proxy::try_create(tpl_account_info, container.pubkey())?; 190 | self.meta.set_len(next_index); 191 | 192 | Ok(()) 193 | } 194 | } 195 | 196 | cfg_if! { 197 | if #[cfg(not(target_os = "solana"))] { 198 | 199 | use futures::{stream::FuturesOrdered, StreamExt}; 200 | 201 | impl<'info,M> PdaProxyCollectionInterface<'info,M> 202 | where M: CollectionMeta 203 | { 204 | 205 | // impl<'info,'refs> AccountReferenceCollection<'info,'refs> { 206 | 207 | pub fn get_proxy_pda_at(&self, program_id : &Pubkey, idx : u64) -> Result<(Pubkey, u8)> { 208 | let (address, bump) = Pubkey::find_program_address( 209 | &self.get_proxy_seed_at(&idx,None), 210 | // &[&self.get_proxy_seed_at(idx)], //domain,&meta.get_seed_as_bytes(),&index_bytes], 211 | program_id 212 | ); 213 | 214 | Ok((address, bump)) 215 | } 216 | 217 | 218 | pub fn get_proxy_pubkey_at(&self, program_id : &Pubkey, idx : usize) -> Result { 219 | Ok(self.get_proxy_pda_at(program_id,idx as u64)?.0) 220 | } 221 | 222 | pub async fn load_container_at<'this,T>(&self, program_id: &Pubkey, idx: usize) 223 | -> Result>> 224 | where T: kaizen::container::Container<'this,'this> 225 | { 226 | let transport = Transport::global()?; 227 | self.load_container_at_with_transport::(program_id, idx, &transport).await 228 | } 229 | 230 | pub async fn load_container_at_with_transport<'this,T>(&self, program_id: &Pubkey, idx: usize, transport: &Arc) 231 | -> Result>> 232 | where T: kaizen::container::Container<'this,'this> 233 | { 234 | let proxy_pubkey = self.get_proxy_pubkey_at(program_id, idx)?; 235 | let proxy = match load_container_with_transport::(transport, &proxy_pubkey).await? { 236 | Some(proxy) => proxy, 237 | None => return Err(error_code!(ErrorCode::AccountReferenceCollectionProxyNotFound)) 238 | }; 239 | 240 | let container_pubkey = proxy.reference(); 241 | load_container_with_transport::(transport,container_pubkey).await 242 | } 243 | 244 | 245 | pub async fn load_container_range<'this,T>(&self, program_id: &Pubkey, range: std::ops::Range) 246 | -> Result>>>> 247 | where T: kaizen::container::Container<'this,'this> 248 | { 249 | let transport = Transport::global()?; 250 | self.load_container_range_with_transport::(program_id, range, &transport).await 251 | } 252 | 253 | pub async fn load_container_range_with_transport<'this,T>(&self, program_id: &Pubkey, range: std::ops::Range, transport: &Arc) 254 | -> Result>>>> 255 | where T: kaizen::container::Container<'this,'this> 256 | { 257 | let mut futures = FuturesOrdered::new(); 258 | for idx in range { 259 | let f = self.load_container_at_with_transport::(program_id, idx, transport); 260 | futures.push_back(f); 261 | } 262 | 263 | Ok(futures.collect::>().await) 264 | } 265 | 266 | } 267 | 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Kaizen 2 | 3 | Solana OS Rust framework for industrial grade applications. 4 | 5 | [github](https://github.com/solana-kaizen/kaizen) 6 | [crates.io](https://crates.io/crates/kaizen) 7 | [docs.rs](https://docs.rs/kaizen) 8 | license 9 | 10 | 11 | 12 | 13 | 14 |

15 |
THE GREAT WAVE OFF KANAGAWA • KATSUSHIKA HOKUSAI • JAPAN 1831

16 | 17 | ‘Kaizen’ focuses on the refinement of the Solana application development infrastructure by identifying framework optimization opportunities in order to increase reliability and simplify Solana-based full-stack application development. 18 | 19 | ## Overview 20 | 21 | Kaizen is a *security-and-reliability-centric* framework for developing of Solana Programs and *client-side web applications* using Rust. The primary goal behind this project is to eliminate IDLs and contain the program and client-side application within the same Rust codebase. 22 | 23 | This in-turn allows developers to use functions and data structures that are a part of the program directly within the client-side web application. 24 | 25 | The framework is backed by native and in-browser async Rust transport layers that can fetch account data and access it client-side via functions interfacing with [AccountInfo](https://docs.rs/solana-program/latest/solana_program/account_info/struct.AccountInfo.html) and *Account Data Containers*. 26 | 27 | An example is available here: 28 | 29 | 30 | ## Features 31 | 32 | * Unified async Rust Web3 transport interface (uses native Rust Solana implementation when building native and Web3.js implementation when running under WASM32 browser environment). 33 | * Built on top of [`workflow-rs`](https://github.com/workflow-rs/workflow-rs) async Rust application development framework and designed to provide unified environment where functions can be used client-side (for example, a function using `workflow_log::log_info!()` will invoke printf!() on native, `console.log()` in browser and `solana_program::log::sol_log()` under Solana OS). 34 | * Unified Solana *instruction builder interface* that uses [Rust Builder Pattern](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html) and includes various functionality for account data exchange and PDA key management. The instruction builder supports creation of batch transactions for dispatch of multi-stage (or large-side) operations and includes transaction queue management functionality. 35 | * Macros for program function mappings, allowing invocation of program functions by function names in-client. 36 | * Segmented account data storage derived from Rust structure declarations, allowing each structure field to be accessed directly, and resized. Segments can be memory-mapped data structures as well as borsh-serialized data structures. 37 | * Container-based approach for account management with a simultaneous in-program and client-side container type registration. 38 | * Client-side container access and caching mechanisms (using async Rust transport api). 39 | * Solana Emulator (extremely simplified) provides the developer with the ability to run programs on native targets (OS) and in-browser (in WASM). This emulator supports a limited subset of account functionality such as account resizing, SOL balance tracking etc., and is intended for testing and prototyping program functionality in the native of in-browser environments. The emulator also supports basic scaffolding for off-chain program unit-testing. 40 | * async Rust subsystem for client-side account data and container fetching, including application-level in-memory account data cache. 41 | * Basic user identity data structures allowing multiple wallets to be bound to a single user identity. 42 | * `Instant` data structure for time tracking (uses Instance on native, `Date::now()` in WASM32 and `Clock::get()` in Solana). 43 | * Support for integration with multiple Solana Programs as well as interfacing with multiple programs from within a single application. 44 | * Helper functions for automated account creation and resizing. 45 | 46 | ## Motivation 47 | 48 | - Interpreted languages such as TypeScript and JavaScript are inherently unsecure, especially taking into account package managers such as NPM and general practices of developers using them. There are various code-injection attacks that can be performed on the code written in these languages. These technologies should not be used in high-security and high-reliability applications, especially business oriented cryptocurrency applications. Rust + WASM greatly reduces these attack surfaces. 49 | - Solana application frameworks such as [Anchor](https://www.anchor-lang.com/) rely on exposing data structures via IDL, introducing multiple layers of tools and technologies between the application and the program. Rust compiled straight into WASM eliminates these layers, allowing application developer to publish primitives directly from the Rust codebase into front-end applications. In many cases, the core application functionality can be written in Rust exposing only API calls needed by the application front-end, thus imposing Rust reliability and strict type system onto the core of the web application. 50 | - When creating complex APIs meant to interface with Solana programs, at times it is desirable to create both a web front-end and a server backend that are capable of communicating with the network and on-chain programs. APIs developed on top of Kaizen, function uniformly in native applications and in web applications. Currently, to function in web applications and to interface with wallets, Kaizen uses Solana web3 libraries. It is our goal in the long-term to completely eliminate web3 dependencies. 51 | 52 | ## Development status 53 | 54 | We have been using the framework for in-house development for few months, gradually improving it. The framework is currently under developmnet and should be considered in alpha / preview stage. Additional work is needed on documentation and tutorials. We are looking for sponsorship to help us dedicate more time toward polishing this platform. 55 | 56 | You should currently approach this only if you are confident in your Rust skills, have good understanding of the Solana Account system and are looking to develop large-scale business or "industrial-grade" applications exposing WASM APIs client-side. 57 | 58 | If you would like to develop applications using this project, we can help answer any questions and guide you through the APIs. Join us on ASPECTRON Discord server if you have any questions. You can find the link for Discord at [https://aspectron.com/en/index.html#about](https://aspectron.com/en/index.html#about) 59 | 60 | ## TODO 61 | 62 | - Parallelism in collection account creation - Currently, in a multi-user environment, functions that provide multiple users with an ability to create collection-bound accounts (PDAs), can collide if multiple users execute account creation in parallel. To mitigate this, the collection creation functionality needs to supply a list of accounts, while the program API handling account creation needs to select corresponding account templates based on the cursor (collection length) value at the moment of the program execution. 63 | - Support for WebSocket updates - Kaizen does not currently support any type of network-side event updates. We need to implement program monitoring channels and create bindings to the Transport and Transaction Queue to automate processes like account creation notifications. 64 | - Refactor Kaizen WASM APIs to use [`Sendable()`](https://github.com/workflow-rs/workflow-rs/blob/master/wasm/src/sendable.rs) wrappers - to date, we have been using `#[async_trait]` and `#[async_trait(?Send)]` macros that were re-exported by the [`workflow-async-trait`](https://github.com/workflow-rs/workflow-async-trait) crate as `#[workflow_async_trait]` where the Send marker would be required on the async trait in the Rust native environment (so that it can be used under *Tokio*) and not required in WASM32 environment (so that it can be used under *async_std*). After using the framework extensively we have concluded that using `Sendable` wrappers is much more efficient and cleaner, removing the need for any *async_trait* customizations. 65 | - Integrate basic wallet functionality and a wallet API as there are use-cases where it may be desirable for business applications to include their own in-application wallets to automate payments. While using web apps in a browser environment, user can take advantage of the browser-compatible wallets (such as Phantom), in native Rust environment, user can utilize native commant-line wallet. However, Kaizen, combined with [NWJS](https://nwjs.io) backed by [`workflow-nw`](https://crates.io/crates/workflow-nw) crate, combined with [`cargo-nw`](https://aspectron.com/en/projects/cargo-nw.html) redistributable package builder, it is possible to create fully-featured HTML-powerd traditional desktop applications installable in Windows, MacOS and Linux environments. However, such applications currently lack the ability to have an interactive wallet (although NWJS supports chrome extensions and technically it should be possible to install Phantom within NWJS, but such installation will be rather complex for the end-user and play against shipping a fully-integrated easy-to-use product). 66 | - Review the entire framework to see which components can be isolated into Rust crate features in an effort to see if we can reduce the footprint of the resulting SBF bytecode. 67 | - There is currently no CPI support for calling methods exposed by the program using Kaizen. CPI support will be implemented by allowing registration of separate program call handlers that do not use structured instruction buffers (this requires first byte of the instruction buffer to be `0`). This is provisioned for. We also need to create a helper struct that allows us to efficiently create an instruction buffer payload from within the program. --------------------------------------------------------------------------------