Support using Squads multisig with sealevel tooling, support ownership transfers in tooling (#2700)

### Description

Sadly, there's no programmatic way to submit transactions to Squads via
an API or anything. As a workaround, if `-k` is passed a Pubkey and not
a path to a Keypair as the payer, transactions will instead be logged in
a base58 serialized format that can then be copied into Squads.
Instructions for exactly how to do this are found in our Notion

So this includes:
* Allow for the payer to just be a Pubkey without a Keypair
* Some commands & functions to allow for ownership transfer

### Drive-by changes

* There was a whole directory
`rust/chains/hyperlane-sealevel/src/solana` that somehow got into main,
I deleted this
* Cleaned up some logs to be formatted more nicely

### Related issues

part of https://github.com/hyperlane-xyz/issues/issues/545

### Backward compatibility

backward compatible

### Testing

Transferred ownership of devnet

---------

Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com>
pull/2704/head
Trevor Porter 1 year ago committed by GitHub
parent 71e8988ccd
commit 2d8fced77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      rust/Cargo.lock
  2. 9
      rust/chains/hyperlane-sealevel/src/mailbox.rs
  3. 7
      rust/chains/hyperlane-sealevel/src/solana/ed25519_program.rs
  4. 360
      rust/chains/hyperlane-sealevel/src/solana/fee_calculator.rs
  5. 101
      rust/chains/hyperlane-sealevel/src/solana/sdk/Cargo.toml
  6. 23
      rust/chains/hyperlane-sealevel/src/solana/sdk/macro/Cargo.toml
  7. 405
      rust/chains/hyperlane-sealevel/src/solana/sdk/macro/src/lib.rs
  8. 1
      rust/chains/hyperlane-sealevel/src/solana/sdk/src/lib.rs
  9. 12
      rust/chains/hyperlane-sealevel/src/solana/secp256k1_program.rs
  10. 1
      rust/chains/hyperlane-sealevel/src/solana/solana_sdk/mod.rs
  11. 423
      rust/chains/hyperlane-sealevel/src/solana/solana_sdk/solana_sdk_macro/mod.rs
  12. 2
      rust/sealevel/client/Cargo.toml
  13. 10
      rust/sealevel/client/src/cmd_utils.rs
  14. 186
      rust/sealevel/client/src/context.rs
  15. 30
      rust/sealevel/client/src/core.rs
  16. 174
      rust/sealevel/client/src/main.rs
  17. 14
      rust/sealevel/client/src/warp_route.rs
  18. 29
      rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs
  19. 35
      rust/sealevel/programs/hyperlane-sealevel-igp/src/instruction.rs
  20. 2
      rust/sealevel/programs/hyperlane-sealevel-igp/src/processor.rs
  21. 23
      rust/sealevel/programs/ism/multisig-ism-message-id/src/instruction.rs
  22. 2
      rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs
  23. 23
      rust/sealevel/programs/mailbox/src/instruction.rs
  24. 2
      rust/sealevel/programs/mailbox/src/processor.rs

2
rust/Cargo.lock generated

@ -3753,7 +3753,9 @@ name = "hyperlane-sealevel-client"
version = "0.1.0"
dependencies = [
"account-utils",
"bincode",
"borsh 0.9.3",
"bs58 0.5.0",
"clap 4.3.19",
"hex 0.4.3",
"hyperlane-core",

@ -489,13 +489,13 @@ impl Mailbox for SealevelMailbox {
data: ixn_data,
accounts,
};
tracing::info!("accounts={:#?}", inbox_instruction.accounts);
instructions.push(inbox_instruction);
let (recent_blockhash, _) = self
.rpc_client
.get_latest_blockhash_with_commitment(commitment)
.await
.map_err(ChainCommunicationError::from_other)?;
let txn = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
@ -503,13 +503,16 @@ impl Mailbox for SealevelMailbox {
recent_blockhash,
);
tracing::info!(?txn, "Created sealevel transaction to process message");
let signature = self
.rpc_client
.send_and_confirm_transaction(&txn)
.await
.map_err(ChainCommunicationError::from_other)?;
tracing::info!("signature={}", signature);
tracing::info!("txn={:?}", txn);
tracing::info!(?txn, ?signature, "Sealevel transaction sent");
let executed = self
.rpc_client
.confirm_transaction_with_commitment(&signature, commitment)

@ -1,7 +0,0 @@
//! The [ed25519 native program][np].
//!
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#ed25519-program
use crate::solana::pubkey::Pubkey;
use solana_sdk_macro::declare_id;
declare_id!("Ed25519SigVerify111111111111111111111111111");

@ -1,360 +0,0 @@
//! Calculation of transaction fees.
#![allow(clippy::integer_arithmetic)]
use serde_derive::{Deserialize, Serialize};
use super::{ed25519_program, message::Message, secp256k1_program};
// use super::
use log::*;
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug)]
#[serde(rename_all = "camelCase")]
pub struct FeeCalculator {
/// The current cost of a signature.
///
/// This amount may increase/decrease over time based on cluster processing
/// load.
pub lamports_per_signature: u64,
}
impl FeeCalculator {
pub fn new(lamports_per_signature: u64) -> Self {
Self {
lamports_per_signature,
}
}
#[deprecated(
since = "1.9.0",
note = "Please do not use, will no longer be available in the future"
)]
pub fn calculate_fee(&self, message: &Message) -> u64 {
let mut num_signatures: u64 = 0;
for instruction in &message.instructions {
let program_index = instruction.program_id_index as usize;
// Message may not be sanitized here
if program_index < message.account_keys.len() {
let id = message.account_keys[program_index];
if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id))
&& !instruction.data.is_empty()
{
num_signatures += instruction.data[0] as u64;
}
}
}
self.lamports_per_signature
* (u64::from(message.header.num_required_signatures) + num_signatures)
}
}
/*
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
#[serde(rename_all = "camelCase")]
pub struct FeeRateGovernor {
// The current cost of a signature This amount may increase/decrease over time based on
// cluster processing load.
#[serde(skip)]
pub lamports_per_signature: u64,
// The target cost of a signature when the cluster is operating around target_signatures_per_slot
// signatures
pub target_lamports_per_signature: u64,
// Used to estimate the desired processing capacity of the cluster. As the signatures for
// recent slots are fewer/greater than this value, lamports_per_signature will decrease/increase
// for the next slot. A value of 0 disables lamports_per_signature fee adjustments
pub target_signatures_per_slot: u64,
pub min_lamports_per_signature: u64,
pub max_lamports_per_signature: u64,
// What portion of collected fees are to be destroyed, as a fraction of std::u8::MAX
pub burn_percent: u8,
}
pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
// Percentage of tx fees to burn
pub const DEFAULT_BURN_PERCENT: u8 = 50;
impl Default for FeeRateGovernor {
fn default() -> Self {
Self {
lamports_per_signature: 0,
target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
min_lamports_per_signature: 0,
max_lamports_per_signature: 0,
burn_percent: DEFAULT_BURN_PERCENT,
}
}
}
impl FeeRateGovernor {
pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
let base_fee_rate_governor = Self {
target_lamports_per_signature,
lamports_per_signature: target_lamports_per_signature,
target_signatures_per_slot,
..FeeRateGovernor::default()
};
Self::new_derived(&base_fee_rate_governor, 0)
}
pub fn new_derived(
base_fee_rate_governor: &FeeRateGovernor,
latest_signatures_per_slot: u64,
) -> Self {
let mut me = base_fee_rate_governor.clone();
if me.target_signatures_per_slot > 0 {
// lamports_per_signature can range from 50% to 1000% of
// target_lamports_per_signature
me.min_lamports_per_signature = std::cmp::max(1, me.target_lamports_per_signature / 2);
me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
// What the cluster should charge at `latest_signatures_per_slot`
let desired_lamports_per_signature =
me.max_lamports_per_signature
.min(me.min_lamports_per_signature.max(
me.target_lamports_per_signature
* std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64)
as u64
/ me.target_signatures_per_slot as u64,
));
trace!(
"desired_lamports_per_signature: {}",
desired_lamports_per_signature
);
let gap = desired_lamports_per_signature as i64
- base_fee_rate_governor.lamports_per_signature as i64;
if gap == 0 {
me.lamports_per_signature = desired_lamports_per_signature;
} else {
// Adjust fee by 5% of target_lamports_per_signature to produce a smooth
// increase/decrease in fees over time.
let gap_adjust =
std::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
trace!(
"lamports_per_signature gap is {}, adjusting by {}",
gap,
gap_adjust
);
me.lamports_per_signature =
me.max_lamports_per_signature
.min(me.min_lamports_per_signature.max(
(base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
as u64,
));
}
} else {
me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
me.min_lamports_per_signature = me.target_lamports_per_signature;
me.max_lamports_per_signature = me.target_lamports_per_signature;
}
debug!(
"new_derived(): lamports_per_signature: {}",
me.lamports_per_signature
);
me
}
pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
Self {
lamports_per_signature,
..*self
}
}
/// calculate unburned fee from a fee total, returns (unburned, burned)
pub fn burn(&self, fees: u64) -> (u64, u64) {
let burned = fees * u64::from(self.burn_percent) / 100;
(fees - burned, burned)
}
/// create a FeeCalculator based on current cluster signature throughput
pub fn create_fee_calculator(&self) -> FeeCalculator {
FeeCalculator::new(self.lamports_per_signature)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{pubkey::Pubkey, system_instruction},
};
#[test]
fn test_fee_rate_governor_burn() {
let mut fee_rate_governor = FeeRateGovernor::default();
assert_eq!(fee_rate_governor.burn(2), (1, 1));
fee_rate_governor.burn_percent = 0;
assert_eq!(fee_rate_governor.burn(2), (2, 0));
fee_rate_governor.burn_percent = 100;
assert_eq!(fee_rate_governor.burn(2), (0, 2));
}
#[test]
#[allow(deprecated)]
fn test_fee_calculator_calculate_fee() {
// Default: no fee.
let message = Message::default();
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
// No signature, no fee.
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
// One signature, a fee.
let pubkey0 = Pubkey::new(&[0; 32]);
let pubkey1 = Pubkey::new(&[1; 32]);
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let message = Message::new(&[ix0], Some(&pubkey0));
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
// Two signatures, double the fee.
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
let message = Message::new(&[ix0, ix1], Some(&pubkey0));
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
}
#[test]
#[allow(deprecated)]
fn test_fee_calculator_calculate_fee_secp256k1() {
use crate::instruction::Instruction;
let pubkey0 = Pubkey::new(&[0; 32]);
let pubkey1 = Pubkey::new(&[1; 32]);
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let mut secp_instruction = Instruction {
program_id: crate::secp256k1_program::id(),
accounts: vec![],
data: vec![],
};
let mut secp_instruction2 = Instruction {
program_id: crate::secp256k1_program::id(),
accounts: vec![],
data: vec![1],
};
let message = Message::new(
&[
ix0.clone(),
secp_instruction.clone(),
secp_instruction2.clone(),
],
Some(&pubkey0),
);
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
secp_instruction.data = vec![0];
secp_instruction2.data = vec![10];
let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
}
#[test]
fn test_fee_rate_governor_derived_default() {
solana_logger::setup();
let f0 = FeeRateGovernor::default();
assert_eq!(
f0.target_signatures_per_slot,
DEFAULT_TARGET_SIGNATURES_PER_SLOT
);
assert_eq!(
f0.target_lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
);
assert_eq!(f0.lamports_per_signature, 0);
let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
assert_eq!(
f1.target_signatures_per_slot,
DEFAULT_TARGET_SIGNATURES_PER_SLOT
);
assert_eq!(
f1.target_lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
);
assert_eq!(
f1.lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
); // min
}
#[test]
fn test_fee_rate_governor_derived_adjust() {
solana_logger::setup();
let mut f = FeeRateGovernor {
target_lamports_per_signature: 100,
target_signatures_per_slot: 100,
..FeeRateGovernor::default()
};
f = FeeRateGovernor::new_derived(&f, 0);
// Ramp fees up
let mut count = 0;
loop {
let last_lamports_per_signature = f.lamports_per_signature;
f = FeeRateGovernor::new_derived(&f, std::u64::MAX);
info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
// some maximum target reached
if f.lamports_per_signature == last_lamports_per_signature {
break;
}
// shouldn't take more than 1000 steps to get to minimum
assert!(count < 1000);
count += 1;
}
// Ramp fees down
let mut count = 0;
loop {
let last_lamports_per_signature = f.lamports_per_signature;
f = FeeRateGovernor::new_derived(&f, 0);
info!(
"[down] f.lamports_per_signature={}",
f.lamports_per_signature
);
// some minimum target reached
if f.lamports_per_signature == last_lamports_per_signature {
break;
}
// shouldn't take more than 1000 steps to get to minimum
assert!(count < 1000);
count += 1;
}
// Arrive at target rate
let mut count = 0;
while f.lamports_per_signature != f.target_lamports_per_signature {
f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
info!(
"[target] f.lamports_per_signature={}",
f.lamports_per_signature
);
// shouldn't take more than 100 steps to get to target
assert!(count < 100);
count += 1;
}
}
}
*/

