#[cfg(feature = "borsh")]
use borsh;
#[cfg(feature = "bincode")]
use serde::Serialize;
use crate::{
error::{Result, RialoError},
keyring::Keyring,
rpc::{types::Pubkey, RpcClient},
transaction::{AccountMeta, Instruction, TransactionBuilder},
};
#[derive(Debug, Clone)]
pub struct ProgramInvocation {
pub program_id: Pubkey,
pub accounts: Vec<AccountMeta>,
pub data: Vec<u8>,
}
impl ProgramInvocation {
pub fn new(program_id: Pubkey) -> Self {
Self {
program_id,
accounts: Vec::new(),
data: Vec::new(),
}
}
pub fn add_account(mut self, pubkey: Pubkey, is_signer: bool, is_writable: bool) -> Self {
self.accounts.push(AccountMeta {
pubkey,
is_signer,
is_writable,
});
self
}
pub fn add_readonly_account(self, pubkey: Pubkey) -> Self {
self.add_account(pubkey, false, false)
}
pub fn add_writable_account(self, pubkey: Pubkey) -> Self {
self.add_account(pubkey, false, true)
}
pub fn add_signer_account(self, pubkey: Pubkey, is_writable: bool) -> Self {
self.add_account(pubkey, true, is_writable)
}
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.data = data;
self
}
#[cfg(feature = "borsh")]
pub fn with_borsh_data<T>(mut self, data: &T) -> Result<Self>
where
T: borsh::BorshSerialize,
{
self.data = borsh::to_vec(data).map_err(|e| {
RialoError::SerializationError(format!("Failed to serialize instruction data: {}", e))
})?;
Ok(self)
}
#[cfg(feature = "bincode")]
pub fn with_bincode_data<T>(mut self, data: &T) -> Result<Self>
where
T: Serialize,
{
self.data = bincode::serialize(data).map_err(|e| {
RialoError::SerializationError(format!("Failed to serialize instruction data: {e}"))
})?;
Ok(self)
}
pub fn into_instruction(self) -> Instruction {
Instruction {
program_id: self.program_id,
accounts: self.accounts,
data: self.data,
}
}
pub async fn invoke<C: RpcClient>(self, client: &C, payer: &Keyring) -> Result<String> {
let valid_from = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as i64;
let instruction = self.into_instruction();
let mut tx_builder = TransactionBuilder::new(payer.pubkey(), valid_from);
tx_builder.add_instruction(instruction);
let signed_tx = tx_builder.sign_with_keypair(payer, 0)?;
let signature = client.send_transaction(&signed_tx, None).await?;
Ok(signature.to_string())
}
pub async fn simulate(self, payer: &Keyring) -> Result<()> {
let valid_from = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as i64;
if self.data.is_empty() {
return Err(RialoError::InvalidInput(
"Instruction data is empty".to_string(),
));
}
let instruction = self.into_instruction();
let mut tx_builder = TransactionBuilder::new(payer.pubkey(), valid_from);
tx_builder.add_instruction(instruction);
let _signed_tx = tx_builder.sign_with_keypair(payer, 0)?;
println!("Program invocation simulation passed");
Ok(())
}
}
pub struct ProgramInvocationBuilder {
invocation: ProgramInvocation,
}
impl ProgramInvocationBuilder {
pub fn new(program_id: Pubkey) -> Self {
Self {
invocation: ProgramInvocation::new(program_id),
}
}
pub fn add_account(mut self, pubkey: Pubkey, is_signer: bool, is_writable: bool) -> Self {
self.invocation = self.invocation.add_account(pubkey, is_signer, is_writable);
self
}
pub fn add_readonly_account(mut self, pubkey: Pubkey) -> Self {
self.invocation = self.invocation.add_readonly_account(pubkey);
self
}
pub fn add_writable_account(mut self, pubkey: Pubkey) -> Self {
self.invocation = self.invocation.add_writable_account(pubkey);
self
}
pub fn add_signer_account(mut self, pubkey: Pubkey, is_writable: bool) -> Self {
self.invocation = self.invocation.add_signer_account(pubkey, is_writable);
self
}
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.invocation = self.invocation.with_data(data);
self
}
#[cfg(feature = "borsh")]
pub fn with_borsh_data<T>(mut self, data: &T) -> Result<Self>
where
T: borsh::BorshSerialize,
{
self.invocation = self.invocation.with_borsh_data(data)?;
Ok(self)
}
#[cfg(feature = "bincode")]
pub fn with_bincode_data<T>(mut self, data: &T) -> Result<Self>
where
T: Serialize,
{
self.invocation = self.invocation.with_bincode_data(data)?;
Ok(self)
}
pub fn build(self) -> ProgramInvocation {
self.invocation
}
pub async fn invoke<C: RpcClient>(self, client: &C, payer: &Keyring) -> Result<String> {
self.build().invoke(client, payer).await
}
pub async fn simulate(self, payer: &Keyring) -> Result<()> {
self.build().simulate(payer).await
}
}
impl ProgramInvocation {
pub fn transfer_like(program_id: Pubkey, from: Pubkey, to: Pubkey, data: Vec<u8>) -> Self {
Self::new(program_id)
.add_signer_account(from, true)
.add_writable_account(to)
.with_data(data)
}
pub fn initialize(
program_id: Pubkey,
initializer: Pubkey,
account_to_initialize: Pubkey,
data: Vec<u8>,
) -> Self {
Self::new(program_id)
.add_signer_account(initializer, true)
.add_writable_account(account_to_initialize)
.with_data(data)
}
pub fn close_account(
program_id: Pubkey,
authority: Pubkey,
account_to_close: Pubkey,
rent_recipient: Pubkey,
) -> Self {
Self::new(program_id)
.add_signer_account(authority, false)
.add_writable_account(account_to_close)
.add_writable_account(rent_recipient)
.with_data(vec![]) }
}