Browse Source

Add system interface (#16)

* [wip] Add interface crate

* [wip] Add missing features

* [wip] Add wasm

* Add dev dependencies

* Update rust renderer

* Bump toolchain version

* Add individual modules

* Address review comments

* Update lock file

* Fix dependencies

* Switch crates version

* Fix rustdoc warnings

* Remove serde feature

* More review comments

* Add constants test
Fernando Otero 1 year ago
parent
commit
2593378df2

File diff suppressed because it is too large
+ 1090 - 4994
Cargo.lock


+ 4 - 6
Cargo.toml

@@ -1,12 +1,10 @@
 [workspace]
 resolver = "2"
-members = ["clients/rust"]
+members = ["clients/rust", "interface"]
 
 [workspace.metadata.cli]
-solana = "1.18.18"
+solana = "2.1.0"
 
-# Specify Rust toolchains for rustfmt, clippy, and build.
-# Any unprovided toolchains default to stable.
 [workspace.metadata.toolchains]
-format = "1.78.0"
-lint = "1.78.0"
+format = "nightly-2024-08-08"
+lint = "nightly-2024-08-08"

+ 1 - 8
clients/rust/Cargo.toml

@@ -8,22 +8,15 @@ readme = "README.md"
 license-file = "../../LICENSE"
 
 [features]
-anchor = ["dep:anchor-lang"]
 test-sbf = []
 serde = ["dep:serde", "dep:serde_with", "kaigan/serde"]
 
 [dependencies]
-anchor-lang = { version = "0.30.0", optional = true }
 borsh = "^0.10"
 kaigan = "^0.2"
 num-derive = "^0.3"
 num-traits = "^0.2"
 serde = { version = "^1.0", features = ["derive"], optional = true }
 serde_with = { version = "^3.0", optional = true }
-solana-program = "~1.18"
+solana-program = "^2.1"
 thiserror = "^1.0"
-
-[dev-dependencies]
-assert_matches = "1.5.0"
-solana-program-test = "~1.18"
-solana-sdk = "~1.18"

+ 0 - 25
clients/rust/src/generated/accounts/nonce.rs

@@ -49,28 +49,3 @@ impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Nonce {
         Self::deserialize(&mut data)
     }
 }
-
-#[cfg(feature = "anchor")]
-impl anchor_lang::AccountDeserialize for Nonce {
-    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
-        Ok(Self::deserialize(buf)?)
-    }
-}
-
-#[cfg(feature = "anchor")]
-impl anchor_lang::AccountSerialize for Nonce {}
-
-#[cfg(feature = "anchor")]
-impl anchor_lang::Owner for Nonce {
-    fn owner() -> Pubkey {
-        crate::SYSTEM_ID
-    }
-}
-
-#[cfg(feature = "anchor-idl-build")]
-impl anchor_lang::IdlBuild for Nonce {}
-
-#[cfg(feature = "anchor-idl-build")]
-impl anchor_lang::Discriminator for Nonce {
-    const DISCRIMINATOR: [u8; 8] = [0; 8];
-}

+ 1 - 1
clients/rust/src/generated/instructions/advance_nonce_account.rs

@@ -237,7 +237,7 @@ impl<'a, 'b> AdvanceNonceAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(4 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.nonce_account.clone());
         account_infos.push(self.recent_blockhashes_sysvar.clone());

+ 1 - 1
clients/rust/src/generated/instructions/allocate.rs