@ -1,101 +0,0 @@
[package]
name = "solana-sdk"
version = "1.14.13"
description = "Solana SDK"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-sdk"
readme = "README.md"
license = "Apache-2.0"
edition = "2021"
[features]
# "program" feature is a legacy feature retained to support v1.3 and older
# programs. New development should not use this feature. Instead use the
# solana-program crate
program = []
default = [
"full" # functionality that is not compatible or needed for on-chain programs
]
full = [
"assert_matches",
"byteorder",
"chrono",
"generic-array",
"memmap2",
"rand",
"rand_chacha",
"serde_json",
# "ed25519-dalek",
"ed25519-dalek-bip32",
# "solana-logger",
"libsecp256k1",
"sha3",
"digest",
]
[dependencies]
assert_matches = { version = "1.5.0", optional = true }
base64 = "0.13"
bincode = "1.3.3"
bitflags = "1.3.1"
borsh = "0.9.3"
bs58 = "0.4.0"
bytemuck = { version = "1.11.0", features = ["derive"] }
byteorder = { version = "1.4.3", optional = true }
chrono = { default-features = false, features = ["alloc"], version = "0.4", optional = true }
derivation-path = { version = "0.2.0", default-features = false }
digest = { version = "0.10.1", optional = true }
ed25519-dalek-bip32 = { version = "0.2.0", optional = true }
ed25519-dalek = { version = "=1.0.1", git = "https://github.com/Eclipse-Laboratories-Inc/ed25519-dalek", branch = "steven/fix-deps" }
generic-array = { version = "0.14.5", default-features = false, features = ["serde", "more_lengths"], optional = true }
hmac = "0.12.1"
itertools = "0.10.3"
lazy_static = "1.4.0"
libsecp256k1 = { version = "0.6.0", optional = true }
log = "0.4.17"
memmap2 = { version = "0.5.3", optional = true }
num-derive = "0.3"
num-traits = "0.2"
pbkdf2 = { version = "0.11.0", default-features = false }
qstring = "0.7.2"
rand = { version = "0.7.0", optional = true }
rand_chacha = { version = "0.2.2", optional = true }
rustversion = "1.0.7"
serde = "1.0.138"
serde_bytes = "0.11"
serde_derive = "1.0.103"
serde_json = { version = "1.0.81", optional = true }
sha2 = "0.10.2"
sha3 = { version = "0.10.1", optional = true }
# solana-frozen-abi = { path = "../frozen-abi", version = "=1.14.13" }
# solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.14.13" }
# solana-logger = { path = "../logger", version = "=1.14.13", optional = true }
# solana-program = { path = "program", version = "=1.14.13" }
solana-sdk-macro = { path = "macro", version = "=1.14.13" }
thiserror = "1.0"
uriparse = "0.6.4"
wasm-bindgen = "0.2"
[dependencies.curve25519-dalek]
version = "3.2.1"
features = ["serde"]
git = "https://github.com/Eclipse-Laboratories-Inc/curve25519-dalek"
branch = "steven/fix-deps"
[dev-dependencies]
anyhow = "1.0.58"
hex = "0.4.3"
static_assertions = "1.1.0"
tiny-bip39 = "0.8.2"
[build-dependencies]
rustc_version = "0.4"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
crate-type = ["cdylib", "rlib"]

