From 74d1f400bd651b596ea68b7e5a4e67d200b54ef0 Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Wed, 2 Apr 2025 22:38:28 +0100 Subject: [PATCH] add config-interface crate --- Cargo.lock | 16 +++++ Cargo.toml | 2 + config-interface/Cargo.toml | 50 +++++++++++++++ config-interface/src/config_instruction.rs | 50 +++++++++++++++ config-interface/src/lib.rs | 75 ++++++++++++++++++++++ 5 files changed, 193 insertions(+) create mode 100644 config-interface/Cargo.toml create mode 100644 config-interface/src/config_instruction.rs create mode 100644 config-interface/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c64e265ce..d84fc1269 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2740,6 +2740,22 @@ dependencies = [ "solana-sdk-ids", ] +[[package]] +name = "solana-config-interface" +version = "1.0.0" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-short-vec", + "solana-stake-interface", + "solana-system-interface", +] + [[package]] name = "solana-cpi" version = "2.2.1" diff --git a/Cargo.toml b/Cargo.toml index 6c10366eb..79ba669bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "cluster-type", "commitment-config", "compute-budget-interface", + "config-interface", "cpi", "decode-error", "define-syscall", @@ -222,6 +223,7 @@ solana-clock = { path = "clock", version = "2.2.1" } solana-cluster-type = { path = "cluster-type", version = "2.2.1" } solana-commitment-config = { path = "commitment-config", version = "2.2.1" } solana-compute-budget-interface = { path = "compute-budget-interface", version = "2.2.1" } +solana-config-interface = { path = "config-interface", version = "1.0.0" } solana-cpi = { path = "cpi", version = "2.2.1" } solana-decode-error = { path = "decode-error", version = "2.2.1" } solana-define-syscall = { path = "define-syscall", version = "2.2.1" } diff --git a/config-interface/Cargo.toml b/config-interface/Cargo.toml new file mode 100644 index 000000000..21bdd9191 --- /dev/null +++ b/config-interface/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "solana-config-interface" +description = "Solana config program interface." +documentation = "https://docs.rs/solana-config-interface" +version = "1.0.0" +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bincode = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-account = { workspace = true, optional = true } +solana-instruction = { workspace = true, optional = true } +solana-pubkey = { workspace = true } +solana-sdk-ids = { workspace = true } +solana-short-vec = { workspace = true, optional = true } +solana-stake-interface = { workspace = true, optional = true, features = [ + "bincode", +] } +solana-system-interface = { workspace = true, optional = true, features = [ + "bincode", +] } + +[features] +bincode = [ + "dep:bincode", + "dep:solana-account", + "dep:solana-instruction", + "dep:solana-stake-interface", + "dep:solana-system-interface", + "serde", +] +serde = [ + "dep:serde", + "dep:serde_derive", + "dep:solana-short-vec", + "solana-pubkey/serde", +] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[lints] +workspace = true diff --git a/config-interface/src/config_instruction.rs b/config-interface/src/config_instruction.rs new file mode 100644 index 000000000..4584c0371 --- /dev/null +++ b/config-interface/src/config_instruction.rs @@ -0,0 +1,50 @@ +use { + crate::{id, ConfigKeys, ConfigState}, + solana_instruction::{AccountMeta, Instruction}, + solana_pubkey::Pubkey, + solana_system_interface::instruction as system_instruction, +}; + +fn initialize_account(config_pubkey: &Pubkey) -> Instruction { + let account_metas = vec![AccountMeta::new(*config_pubkey, true)]; + let account_data = (ConfigKeys { keys: vec![] }, T::default()); + Instruction::new_with_bincode(id(), &account_data, account_metas) +} + +#[cfg(feature = "bincode")] +/// Create a new, empty configuration account +pub fn create_account( + from_account_pubkey: &Pubkey, + config_account_pubkey: &Pubkey, + lamports: u64, + keys: Vec<(Pubkey, bool)>, +) -> Vec { + let space = T::max_space() + ConfigKeys::serialized_size(keys); + vec![ + system_instruction::create_account( + from_account_pubkey, + config_account_pubkey, + lamports, + space, + &id(), + ), + initialize_account::(config_account_pubkey), + ] +} + +/// Store new data in a configuration account +pub fn store( + config_account_pubkey: &Pubkey, + is_config_signer: bool, + keys: Vec<(Pubkey, bool)>, + data: &T, +) -> Instruction { + let mut account_metas = vec![AccountMeta::new(*config_account_pubkey, is_config_signer)]; + for (signer_pubkey, _) in keys.iter().filter(|(_, is_signer)| *is_signer) { + if signer_pubkey != config_account_pubkey { + account_metas.push(AccountMeta::new(*signer_pubkey, true)); + } + } + let account_data = (ConfigKeys { keys }, data); + Instruction::new_with_bincode(id(), &account_data, account_metas) +} diff --git a/config-interface/src/lib.rs b/config-interface/src/lib.rs new file mode 100644 index 000000000..06118dfdc --- /dev/null +++ b/config-interface/src/lib.rs @@ -0,0 +1,75 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![allow(clippy::arithmetic_side_effects)] +#[cfg(feature = "bincode")] +pub mod config_instruction; + +use solana_pubkey::Pubkey; +pub use solana_sdk_ids::config::id; +#[cfg(feature = "bincode")] +#[allow(deprecated)] +use { + bincode::{deserialize, serialize, serialized_size}, + solana_account::{Account, AccountSharedData}, + solana_stake_interface::config::Config as StakeConfig, +}; +#[cfg(feature = "serde")] +use { + serde_derive::{Deserialize, Serialize}, + solana_short_vec as short_vec, +}; + +#[cfg(feature = "serde")] +pub trait ConfigState: serde::Serialize + Default { + /// Maximum space that the serialized representation will require + fn max_space() -> u64; +} + +// TODO move ConfigState into `solana_program` to implement trait locally +#[cfg(feature = "bincode")] +#[allow(deprecated)] +impl ConfigState for StakeConfig { + fn max_space() -> u64 { + serialized_size(&StakeConfig::default()).unwrap() + } +} + +/// A collection of keys to be stored in Config account data. +#[derive(Debug, Default)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +pub struct ConfigKeys { + // Each key tuple comprises a unique `Pubkey` identifier, + // and `bool` whether that key is a signer of the data + #[cfg_attr(feature = "serde", serde(with = "short_vec"))] + pub keys: Vec<(Pubkey, bool)>, +} + +#[cfg(feature = "bincode")] +impl ConfigKeys { + pub fn serialized_size(keys: Vec<(Pubkey, bool)>) -> u64 { + serialized_size(&ConfigKeys { keys }).unwrap() + } +} + +#[cfg(feature = "bincode")] +pub fn get_config_data(bytes: &[u8]) -> Result<&[u8], bincode::Error> { + deserialize::(bytes) + .and_then(|keys| serialized_size(&keys)) + .map(|offset| &bytes[offset as usize..]) +} + +#[cfg(feature = "bincode")] +// utility for pre-made Accounts +pub fn create_config_account( + keys: Vec<(Pubkey, bool)>, + config_data: &T, + lamports: u64, +) -> AccountSharedData { + let mut data = serialize(&ConfigKeys { keys }).unwrap(); + data.extend_from_slice(&serialize(config_data).unwrap()); + AccountSharedData::from(Account { + lamports, + data, + owner: id(), + ..Account::default() + }) +}