Skip to content

Commit

Permalink
print only execution mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ochaloup committed Jun 29, 2023
1 parent 3c55b54 commit 71db03b
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 20 deletions.
3 changes: 2 additions & 1 deletion libs/marinade-client-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ solana-client = "1.14.18"
marinade-finance = { git = "https://github.com/marinade-finance/liquid-staking-program.git", branch = "anchor-0.27" }
dynsigner = { path = "../dynsigner" }
anchor-lang = "0.27.0"
anchor-client = "0.27.0"
anchor-client = "0.27.0"
borsh = "0.9.3"
1 change: 1 addition & 0 deletions libs/marinade-client-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod instructions;
pub mod rpc_marinade;
pub mod state;
pub mod transaction_executors;
pub mod transaction_instruction;
pub mod verifiers;

pub use solana_sdk;
Expand Down
93 changes: 75 additions & 18 deletions libs/marinade-client-rs/src/transaction_executors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::transaction_instruction::{TransactionAccount, TransactionInstruction};
use anchor_client::RequestBuilder;
use anyhow::{anyhow, bail};
use anchor_lang::idl::IdlAccount;
use anyhow::bail;
use borsh::BorshSerialize;
use log::{debug, error, info, warn};
use solana_client::client_error::ClientErrorKind;
use solana_client::rpc_client::RpcClient;
Expand All @@ -9,6 +12,7 @@ use solana_client::rpc_response::{RpcResult, RpcSimulateTransactionResult};
use solana_sdk::commitment_config::CommitmentLevel;
use solana_sdk::signature::Signature;
use solana_sdk::signer::Signer;
use spl_token::solana_program::instruction::Instruction;
use std::ops::Deref;

pub trait TransactionExecutor {
Expand Down Expand Up @@ -116,47 +120,100 @@ pub fn log_simulation(
Ok(())
}

pub fn print_base64(instructions: &Vec<Instruction>) -> anyhow::Result<()> {
for instruction in instructions {
let transaction_instruction = TransactionInstruction {
program_id: instruction.program_id,
accounts: instruction
.accounts
.iter()
.map(TransactionAccount::from)
.collect(),
data: instruction.data.clone(),
};
println!(
"base64 instruction to idl account {} of program {}:",
IdlAccount::address(&instruction.program_id),
instruction.program_id
);
println!(
" {}",
anchor_lang::__private::base64::encode(transaction_instruction.try_to_vec()?)
);
}
Ok(())
}

pub fn execute<'a, I, C>(
anchor_builders: I,
rpc_client: &RpcClient,
simulate: bool,
print_only: bool,
) -> anyhow::Result<()>
where
I: IntoIterator<Item = RequestBuilder<'a, C>>,
C: Deref<Target = dynsigner::DynSigner> + Clone,
{
if !simulate {
let commitment_level = rpc_client.commitment().commitment;
anchor_builders
.into_iter()
.try_for_each(|builder| log_execution(builder.execute(commitment_level)))?;
} else {
let mut builders_iterator = anchor_builders.into_iter();
log_simulation(
builders_iterator
.next()
.ok_or_else(|| anyhow!("No transactions to simulate"))?
.simulate(rpc_client),
)?;
if builders_iterator.next().is_some() {
warn_text_simulate_print_only(simulate, print_only);

if simulate {
let mut count = 0u32;
for builder in anchor_builders {
if print_only {
print_base64(&builder.instructions()?)?;
continue;
}
log_simulation(builder.simulate(rpc_client))?;
count += 1;
}
if count > 1 {
warn!(
"Simulation mode: only the first transaction was simulated. The rest are ignored."
);
}
} else {
// execute or print_only
let commitment_level = rpc_client.commitment().commitment;
anchor_builders.into_iter().try_for_each(|builder| {
if print_only {
print_base64(&builder.instructions()?)
} else {
log_execution(builder.execute(commitment_level))
}
})?;
}

Ok(())
}

pub fn execute_single<C: Deref<Target = dynsigner::DynSigner> + Clone>(
anchor_builder: RequestBuilder<C>,
rpc_client: &RpcClient,
simulate: bool,
print_only: bool,
) -> anyhow::Result<()> {
if !simulate {
warn_text_simulate_print_only(simulate, print_only);

if print_only {
print_base64(&anchor_builder.instructions()?)?;
}

if simulate {
log_simulation(anchor_builder.simulate(rpc_client))?;
} else if !print_only {
// !simulate && !print_only
let commitment_level = rpc_client.commitment().commitment;
log_execution(anchor_builder.execute(commitment_level))?;
} else {
log_simulation(anchor_builder.simulate(rpc_client))?;
}

Ok(())
}

fn warn_text_simulate_print_only(simulate: bool, print_only: bool) {
if simulate {
warn!("Simulation mode: transactions will not be executed, only simulated.");
}
if print_only {
warn!("Print only mode: transactions will only be printed in base64 format.");
}
}
52 changes: 52 additions & 0 deletions libs/marinade-client-rs/src/transaction_instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use anchor_lang::prelude::{AnchorDeserialize, AnchorSerialize};
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;

// Set of struct wrappers that can be used to deserialize instruction.
// For marinade client it's the base64 format which is used in multisig like SPL Governance.

#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
pub struct TransactionInstruction {
// Target program to execute against.
pub program_id: Pubkey,
// Accounts requried for the transaction.
pub accounts: Vec<TransactionAccount>,
// Instruction data for the transaction.
pub data: Vec<u8>,
}

impl From<&TransactionInstruction> for Instruction {
fn from(tx: &TransactionInstruction) -> Instruction {
Instruction {
program_id: tx.program_id,
accounts: tx.accounts.iter().map(AccountMeta::from).collect(),
data: tx.data.clone(),
}
}
}

#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]
pub struct TransactionAccount {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}

impl From<&TransactionAccount> for AccountMeta {
fn from(account: &TransactionAccount) -> AccountMeta {
match account.is_writable {
false => AccountMeta::new_readonly(account.pubkey, account.is_signer),
true => AccountMeta::new(account.pubkey, account.is_signer),
}
}
}

impl From<&AccountMeta> for TransactionAccount {
fn from(account_meta: &AccountMeta) -> TransactionAccount {
TransactionAccount {
pubkey: account_meta.pubkey,
is_signer: account_meta.is_signer,
is_writable: account_meta.is_writable,
}
}
}
14 changes: 13 additions & 1 deletion libs/marinade-common-cli/src/config_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ pub fn program_arg<'a, 'b>() -> Arg<'a, 'b> {
pub const INSTANCE_ARG: ArgConstant<'static> = ArgConstant {
name: "instance",
long: "instance",
// TODO: should be possible to load the config file instead of a pubkey?
help: "Marinade instance pubkey.",
};
pub fn instance_arg<'a, 'b>() -> Arg<'a, 'b> {
Expand All @@ -123,3 +122,16 @@ pub fn instance_arg<'a, 'b>() -> Arg<'a, 'b> {
.default_value("8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC")
.help(INSTANCE_ARG.help)
}

pub const PRINT_ONLY_ARG: ArgConstant<'static> = ArgConstant {
name: "print_only",
long: "print-only",
help: "The transaction is not executed but outputed in base64 format",
};
pub fn print_only_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(PRINT_ONLY_ARG.name)
.takes_value(false)
.short("p")
.help(PRINT_ONLY_ARG.long)
.help(PRINT_ONLY_ARG.help)
}

0 comments on commit 71db03b

Please sign in to comment.