@ -1,23 +0,0 @@
[package]
name = "solana-sdk-macro"
version = "1.14.13"
description = "Solana SDK Macro"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-sdk-macro"
license = "Apache-2.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
bs58 = "0.4.0"
proc-macro2 = "1.0.19"
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }
rustversion = "1.0.7"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

@ -1,405 +0,0 @@
//! Convenience macro to declare a static public key and functions to interact with it
//!
//! Input: a single literal base58 string representation of a program's id
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Delimiter, Span, TokenTree};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Expr, LitByte, LitStr};
fn parse_id(
input: ParseStream,
pubkey_type: proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
parse_pubkey(&id_literal, &pubkey_type)?
} else {
let expr: Expr = input.parse()?;
quote! { #expr }
};
if !input.is_empty() {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
}
Ok(id)
}
fn id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
assert!(check_id(&id()));
}
});
}
/*
fn deprecated_id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
#[deprecated()]
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
#[deprecated()]
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
#[allow(deprecated)]
assert!(check_id(&id()));
}
});
}
struct SdkPubkey(proc_macro2::TokenStream);
impl Parse for SdkPubkey {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for SdkPubkey {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let id = &self.0;
tokens.extend(quote! {#id})
}
}
struct ProgramSdkPubkey(proc_macro2::TokenStream);
impl Parse for ProgramSdkPubkey {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkPubkey {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let id = &self.0;
tokens.extend(quote! {#id})
}
}
*/
struct Id(proc_macro2::TokenStream);
impl Parse for Id {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { Pubkey }).map(Self)
}
}
impl ToTokens for Id {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(&self.0, quote! { Pubkey }, tokens)
}
}
/*
struct IdDeprecated(proc_macro2::TokenStream);
impl Parse for IdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for IdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
}
}
struct ProgramSdkId(proc_macro2::TokenStream);
impl Parse for ProgramSdkId {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkId {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
}
}
struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
impl Parse for ProgramSdkIdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkIdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
}
}
#[allow(dead_code)] // `respan` may be compiled out
struct RespanInput {
to_respan: Path,
respan_using: Span,
}
impl Parse for RespanInput {
fn parse(input: ParseStream) -> Result<Self> {
let to_respan: Path = input.parse()?;
let _comma: Token![,] = input.parse()?;
let respan_tree: TokenTree = input.parse()?;
match respan_tree {
TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
let ident: Ident = syn::parse2(g.stream())?;
Ok(RespanInput {
to_respan,
respan_using: ident.span(),
})
}
TokenTree::Ident(i) => Ok(RespanInput {
to_respan,
respan_using: i.span(),
}),
val => Err(syn::Error::new_spanned(
val,
"expected None-delimited group",
)),
}
}
}
/// A proc-macro which respans the tokens in its first argument (a `Path`)
/// to be resolved at the tokens of its second argument.
/// For internal use only.
///
/// There must be exactly one comma in the input,
/// which is used to separate the two arguments.
/// The second argument should be exactly one token.
///
/// For example, `respan!($crate::foo, with_span)`
/// produces the tokens `$crate::foo`, but resolved
/// at the span of `with_span`.
///
/// The input to this function should be very short -
/// its only purpose is to override the span of a token
/// sequence containing `$crate`. For all other purposes,
/// a more general proc-macro should be used.
#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
#[proc_macro]
pub fn respan(input: TokenStream) -> TokenStream {
// Obtain the `Path` we are going to respan, and the ident
// whose span we will be using.
let RespanInput {
to_respan,
respan_using,
} = parse_macro_input!(input as RespanInput);
// Respan all of the tokens in the `Path`
let to_respan: proc_macro2::TokenStream = to_respan
.into_token_stream()
.into_iter()
.map(|mut t| {
// Combine the location of the token with the resolution behavior of `respan_using`
let new_span: Span = t.span().resolved_at(respan_using);
t.set_span(new_span);
t
})
.collect();
TokenStream::from(to_respan)
}
#[proc_macro]
pub fn pubkey(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as SdkPubkey);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_pubkey(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkPubkey);
TokenStream::from(quote! {#id})
}
*/
#[proc_macro]
pub fn declare_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as Id);
TokenStream::from(quote! {#id})
}
/*
#[proc_macro]
pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as IdDeprecated);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_declare_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkId);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
TokenStream::from(quote! {#id})
}
*/
fn parse_pubkey(
id_literal: &LitStr,
pubkey_type: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id_vec = bs58::decode(id_literal.value())
.into_vec()
.map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
syn::Error::new_spanned(
id_literal,
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
)
})?;
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
Ok(quote! {
#pubkey_type::new_from_array(
[#(#bytes,)*]
)
})
}
/*
struct Pubkeys {
method: Ident,
num: usize,
pubkeys: proc_macro2::TokenStream,
}
impl Parse for Pubkeys {
fn parse(input: ParseStream) -> Result<Self> {
let pubkey_type = quote! {
::solana_sdk::pubkey::Pubkey
};
let method = input.parse()?;
let _comma: Token![,] = input.parse()?;
let (num, pubkeys) = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
(1, parse_pubkey(&id_literal, &pubkey_type)?)
} else if input.peek(Bracket) {
let pubkey_strings;
bracketed!(pubkey_strings in input);
let punctuated: Punctuated<LitStr, Token![,]> =
Punctuated::parse_terminated(&pubkey_strings)?;
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
for string in punctuated.iter() {
pubkeys.push(parse_pubkey(string, &pubkey_type)?);
}
(pubkeys.len(), quote! {#pubkeys})
} else {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
};
Ok(Pubkeys {
method,
num,
pubkeys,
})
}
}
impl ToTokens for Pubkeys {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Pubkeys {
method,
num,
pubkeys,
} = self;
let pubkey_type = quote! {
::solana_sdk::pubkey::Pubkey
};
if *num == 1 {
tokens.extend(quote! {
pub fn #method() -> #pubkey_type {
#pubkeys
}
});
} else {
tokens.extend(quote! {
pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
vec![#pubkeys]
}
});
}
}
}
#[proc_macro]
pub fn pubkeys(input: TokenStream) -> TokenStream {
let pubkeys = parse_macro_input!(input as Pubkeys);
TokenStream::from(quote! {#pubkeys})
}
// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
// BPF program to fail to load, so for now this stub should be used when building for BPF
#[proc_macro_attribute]
pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
match parse_macro_input!(item as syn::Item) {
syn::Item::Struct(mut item_struct) => {
if let syn::Fields::Named(fields) = &mut item_struct.fields {
// Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
// syntax supplied by the normal `wasm_bindgen` macro.
for field in fields.named.iter_mut() {
field.attrs.retain(|attr| {
!attr
.path
.segments
.iter()
.any(|segment| segment.ident == "wasm_bindgen")
});
}
}
quote! { #item_struct }
}
item => {
quote!(#item)
}
}
.into()
}
*/