@@ -205,7 +205,7 @@ impl<'a, 'b> AllocateCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(1 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(2 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.new_account.clone());
         remaining_accounts

+ 1 - 1
clients/rust/src/generated/instructions/allocate_with_seed.rs

@@ -257,7 +257,7 @@ impl<'a, 'b> AllocateWithSeedCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(2 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(3 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.new_account.clone());
         account_infos.push(self.base_account.clone());

+ 1 - 1
clients/rust/src/generated/instructions/assign.rs

@@ -209,7 +209,7 @@ impl<'a, 'b> AssignCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(1 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(2 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.account.clone());
         remaining_accounts

+ 1 - 1
clients/rust/src/generated/instructions/assign_with_seed.rs

@@ -249,7 +249,7 @@ impl<'a, 'b> AssignWithSeedCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(2 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(3 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.account.clone());
         account_infos.push(self.base_account.clone());

+ 1 - 1
clients/rust/src/generated/instructions/authorize_nonce_account.rs

@@ -239,7 +239,7 @@ impl<'a, 'b> AuthorizeNonceAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(2 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(3 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.nonce_account.clone());
         account_infos.push(self.nonce_authority.clone());

+ 1 - 1
clients/rust/src/generated/instructions/create_account.rs

@@ -247,7 +247,7 @@ impl<'a, 'b> CreateAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(2 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(3 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.payer.clone());
         account_infos.push(self.new_account.clone());

+ 1 - 1
clients/rust/src/generated/instructions/create_account_with_seed.rs

@@ -291,7 +291,7 @@ impl<'a, 'b> CreateAccountWithSeedCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(4 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.payer.clone());
         account_infos.push(self.new_account.clone());

+ 1 - 1
clients/rust/src/generated/instructions/initialize_nonce_account.rs

@@ -268,7 +268,7 @@ impl<'a, 'b> InitializeNonceAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(4 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.nonce_account.clone());
         account_infos.push(self.recent_blockhashes_sysvar.clone());

+ 1 - 1
clients/rust/src/generated/instructions/transfer_sol.rs

@@ -228,7 +228,7 @@ impl<'a, 'b> TransferSolCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(2 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(3 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.source.clone());
         account_infos.push(self.destination.clone());

+ 1 - 1
clients/rust/src/generated/instructions/transfer_sol_with_seed.rs

@@ -273,7 +273,7 @@ impl<'a, 'b> TransferSolWithSeedCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(4 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.source.clone());
         account_infos.push(self.base_account.clone());

+ 1 - 1
clients/rust/src/generated/instructions/upgrade_nonce_account.rs

@@ -182,7 +182,7 @@ impl<'a, 'b> UpgradeNonceAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(1 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(2 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.nonce_account.clone());
         remaining_accounts

+ 1 - 1
clients/rust/src/generated/instructions/withdraw_nonce_account.rs

@@ -321,7 +321,7 @@ impl<'a, 'b> WithdrawNonceAccountCpi<'a, 'b> {
             accounts,
             data,
         };
-        let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len());
+        let mut account_infos = Vec::with_capacity(6 + remaining_accounts.len());
         account_infos.push(self.__program.clone());
         account_infos.push(self.nonce_account.clone());
         account_infos.push(self.recipient_account.clone());

+ 42 - 0
interface/Cargo.toml

@@ -0,0 +1,42 @@
+[package]
+name = "solana-system-interface"
+version = "0.0.1"
+description = "Instructions and constructors for the System program"
+repository = "https://github.com/solana-program/system"
+edition = "2021"
+readme = "README.md"
+license-file = "../LICENSE"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
+all-features = true
+rustdoc-args = ["--cfg=docsrs"]
+
+[dependencies]
+num-traits = "0.2"
+serde = "1.0.210"
+serde_derive = "1.0.210"
+solana-decode-error = "^2.1"
+solana-frozen-abi = { version = "^2.1", features = ["frozen-abi"], optional = true }
+solana-frozen-abi-macro = { version = "^2.1", features = ["frozen-abi"], optional = true }
+solana-instruction = { version = "^2.1", features = ["bincode", "std"] }
+solana-pubkey = { version = "^2.1", default-features = false, features = ["serde"] }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+js-sys = "0.3.72"
+wasm-bindgen = "0.2"
+
+[dev-dependencies]
+anyhow = "1.0.89"
+borsh = { version = "1.5.1", features = ["derive", "unstable__schema"] }
+solana-nonce = "^0.0.2"
+solana-program = { version = "^2.1", default-features = false }
+static_assertions = "1.1.0"
+strum = "0.24"
+strum_macros = "0.24"
+
+[features]
+frozen-abi = [
+    "dep:solana-frozen-abi",
+    "dep:solana-frozen-abi-macro"
+]

+ 3 - 0
interface/README.md

@@ -0,0 +1,3 @@
+# `solana-system-interface`
+
+Instructions and constructors for the System program.

+ 156 - 0
interface/src/error.rs

@@ -0,0 +1,156 @@
+use num_traits::{FromPrimitive, ToPrimitive};
+use serde_derive::{Deserialize, Serialize};
+use solana_decode_error::DecodeError;
+
+// Use strum when testing to ensure our FromPrimitive
+// impl is exhaustive
+#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum SystemError {
+    /// An account with the same address already exists.
+    AccountAlreadyInUse,
+    /// Account does not have enough SOL to perform the operation.
+    ResultWithNegativeLamports,
+    /// Cannot assign account to this program id.
+    InvalidProgramId,
+    /// Cannot allocate account data of this length.
+    InvalidAccountDataLength,
+    /// Length of requested seed is too long.
+    MaxSeedLengthExceeded,
+    /// Provided address does not match addressed derived from seed.
+    AddressWithSeedMismatch,
+    /// Advancing stored nonce requires a populated RecentBlockhashes sysvar.
+    NonceNoRecentBlockhashes,
+    /// Stored nonce is still in recent_blockhashes.
+    NonceBlockhashNotExpired,
+    /// Specified nonce does not match stored nonce.
+    NonceUnexpectedBlockhashValue,
+}
+
+impl FromPrimitive for SystemError {
+    #[inline]
+    fn from_i64(n: i64) -> Option<Self> {
+        if n == Self::AccountAlreadyInUse as i64 {
+            Some(Self::AccountAlreadyInUse)
+        } else if n == Self::ResultWithNegativeLamports as i64 {
+            Some(Self::ResultWithNegativeLamports)
+        } else if n == Self::InvalidProgramId as i64 {
+            Some(Self::InvalidProgramId)
+        } else if n == Self::InvalidAccountDataLength as i64 {
+            Some(Self::InvalidAccountDataLength)
+        } else if n == Self::MaxSeedLengthExceeded as i64 {
+            Some(Self::MaxSeedLengthExceeded)
+        } else if n == Self::AddressWithSeedMismatch as i64 {
+            Some(Self::AddressWithSeedMismatch)
+        } else if n == Self::NonceNoRecentBlockhashes as i64 {
+            Some(Self::NonceNoRecentBlockhashes)
+        } else if n == Self::NonceBlockhashNotExpired as i64 {
+            Some(Self::NonceBlockhashNotExpired)
+        } else if n == Self::NonceUnexpectedBlockhashValue as i64 {
+            Some(Self::NonceUnexpectedBlockhashValue)
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn from_u64(n: u64) -> Option<Self> {
+        Self::from_i64(n as i64)
+    }
+}
+
+impl ToPrimitive for SystemError {
+    #[inline]
+    fn to_i64(&self) -> Option<i64> {
+        Some(match *self {
+            Self::AccountAlreadyInUse => Self::AccountAlreadyInUse as i64,
+            Self::ResultWithNegativeLamports => Self::ResultWithNegativeLamports as i64,
+            Self::InvalidProgramId => Self::InvalidProgramId as i64,
+            Self::InvalidAccountDataLength => Self::InvalidAccountDataLength as i64,
+            Self::MaxSeedLengthExceeded => Self::MaxSeedLengthExceeded as i64,
+            Self::AddressWithSeedMismatch => Self::AddressWithSeedMismatch as i64,
+            Self::NonceNoRecentBlockhashes => Self::NonceNoRecentBlockhashes as i64,
+            Self::NonceBlockhashNotExpired => Self::NonceBlockhashNotExpired as i64,
+            Self::NonceUnexpectedBlockhashValue => Self::NonceUnexpectedBlockhashValue as i64,
+        })
+    }
+    #[inline]
+    fn to_u64(&self) -> Option<u64> {
+        self.to_i64().map(|x| x as u64)
+    }
+}
+
+impl std::error::Error for SystemError {}
+
+impl core::fmt::Display for SystemError {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+        match self {
+            SystemError::AccountAlreadyInUse => {
+                f.write_str("an account with the same address already exists")
+            }
+            SystemError::ResultWithNegativeLamports => {
+                f.write_str("account does not have enough SOL to perform the operation")
+            }
+            SystemError::InvalidProgramId => {
+                f.write_str("cannot assign account to this program id")
+            }
+            SystemError::InvalidAccountDataLength => {
+                f.write_str("cannot allocate account data of this length")
+            }
+            SystemError::MaxSeedLengthExceeded => {
+                f.write_str("length of requested seed is too long")
+            }
+            SystemError::AddressWithSeedMismatch => {
+                f.write_str("provided address does not match addressed derived from seed")
+            }
+            SystemError::NonceNoRecentBlockhashes => {
+                f.write_str("advancing stored nonce requires a populated RecentBlockhashes sysvar")
+            }
+            SystemError::NonceBlockhashNotExpired => {
+                f.write_str("stored nonce is still in recent_blockhashes")
+            }
+            SystemError::NonceUnexpectedBlockhashValue => {
+                f.write_str("specified nonce does not match stored nonce")
+            }
+        }
+    }
+}
+
+impl<T> DecodeError<T> for SystemError {
+    fn type_of() -> &'static str {
+        "SystemError"
+    }
+}
+
+impl From<u64> for SystemError {
+    fn from(error: u64) -> Self {
+        match error {
+            0 => SystemError::AccountAlreadyInUse,
+            1 => SystemError::ResultWithNegativeLamports,
+            2 => SystemError::InvalidProgramId,
+            3 => SystemError::InvalidAccountDataLength,
+            4 => SystemError::MaxSeedLengthExceeded,
+            5 => SystemError::AddressWithSeedMismatch,
+            6 => SystemError::NonceNoRecentBlockhashes,
+            7 => SystemError::NonceBlockhashNotExpired,
+            8 => SystemError::NonceUnexpectedBlockhashValue,
+            _ => panic!("Unsupported SystemError"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use {super::SystemError, num_traits::FromPrimitive, strum::IntoEnumIterator};
+
+    #[test]
+    fn test_system_error_from_primitive_exhaustive() {
+        for variant in SystemError::iter() {
+            let variant_i64 = variant.clone() as i64;
+            assert_eq!(
+                SystemError::from_repr(variant_i64 as usize),
+                SystemError::from_i64(variant_i64)
+            );
+            assert_eq!(SystemError::from(variant_i64 as u64), variant);
+        }
+    }
+}

File diff suppressed because it is too large
+ 168 - 310
interface/src/instruction.rs


+ 68 - 4
interface/src/lib.rs

@@ -1,5 +1,69 @@
-//! The [system native program][np].
-//!
-//! [np]: https://docs.solanalabs.com/runtime/programs#system-program
+//! The System program interface.
 
-crate::declare_id!("11111111111111111111111111111111");
+#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
+pub mod error;
+pub mod instruction;
+#[cfg(target_arch = "wasm32")]
+mod wasm;
+
+use solana_pubkey::Pubkey;
+
+// Inline some constants to avoid dependencies.
+//
+// Note: replace these inline IDs with the corresponding value from
+// `solana_sdk_ids` once the version is updated to 2.2.0.
+
+const RECENT_BLOCKHASHES_ID: Pubkey =
+    Pubkey::from_str_const("SysvarRecentB1ockHashes11111111111111111111");
+
+const RENT_ID: Pubkey = Pubkey::from_str_const("SysvarRent111111111111111111111111111111111");
+
+#[cfg(test)]
+static_assertions::const_assert_eq!(solana_nonce::state::State::size(), NONCE_STATE_SIZE);
+/// The serialized size of the nonce state.
+const NONCE_STATE_SIZE: usize = 80;
+
+#[cfg(test)]
+static_assertions::const_assert!(MAX_PERMITTED_DATA_LENGTH <= u32::MAX as u64);
+/// Maximum permitted size of account data (10 MiB).
+///
+// SBF program entrypoint assumes that the max account data length
+// will fit inside a u32. If this constant no longer fits in a u32,
+// the entrypoint deserialization code in the SDK must be updated.
+pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
+
+#[cfg(test)]
+static_assertions::const_assert_eq!(MAX_PERMITTED_DATA_LENGTH, 10_485_760);
+/// Maximum permitted size of new allocations per transaction, in bytes.
+///
+/// The value was chosen such that at least one max sized account could be created,
+/// plus some additional resize allocations.
+pub const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
+    MAX_PERMITTED_DATA_LENGTH as i64 * 2;
+
+pub mod program {
+    use solana_pubkey::declare_id;
+
+    declare_id!("11111111111111111111111111111111");
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use solana_program::sysvar::SysvarId;
+
+    #[allow(deprecated)]
+    #[test]
+    fn test_constants() {
+        // Ensure that the constants are in sync with the solana program.
+        assert_eq!(
+            RECENT_BLOCKHASHES_ID,
+            solana_program::sysvar::recent_blockhashes::RecentBlockhashes::id(),
+        );
+
+        // Ensure that the constants are in sync with the solana rent.
+        assert_eq!(RENT_ID, solana_program::sysvar::rent::Rent::id());
+    }
+}

+ 7 - 1
interface/src/wasm.rs

@@ -2,7 +2,13 @@
 #![cfg(target_arch = "wasm32")]
 #![allow(non_snake_case)]
 use {
-    crate::{instruction::Instruction, pubkey::Pubkey, system_instruction::*},
+    crate::instruction::{
+        advance_nonce_account, allocate, allocate_with_seed, assign, assign_with_seed,
+        authorize_nonce_account, create_account, create_account_with_seed, create_nonce_account,
+        transfer, transfer_with_seed, withdraw_nonce_account, SystemInstruction,
+    },
+    solana_instruction::Instruction,
+    solana_pubkey::Pubkey,
     wasm_bindgen::prelude::*,
 };
 

+ 3 - 3
package.json

@@ -19,10 +19,10 @@
     "template:upgrade": "zx ./scripts/upgrade-template.mjs"
   },
   "devDependencies": {
-    "@codama/renderers-js": "^1.0.0",
-    "@codama/renderers-rust": "^1.0.0",
+    "@codama/renderers-js": "^1.1.0",
+    "@codama/renderers-rust": "^1.0.4",
     "@iarna/toml": "^2.2.5",
-    "codama": "^1.0.0",
+    "codama": "^1.1.0",
     "typescript": "^5.5.2",
     "zx": "^7.2.3"
   },

File diff suppressed because it is too large
+ 234 - 521
pnpm-lock.yaml


+ 2 - 0
rust-toolchain.toml

@@ -0,0 +1,2 @@
+[toolchain]
+channel = "1.81.0"

+ 1 - 0
scripts/generate-clients.mjs

@@ -24,5 +24,6 @@ codama.accept(
   renderRustVisitor(path.join(rustClient, 'src', 'generated'), {
     formatCode: true,
     crateFolder: rustClient,
+    anchorTraits: false,
   })
 );

Some files were not shown because too many files changed in this diff