@ -1 +0,0 @@
pub use solana_sdk_macro::declare_id;

@ -1,12 +0,0 @@
//! The [secp256k1 native program][np].
//!
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program
//!
//! Constructors for secp256k1 program instructions, and documentation on the
//! program's usage can be found in [`solana_sdk::secp256k1_instruction`].
//!
//! [`solana_sdk::secp256k1_instruction`]: https://docs.rs/solana-sdk/latest/solana_sdk/secp256k1_instruction/index.html
use crate::solana::pubkey::Pubkey;
use solana_sdk_macro::declare_id;
declare_id!("KeccakSecp256k11111111111111111111111111111");

@ -1 +0,0 @@
pub use solana_sdk_macro::declare_id;

@ -1,423 +0,0 @@
//! Convenience macro to declare a static public key and functions to interact with it
//!
//! Input: a single literal base58 string representation of a program's id
extern crate proc_macro;
use proc_macro::{TokenStream};
use syn::{parse_macro_input, LitStr, Expr, LitByte};
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result};
use proc_macro2::{Delimiter, Span, TokenTree};
use {
// proc_macro::TokenStream,
// proc_macro2::{Delimiter, Span, TokenTree},
// quote::{quote, ToTokens},
// std::convert::TryFrom,
// syn::{
// bracketed,
// parse::{Parse, ParseStream, Result},
// parse_macro_input,
// punctuated::Punctuated,
// token::Bracket,
// Expr, Ident, LitByte, LitStr, Path, Token,
// },
};
fn parse_id(
input: ParseStream,
pubkey_type: proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
parse_pubkey(&id_literal, &pubkey_type)?
} else {
let expr: Expr = input.parse()?;
quote! { #expr }
};
if !input.is_empty() {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
}
Ok(id)
}
fn id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
assert!(check_id(&id()));
}
});
}
/*
fn deprecated_id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
#[deprecated()]
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
#[deprecated()]
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
#[allow(deprecated)]
assert!(check_id(&id()));
}
});
}
struct SdkPubkey(proc_macro2::TokenStream);
impl Parse for SdkPubkey {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for SdkPubkey {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let id = &self.0;
tokens.extend(quote! {#id})
}
}
struct ProgramSdkPubkey(proc_macro2::TokenStream);
impl Parse for ProgramSdkPubkey {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkPubkey {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let id = &self.0;
tokens.extend(quote! {#id})
}
}
*/
struct Id(proc_macro2::TokenStream);
impl Parse for Id {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for Id {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
}
}
/*
struct IdDeprecated(proc_macro2::TokenStream);
impl Parse for IdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for IdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
}
}
struct ProgramSdkId(proc_macro2::TokenStream);
impl Parse for ProgramSdkId {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkId {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
}
}
struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
impl Parse for ProgramSdkIdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
}
}
impl ToTokens for ProgramSdkIdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
}
}
#[allow(dead_code)] // `respan` may be compiled out
struct RespanInput {
to_respan: Path,
respan_using: Span,
}
impl Parse for RespanInput {
fn parse(input: ParseStream) -> Result<Self> {
let to_respan: Path = input.parse()?;
let _comma: Token![,] = input.parse()?;
let respan_tree: TokenTree = input.parse()?;
match respan_tree {
TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
let ident: Ident = syn::parse2(g.stream())?;
Ok(RespanInput {
to_respan,
respan_using: ident.span(),
})
}
TokenTree::Ident(i) => Ok(RespanInput {
to_respan,
respan_using: i.span(),
}),
val => Err(syn::Error::new_spanned(
val,
"expected None-delimited group",
)),
}
}
}
/// A proc-macro which respans the tokens in its first argument (a `Path`)
/// to be resolved at the tokens of its second argument.
/// For internal use only.
///
/// There must be exactly one comma in the input,
/// which is used to separate the two arguments.
/// The second argument should be exactly one token.
///
/// For example, `respan!($crate::foo, with_span)`
/// produces the tokens `$crate::foo`, but resolved
/// at the span of `with_span`.
///
/// The input to this function should be very short -
/// its only purpose is to override the span of a token
/// sequence containing `$crate`. For all other purposes,
/// a more general proc-macro should be used.
#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
#[proc_macro]
pub fn respan(input: TokenStream) -> TokenStream {
// Obtain the `Path` we are going to respan, and the ident
// whose span we will be using.
let RespanInput {
to_respan,
respan_using,
} = parse_macro_input!(input as RespanInput);
// Respan all of the tokens in the `Path`
let to_respan: proc_macro2::TokenStream = to_respan
.into_token_stream()
.into_iter()
.map(|mut t| {
// Combine the location of the token with the resolution behavior of `respan_using`
let new_span: Span = t.span().resolved_at(respan_using);
t.set_span(new_span);
t
})
.collect();
TokenStream::from(to_respan)
}
#[proc_macro]
pub fn pubkey(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as SdkPubkey);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_pubkey(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkPubkey);
TokenStream::from(quote! {#id})
}
*/
#[proc_macro]
pub fn declare_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as Id);
TokenStream::from(quote! {#id})
}
/*
#[proc_macro]
pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as IdDeprecated);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_declare_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkId);
TokenStream::from(quote! {#id})
}
#[proc_macro]
pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
TokenStream::from(quote! {#id})
}
*/
fn parse_pubkey(
id_literal: &LitStr,
pubkey_type: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id_vec = bs58::decode(id_literal.value())
.into_vec()
.map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
syn::Error::new_spanned(
id_literal,
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
)
})?;
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
Ok(quote! {
#pubkey_type::new_from_array(
[#(#bytes,)*]
)
})
}
/*
struct Pubkeys {
method: Ident,
num: usize,
pubkeys: proc_macro2::TokenStream,
}
impl Parse for Pubkeys {
fn parse(input: ParseStream) -> Result<Self> {
let pubkey_type = quote! {
::solana_sdk::pubkey::Pubkey
};
let method = input.parse()?;
let _comma: Token![,] = input.parse()?;
let (num, pubkeys) = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
(1, parse_pubkey(&id_literal, &pubkey_type)?)
} else if input.peek(Bracket) {
let pubkey_strings;
bracketed!(pubkey_strings in input);
let punctuated: Punctuated<LitStr, Token![,]> =
Punctuated::parse_terminated(&pubkey_strings)?;
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
for string in punctuated.iter() {
pubkeys.push(parse_pubkey(string, &pubkey_type)?);
}
(pubkeys.len(), quote! {#pubkeys})
} else {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
};
Ok(Pubkeys {
method,
num,
pubkeys,
})
}
}
impl ToTokens for Pubkeys {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Pubkeys {
method,
num,
pubkeys,
} = self;
let pubkey_type = quote! {
::solana_sdk::pubkey::Pubkey
};
if *num == 1 {
tokens.extend(quote! {
pub fn #method() -> #pubkey_type {
#pubkeys
}
});
} else {
tokens.extend(quote! {
pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
vec![#pubkeys]
}
});
}
}
}
#[proc_macro]
pub fn pubkeys(input: TokenStream) -> TokenStream {
let pubkeys = parse_macro_input!(input as Pubkeys);
TokenStream::from(quote! {#pubkeys})
}
// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
// BPF program to fail to load, so for now this stub should be used when building for BPF
#[proc_macro_attribute]
pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
match parse_macro_input!(item as syn::Item) {
syn::Item::Struct(mut item_struct) => {
if let syn::Fields::Named(fields) = &mut item_struct.fields {
// Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
// syntax supplied by the normal `wasm_bindgen` macro.
for field in fields.named.iter_mut() {
field.attrs.retain(|attr| {
!attr
.path
.segments
.iter()
.any(|segment| segment.ident == "wasm_bindgen")
});
}
}
quote! { #item_struct }
}
item => {
quote!(#item)
}
}
.into()
}
*/

@ -7,6 +7,8 @@ edition = "2021"
[dependencies]
borsh.workspace = true
bs58.workspace = true
bincode.workspace = true
clap = { workspace = true, features = ["derive"] }
hex.workspace = true
pretty_env_logger.workspace = true

@ -24,7 +24,7 @@ pub(crate) fn account_exists(client: &RpcClient, account: &Pubkey) -> Result<boo
}
pub(crate) fn deploy_program_idempotent(
payer_path: &str,
payer_keypair_path: &str,
program_keypair: &Keypair,
program_keypair_path: &str,
program_path: &str,
@ -32,7 +32,7 @@ pub(crate) fn deploy_program_idempotent(
) -> Result<(), ClientError> {
let client = RpcClient::new(url.to_string());
if !account_exists(&client, &program_keypair.pubkey())? {
deploy_program(payer_path, program_keypair_path, program_path, url);
deploy_program(payer_keypair_path, program_keypair_path, program_path, url);
} else {
println!("Program {} already deployed", program_keypair.pubkey());
}
@ -41,7 +41,7 @@ pub(crate) fn deploy_program_idempotent(
}
pub(crate) fn deploy_program(
payer_path: &str,
payer_keypair_path: &str,
program_keypair_path: &str,
program_path: &str,
url: &str,
@ -52,12 +52,12 @@ pub(crate) fn deploy_program(
"--url",
url,
"-k",
payer_path,
payer_keypair_path,
"program",
"deploy",
program_path,
"--upgrade-authority",
payer_path,
payer_keypair_path,
"--program-id",
program_keypair_path,
],

@ -1,41 +1,128 @@
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::instruction::Instruction;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::signers::Signers;
use solana_sdk::transaction::Transaction;
use solana_client::{
rpc_client::RpcClient,
rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig},
};
use solana_sdk::{
commitment_config::CommitmentConfig,
instruction::Instruction,
message::Message,
pubkey::Pubkey,
signature::{Keypair, Signer},
signer::null_signer::NullSigner,
signers::Signers,
transaction::Transaction,
};
use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding};
use std::cell::RefCell;
use std::{cell::RefCell, io::Read};
pub(crate) struct PayerKeypair {
pub keypair: Keypair,
pub keypair_path: String,
}
pub(crate) struct Context {
pub client: RpcClient,
pub payer: Keypair,
pub payer_path: String,
pub payer_pubkey: Pubkey,
payer_keypair: Option<PayerKeypair>,
pub commitment: CommitmentConfig,
// TODO: can we remove this?
pub initial_instructions: RefCell<Vec<Instruction>>,
pub initial_instructions: RefCell<Vec<InstructionWithDescription>>,
}
pub(crate) struct InstructionWithDescription {
pub instruction: Instruction,
pub description: Option<String>,
}
impl<T> From<(T, Option<String>)> for InstructionWithDescription
where
T: Into<Instruction>,
{
fn from((instruction, description): (T, Option<String>)) -> Self {
Self {
instruction: instruction.into(),
description,
}
}
}
pub(crate) struct TxnBuilder<'ctx, 'rpc> {
ctx: &'ctx Context,
client: Option<&'rpc RpcClient>,
instructions: Vec<Instruction>,
instructions_with_descriptions: Vec<InstructionWithDescription>,
}
impl Context {
pub(crate) fn new(
client: RpcClient,
payer_pubkey: Pubkey,
payer_keypair: Option<PayerKeypair>,
commitment: CommitmentConfig,
initial_instructions: RefCell<Vec<InstructionWithDescription>>,
) -> Self {
Self {
client,
payer_pubkey,
payer_keypair,
commitment,
initial_instructions,
}
}
pub(crate) fn new_txn(&self) -> TxnBuilder {
TxnBuilder {
ctx: self,
client: None,
instructions: self.initial_instructions.borrow_mut().drain(..).collect(),
instructions_with_descriptions: self
.initial_instructions
.borrow_mut()
.drain(..)
.collect(),
}
}
pub(crate) fn payer_can_sign(&self) -> bool {
self.payer_keypair.is_some()
}
pub(crate) fn payer_signer(&self) -> Box<dyn Signer> {
if let Some(PayerKeypair { keypair, .. }) = &self.payer_keypair {
Box::new(Keypair::from_bytes(&keypair.to_bytes()).unwrap())
} else {
Box::new(NullSigner::new(&self.payer_pubkey))
}
}
pub(crate) fn payer_keypair_path(&self) -> &String {
&self
.payer_keypair
.as_ref()
.expect("No payer keypair path")
.keypair_path
}
}
impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
pub(crate) fn add(mut self, instruction: Instruction) -> Self {
self.instructions.push(instruction);
pub(crate) fn add(self, instruction: Instruction) -> Self {
self.add_with_optional_description(instruction, None)
}
pub(crate) fn add_with_description<T>(self, instruction: Instruction, description: T) -> Self
where
T: Into<String>,
{
self.add_with_optional_description(instruction, Some(description.into()))
}
fn add_with_optional_description(
mut self,
instruction: Instruction,
description: Option<String>,
) -> Self {
self.instructions_with_descriptions
.push(InstructionWithDescription {
instruction,
description,
});
self
}
@ -44,21 +131,64 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
self
}
pub(crate) fn instructions(&self) -> Vec<Instruction> {
self.instructions_with_descriptions
.iter()
.map(|i| i.instruction.clone())
.collect()
}
pub(crate) fn pretty_print_transaction(&self) {
println!("\t==== Instructions: ====");
for (i, InstructionWithDescription { description, .. }) in
self.instructions_with_descriptions.iter().enumerate()
{
println!(
"\tInstruction {}: {}",
i,
description.as_deref().unwrap_or("No description provided")
);
}
let message = Message::new(&self.instructions(), None);
let txn = Transaction::new_unsigned(message);
println!(
"\t==== Transaction in base58: ====\n\t{}",
bs58::encode(bincode::serialize(&txn).unwrap()).into_string()
);
}
pub(crate) fn send_with_payer(self) -> Option<EncodedConfirmedTransactionWithStatusMeta> {
let payer = &self.ctx.payer;
self.send(&[payer])
let payer_signer = self.ctx.payer_signer();
self.send(&[&*payer_signer])
}
pub(crate) fn send<T: Signers>(
self,
signers: &T,
) -> Option<EncodedConfirmedTransactionWithStatusMeta> {
// If the payer can't sign, it's presumed that the payer is intended
// to be a Squads multisig, which must be submitted via a separate
// process.
// We print the transaction to stdout and wait for user confirmation to
// continue.
if !self.ctx.payer_can_sign() {
println!("Transaction to be submitted via Squads multisig:");
self.pretty_print_transaction();
wait_for_user_confirmation();
return None;
}
let client = self.client.unwrap_or(&self.ctx.client);
let recent_blockhash = client.get_latest_blockhash().unwrap();
let txn = Transaction::new_signed_with_payer(
&self.instructions,
Some(&self.ctx.payer.pubkey()),
&self.instructions(),
Some(&self.ctx.payer_pubkey),
signers,
recent_blockhash,
);
@ -94,3 +224,19 @@ impl<'ctx, 'rpc> TxnBuilder<'ctx, 'rpc> {
.ok()
}
}
// Poor man's strategy for waiting for user confirmation
fn wait_for_user_confirmation() {
println!("Continue? [y/n] then press Enter");
let mut input = [0u8; 1];
std::io::stdin().read_exact(&mut input).unwrap();
match input[0] {
b'y' => {
println!("Continuing...");
}
b'n' => {
panic!("User requested exit");
}
_ => {}
}
}

@ -53,7 +53,7 @@ fn deploy_multisig_ism_message_id(ctx: &mut Context, cmd: &CoreDeploy, key_dir:
let program_id = keypair.pubkey();
deploy_program(
&ctx.payer_path,
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
cmd.built_so_dir
.join("hyperlane_sealevel_multisig_ism_message_id.so")
@ -70,7 +70,7 @@ fn deploy_multisig_ism_message_id(ctx: &mut Context, cmd: &CoreDeploy, key_dir:
// Initialize
let instruction = hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
)
.unwrap();
@ -95,7 +95,7 @@ fn deploy_mailbox(
let program_id = keypair.pubkey();
deploy_program(
&ctx.payer_path,
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
core.built_so_dir
.join("hyperlane_sealevel_mailbox.so")
@ -111,7 +111,7 @@ fn deploy_mailbox(
program_id,
core.local_domain,
default_ism,
ctx.payer.pubkey(),
ctx.payer_pubkey,
)
.unwrap();
@ -136,7 +136,7 @@ fn deploy_validator_announce(
let program_id = keypair.pubkey();
deploy_program(
&ctx.payer_path,
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
core.built_so_dir
.join("hyperlane_sealevel_validator_announce.so")
@ -150,7 +150,7 @@ fn deploy_validator_announce(
// Initialize
let instruction = hyperlane_sealevel_validator_announce::instruction::init_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
mailbox_program_id,
core.local_domain,
)
@ -221,7 +221,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
.collect::<Vec<_>>();
deploy_program(
&ctx.payer_path,
ctx.payer_keypair_path(),
keypair_path.to_str().unwrap(),
core.built_so_dir
.join("hyperlane_sealevel_igp.so")
@ -234,7 +234,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
// Initialize the program data
let instruction =
hyperlane_sealevel_igp::instruction::init_instruction(program_id, ctx.payer.pubkey())
hyperlane_sealevel_igp::instruction::init_instruction(program_id, ctx.payer_pubkey)
.unwrap();
ctx.new_txn().add(instruction).send_with_payer();
@ -249,10 +249,10 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
let salt = H256::zero();
let instruction = hyperlane_sealevel_igp::instruction::init_igp_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
salt,
Some(ctx.payer.pubkey()),
ctx.payer.pubkey(),
Some(ctx.payer_pubkey),
ctx.payer_pubkey,
)
.unwrap();
@ -264,9 +264,9 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
let instruction = hyperlane_sealevel_igp::instruction::init_overhead_igp_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
salt,
Some(ctx.payer.pubkey()),
Some(ctx.payer_pubkey),
igp_account,
)
.unwrap();
@ -288,7 +288,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction(
program_id,
igp_account,
ctx.payer.pubkey(),
ctx.payer_pubkey,
gas_oracle_configs,
)
.unwrap();
@ -309,7 +309,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads(
program_id,
overhead_igp_account,
ctx.payer.pubkey(),
ctx.payer_pubkey,
overhead_configs,
)
.unwrap();

@ -184,6 +184,7 @@ enum MailboxSubCmd {
Send(Outbox),
Receive(Inbox),
Delivered(Delivered),
TransferOwnership(TransferOwnership),
}
const MAILBOX_PROG_ID: Pubkey = pubkey!("692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1");
@ -218,8 +219,6 @@ struct Outbox {
message: String,
#[arg(long, short, default_value_t = MAILBOX_PROG_ID)]
program_id: Pubkey,
// #[arg(long, short, default_value_t = MAX_MESSAGE_BODY_BYTES)]
// message_len: usize,
}
#[derive(Args)]
@ -285,6 +284,7 @@ enum TokenSubCmd {
Query(TokenQuery),
TransferRemote(TokenTransferRemote),
EnrollRemoteRouter(TokenEnrollRemoteRouter),
TransferOwnership(TransferOwnership),
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@ -324,6 +324,15 @@ struct TokenEnrollRemoteRouter {
router: H256,
}
#[derive(Args)]
struct TransferOwnership {
#[arg(long, short)]
program_id: Pubkey,
// To avoid accidentally transferring ownership to None,
// only support transferring to other Pubkeys for now.
new_owner: Pubkey,
}
#[derive(Args)]
struct IgpCmd {
#[command(subcommand)]
@ -333,6 +342,19 @@ struct IgpCmd {
#[derive(Subcommand)]
enum IgpSubCmd {
PayForGas(PayForGasArgs),
TransferIgpOwnership(TransferIgpOwnership),
TransferOverheadIgpOwnership(TransferIgpOwnership),
}
#[derive(Args)]
struct TransferIgpOwnership {
#[arg(long, short)]
program_id: Pubkey,
// To avoid accidentally transferring ownership to None,
// only support transferring to other Pubkeys for now.
new_owner: Pubkey,
#[arg(long)]
igp_account: Pubkey,
}
#[derive(Args)]
@ -394,6 +416,7 @@ enum MultisigIsmMessageIdSubCmd {
Init(MultisigIsmMessageIdInit),
SetValidatorsAndThreshold(MultisigIsmMessageIdSetValidatorsAndThreshold),
Query(MultisigIsmMessageIdInit),
TransferOwnership(TransferOwnership),
}
#[derive(Args)]
@ -426,32 +449,57 @@ fn main() {
};
let url = normalize_to_url_if_moniker(cli.url.unwrap_or(config.json_rpc_url));
is_url(&url).unwrap();
let client = RpcClient::new(url);
let keypair_path = cli.keypair.unwrap_or(config.keypair_path);
is_keypair(&keypair_path).unwrap();
let (payer_pubkey, payer_keypair) = if let Ok(payer_keypair) = read_keypair_file(&keypair_path)
{
(
payer_keypair.pubkey(),
Some(PayerKeypair {
keypair: payer_keypair,
keypair_path,
}),
)
} else {
println!(
"Provided key is not a keypair file, treating as a public key {}",
keypair_path
);
(Pubkey::from_str(&keypair_path).unwrap(), None)
};
let client = RpcClient::new(url);
let payer = read_keypair_file(keypair_path.clone()).unwrap();
let commitment = CommitmentConfig::processed();
let mut instructions = vec![];
if cli.compute_budget != DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT {
assert!(cli.compute_budget <= MAX_COMPUTE_UNIT_LIMIT);
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
cli.compute_budget,
));
instructions.push(
(
ComputeBudgetInstruction::set_compute_unit_limit(cli.compute_budget),
Some(format!("Set compute unit limit to {}", cli.compute_budget)),
)
.into(),
);
}
if let Some(heap_size) = cli.heap_size {
assert!(heap_size <= MAX_HEAP_FRAME_BYTES);
instructions.push(ComputeBudgetInstruction::request_heap_frame(heap_size));
instructions.push(
(
ComputeBudgetInstruction::request_heap_frame(heap_size),
Some(format!("Request heap frame of {} bytes", heap_size)),
)
.into(),
);
}
let ctx = Context {
let ctx = Context::new(
client,
payer,
payer_path: keypair_path,
payer_pubkey,
payer_keypair,
commitment,
initial_instructions: instructions.into(),
};
instructions.into(),
);
match cli.cmd {
HyperlaneSealevelCmd::Mailbox(cmd) => process_mailbox_cmd(ctx, cmd),
HyperlaneSealevelCmd::Token(cmd) => process_token_cmd(ctx, cmd),
@ -472,7 +520,7 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
init.program_id,
init.local_domain,
init.default_ism,
ctx.payer.pubkey(),
ctx.payer_pubkey,
)
.unwrap();
@ -520,7 +568,7 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
let (outbox_account, _outbox_bump) =
Pubkey::find_program_address(mailbox_outbox_pda_seeds!(), &outbox.program_id);
let ixn = MailboxInstruction::OutboxDispatch(OutboxDispatch {
sender: ctx.payer.pubkey(),
sender: ctx.payer_pubkey,
destination_domain: outbox.destination,
recipient: H256(outbox.recipient.to_bytes()),
message_body: outbox.message.into(),
@ -531,7 +579,7 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
data: ixn.into_instruction_data().unwrap(),
accounts: vec![
AccountMeta::new(outbox_account, false),
AccountMeta::new_readonly(ctx.payer.pubkey(), true),
AccountMeta::new_readonly(ctx.payer_pubkey, true),
AccountMeta::new_readonly(spl_noop::id(), false),
],
};
@ -598,6 +646,21 @@ fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
println!("Message delivered");
}
}
MailboxSubCmd::TransferOwnership(transfer_ownership) => {
let instruction =
hyperlane_sealevel_mailbox::instruction::transfer_ownership_instruction(
transfer_ownership.program_id,
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Transfer ownership to {}", transfer_ownership.new_owner),
)
.send_with_payer();
}
};
}
@ -934,7 +997,7 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
accounts,
};
let tx_result = ctx.new_txn().add(xfer_instruction).send(&[
&ctx.payer,
&*ctx.payer_signer(),
&sender,
&unique_message_account_keypair,
]);
@ -954,11 +1017,27 @@ fn process_token_cmd(ctx: Context, cmd: TokenCmd) {
data: enroll_instruction.encode().unwrap(),
accounts: vec![
AccountMeta::new(token_account, false),
AccountMeta::new_readonly(ctx.payer.pubkey(), true),
AccountMeta::new_readonly(ctx.payer_pubkey, true),
],
};
ctx.new_txn().add(instruction).send_with_payer();
}
TokenSubCmd::TransferOwnership(transfer) => {
let instruction =
hyperlane_sealevel_token_lib::instruction::transfer_ownership_instruction(
transfer.program_id,
ctx.payer_pubkey,
Some(transfer.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Transfer ownership to {}", transfer.new_owner),
)
.send_with_payer();
}
}
}
@ -968,7 +1047,7 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) {
let init_instruction =
hyperlane_sealevel_validator_announce::instruction::init_instruction(
init.program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
init.mailbox_id,
init.local_domain,
)
@ -1014,7 +1093,7 @@ fn process_validator_announce_cmd(ctx: Context, cmd: ValidatorAnnounceCmd) {
// 3. [writeable] The validator-specific ValidatorStorageLocationsAccount PDA account.
// 4. [writeable] The ReplayProtection PDA account specific to the announcement being made.
let accounts = vec![
AccountMeta::new_readonly(ctx.payer.pubkey(), true),
AccountMeta::new_readonly(ctx.payer_pubkey, true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(validator_announce_account, false),
AccountMeta::new(validator_storage_locations_key, false),
@ -1062,7 +1141,7 @@ fn process_multisig_ism_message_id_cmd(ctx: Context, cmd: MultisigIsmMessageIdCm
let init_instruction =
hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction(
init.program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
)
.unwrap();
ctx.new_txn().add(init_instruction).send_with_payer();
@ -1092,7 +1171,7 @@ fn process_multisig_ism_message_id_cmd(ctx: Context, cmd: MultisigIsmMessageIdCm
// 2. `[writable]` The PDA relating to the provided domain.
// 3. `[executable]` OPTIONAL - The system program account. Required if creating the domain PDA.
let accounts = vec![
AccountMeta::new(ctx.payer.pubkey(), true),
AccountMeta::new(ctx.payer_pubkey, true),
AccountMeta::new_readonly(access_control_pda_key, false),
AccountMeta::new(domain_data_pda_key, false),
AccountMeta::new_readonly(system_program::id(), false),
@ -1122,6 +1201,22 @@ fn process_multisig_ism_message_id_cmd(ctx: Context, cmd: MultisigIsmMessageIdCm
.into_inner();
println!("Access control: {:#?}", access_control);
}
MultisigIsmMessageIdSubCmd::TransferOwnership(transfer_ownership) => {
let instruction =
hyperlane_sealevel_multisig_ism_message_id::instruction::transfer_ownership_instruction(
transfer_ownership.program_id,
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!("Transfer ownership to {}", transfer_ownership.new_owner),
)
.send_with_payer();
}
}
}
@ -1142,7 +1237,7 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
let (ixn, gas_payment_data_account) =
hyperlane_sealevel_igp::instruction::pay_for_gas_instruction(
payment_details.program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
igp_account,
Some(overhead_igp_account),
unique_gas_payment_keypair.pubkey(),
@ -1154,12 +1249,41 @@ fn process_igp_cmd(ctx: Context, cmd: IgpCmd) {
ctx.new_txn()
.add(ixn)
.send(&[&ctx.payer, &unique_gas_payment_keypair]);
.send(&[&*ctx.payer_signer(), &unique_gas_payment_keypair]);
println!(
"Made a payment for message {} with gas payment data account {}",
payment_details.message_id, gas_payment_data_account
);
}
IgpSubCmd::TransferIgpOwnership(ref transfer_ownership)
| IgpSubCmd::TransferOverheadIgpOwnership(ref transfer_ownership) => {
let igp_account_type = match cmd.cmd {
IgpSubCmd::TransferIgpOwnership(_) => {
InterchainGasPaymasterType::Igp(transfer_ownership.igp_account)
}
IgpSubCmd::TransferOverheadIgpOwnership(_) => {
InterchainGasPaymasterType::OverheadIgp(transfer_ownership.igp_account)
}
_ => unreachable!(),
};
let instruction =
hyperlane_sealevel_igp::instruction::transfer_igp_account_ownership_instruction(
transfer_ownership.program_id,
igp_account_type.clone(),
ctx.payer_pubkey,
Some(transfer_ownership.new_owner),
)
.unwrap();
ctx.new_txn()
.add_with_description(
instruction,
format!(
"Transfer ownership of {:?} to {}",
igp_account_type, transfer_ownership.new_owner
),
)
.send_with_payer();
}
}
}

@ -290,7 +290,7 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
.add(
enroll_remote_routers_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
router_configs,
)
.unwrap(),
@ -356,7 +356,7 @@ pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) {
.add(
set_destination_gas_configs(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
destination_gas_configs,
)
.unwrap(),
@ -419,7 +419,7 @@ fn deploy_warp_route(
let program_id = keypair.pubkey();
deploy_program_idempotent(
&ctx.payer_path,
ctx.payer_keypair_path(),
&keypair,
keypair_path.to_str().unwrap(),
built_so_dir
@ -519,7 +519,7 @@ fn fund_ata_payer_up_to(
);
ctx.new_txn()
.add(solana_program::system_instruction::transfer(
&ctx.payer.pubkey(),
&ctx.payer_pubkey,
&ata_payer_account,
funding_amount,
))
@ -575,7 +575,7 @@ fn init_warp_route(
TokenType::Native => ctx.new_txn().add(
hyperlane_sealevel_token_native::instruction::init_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
init,
)?,
),
@ -586,7 +586,7 @@ fn init_warp_route(
ctx.new_txn()
.add(hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
init,
)?);
@ -607,7 +607,7 @@ fn init_warp_route(
TokenType::Collateral(collateral_info) => ctx.new_txn().add(
hyperlane_sealevel_token_collateral::instruction::init_instruction(
program_id,
ctx.payer.pubkey(),
ctx.payer_pubkey,
init,
collateral_info
.spl_token_program

@ -171,3 +171,32 @@ pub fn set_destination_gas_configs(
Ok(instruction)
}
/// Transfers ownership.
pub fn transfer_ownership_instruction(
program_id: Pubkey,
owner_payer: Pubkey,
new_owner: Option<Pubkey>,
) -> Result<SolanaInstruction, ProgramError> {
let (token_key, _token_bump) =
Pubkey::try_find_program_address(hyperlane_token_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
let ixn = Instruction::TransferOwnership(new_owner);
// Accounts:
// 0. [writeable] The token PDA account.
// 1. [signer] The current owner.
let accounts = vec![
AccountMeta::new(token_key, false),
AccountMeta::new_readonly(owner_payer, true),
];
let instruction = SolanaInstruction {
program_id,
data: ixn.encode()?,
accounts,
};
Ok(instruction)
}

@ -10,8 +10,8 @@ use solana_program::{
};
use crate::{
accounts::GasOracle, igp_gas_payment_pda_seeds, igp_pda_seeds, igp_program_data_pda_seeds,
overhead_igp_pda_seeds,
accounts::{GasOracle, InterchainGasPaymasterType},
igp_gas_payment_pda_seeds, igp_pda_seeds, igp_program_data_pda_seeds, overhead_igp_pda_seeds,
};
/// The program instructions.
@ -316,3 +316,34 @@ pub fn pay_for_gas_instruction(
Ok((instruction, gas_payment_account))
}
/// Gets an instruction to change an IGP or Overhead IGP
/// account's owner.
pub fn transfer_igp_account_ownership_instruction(
program_id: Pubkey,
igp_account_type: InterchainGasPaymasterType,
owner_payer: Pubkey,
new_owner: Option<Pubkey>,
) -> Result<SolanaInstruction, ProgramError> {
let (igp_account, instruction) = match igp_account_type {
InterchainGasPaymasterType::Igp(igp_account) => {
(igp_account, Instruction::TransferIgpOwnership(new_owner))
}
InterchainGasPaymasterType::OverheadIgp(igp_account) => (
igp_account,
Instruction::TransferOverheadIgpOwnership(new_owner),
),
};
// 0. [writeable] The IGP or OverheadIGP.
// 1. [signer] The owner of the IGP account.
let instruction = SolanaInstruction {
program_id,
data: instruction.try_to_vec()?,
accounts: vec![
AccountMeta::new(igp_account, false),
AccountMeta::new(owner_payer, true),
],
};
Ok(instruction)
}

@ -485,7 +485,7 @@ fn set_igp_beneficiary(
/// Transfers ownership of an IGP variant.
///
/// Accounts:
/// 0. [] The IGP or OverheadIGP.
/// 0. [writeable] The IGP or OverheadIGP.
/// 1. [signer] The owner of the IGP account.
fn transfer_igp_variant_ownership<T: account_utils::DiscriminatorPrefixedData + AccessControl>(
program_id: &Pubkey,

@ -120,6 +120,29 @@ pub fn init_instruction(
Ok(instruction)
}
/// Creates a TransferOwnership instruction.
pub fn transfer_ownership_instruction(
program_id: Pubkey,
owner_payer: Pubkey,
new_owner: Option<Pubkey>,
) -> Result<SolanaInstruction, ProgramError> {
let (access_control_pda_key, _access_control_pda_bump) =
Pubkey::try_find_program_address(access_control_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
// 0. `[signer]` The current access control owner.
// 1. `[writeable]` The access control PDA account.
let instruction = SolanaInstruction {
program_id,
data: Instruction::TransferOwnership(new_owner).encode()?,
accounts: vec![
AccountMeta::new(owner_payer, true),
AccountMeta::new(access_control_pda_key, false),
],
};
Ok(instruction)
}
#[cfg(test)]
mod test {
use super::*;

@ -506,7 +506,7 @@ fn access_control_data(
///
/// Accounts:
/// 0. `[signer]` The current access control owner.
/// 1. `[]` The access control PDA account.
/// 1. `[writeable]` The access control PDA account.
fn transfer_ownership(
program_id: &Pubkey,
accounts: &[AccountInfo],

@ -119,3 +119,26 @@ pub fn init_instruction(
};
Ok(instruction)
}
/// Creates a TransferOwnership instruction.
pub fn transfer_ownership_instruction(
program_id: Pubkey,
owner_payer: Pubkey,
new_owner: Option<Pubkey>,
) -> Result<SolanaInstruction, ProgramError> {
let (outbox_account, _outbox_bump) =
Pubkey::try_find_program_address(mailbox_outbox_pda_seeds!(), &program_id)
.ok_or(ProgramError::InvalidSeeds)?;
// 0. `[writeable]` The Outbox PDA account.
// 1. `[signer]` The current owner.
let instruction = SolanaInstruction {
program_id,
data: Instruction::TransferOwnership(new_owner).into_instruction_data()?,
accounts: vec![
AccountMeta::new(outbox_account, false),
AccountMeta::new(owner_payer, true),
],
};
Ok(instruction)
}

@ -829,7 +829,7 @@ fn get_owner(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
/// Transfers ownership.
///
/// Accounts:
/// 0. `[]` The Outbox PDA account.
/// 0. `[writeable]` The Outbox PDA account.
/// 1. `[signer]` The current owner.
fn transfer_ownership(
program_id: &Pubkey,

Loading…
Cancel
Save