Explorar o código

processor: allocate (#40)

* processor: allocate

* end the fn with realloc result

* refactor addresses helper to eliminate hash set
Joe C hai 8 meses
pai
achega
ac2a9d3c71
Modificáronse 7 ficheiros con 372 adicións e 6 borrados
  1. 110 0
      Cargo.lock
  2. 2 0
      Cargo.toml
  3. 7 1
      interface/src/error.rs
  4. 7 2
      program/Cargo.toml
  5. 126 3
      program/src/processor.rs
  6. 112 0
      program/tests/allocate.rs
  7. 8 0
      program/tests/setup.rs

+ 110 - 0
Cargo.lock

@@ -1999,6 +1999,18 @@ dependencies = [
  "libsecp256k1-core",
 ]
 
+[[package]]
+name = "light-poseidon"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee"
+dependencies = [
+ "ark-bn254",
+ "ark-ff",
+ "num-bigint 0.4.6",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "litemap"
 version = "0.7.4"
@@ -2099,6 +2111,45 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "mollusk-svm"
+version = "0.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95e7c0b51a00d234774b61bf829aa75316eb3a5ebf30bd622010bd046daa287a"
+dependencies = [
+ "bincode",
+ "mollusk-svm-error",
+ "mollusk-svm-keys",
+ "solana-bpf-loader-program",
+ "solana-compute-budget",
+ "solana-log-collector",
+ "solana-logger",
+ "solana-program-runtime",
+ "solana-sdk",
+ "solana-system-program 2.1.12",
+ "solana-timings",
+]
+
+[[package]]
+name = "mollusk-svm-error"
+version = "0.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d7892725c98a376b55e03ccbea44a0d2111c8f7bab0432b42ea689f3f612e7"
+dependencies = [
+ "solana-sdk",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "mollusk-svm-keys"
+version = "0.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd18816096707d148467889844ae57384ab4cbbe50ed24fd03139e6e34d61301"
+dependencies = [
+ "mollusk-svm-error",
+ "solana-sdk",
+]
+
 [[package]]
 name = "nix"
 version = "0.29.0"
@@ -3352,6 +3403,33 @@ dependencies = [
  "borsh 1.5.2",
 ]
 
+[[package]]
+name = "solana-bpf-loader-program"
+version = "2.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a72f4ec4df7ac88310560ec9e03358017176d38d6947f0de118925b31bfcc57c"
+dependencies = [
+ "bincode",
+ "byteorder",
+ "libsecp256k1",
+ "log",
+ "scopeguard",
+ "solana-bn254",
+ "solana-compute-budget",
+ "solana-curve25519",
+ "solana-feature-set",
+ "solana-log-collector",
+ "solana-measure",
+ "solana-poseidon",
+ "solana-program-memory",
+ "solana-program-runtime",
+ "solana-sdk",
+ "solana-timings",
+ "solana-type-overrides",
+ "solana_rbpf",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "solana-client"
 version = "2.1.12"
@@ -3759,6 +3837,18 @@ dependencies = [
  "solana-vote-program",
 ]
 
+[[package]]
+name = "solana-poseidon"
+version = "2.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cb8f71202f04231fbc0869c94da00e7198abc9117100c62e4efb3cdf533f39d"
+dependencies = [
+ "ark-bn254",
+ "light-poseidon",
+ "solana-define-syscall",
+ "thiserror 1.0.69",
+]
+
 [[package]]
 name = "solana-precompile-error"
 version = "2.1.12"
@@ -4376,11 +4466,31 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "solana-system-program"
+version = "2.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58e1e57bab6f597a95790ce6a1133a8984dd5e4939bffe87ac08b7b0fa1f545f"
+dependencies = [
+ "bincode",
+ "log",
+ "serde",
+ "serde_derive",
+ "solana-log-collector",
+ "solana-program-runtime",
+ "solana-sdk",
+ "solana-type-overrides",
+]
+
 [[package]]
 name = "solana-system-program"
 version = "3.0.0"
 dependencies = [
+ "mollusk-svm",
+ "solana-account",
  "solana-account-info",
+ "solana-bincode",
+ "solana-msg",
  "solana-program-entrypoint",
  "solana-program-error",
  "solana-pubkey",

+ 2 - 0
Cargo.toml

@@ -14,7 +14,9 @@ license = "Apache-2.0"
 edition = "2021"
 
 [workspace.dependencies]
+solana-account = "2.1"
 solana-account-info = "2.1"
+solana-bincode = "2.1"
 solana-cpi = "2.1"
 solana-decode-error = "2.1"
 solana-frozen-abi = "2.1"

+ 7 - 1
interface/src/error.rs

@@ -2,7 +2,7 @@ use {
     num_traits::{FromPrimitive, ToPrimitive},
     solana_decode_error::DecodeError,
     solana_msg::msg,
-    solana_program_error::PrintProgramError,
+    solana_program_error::{PrintProgramError, ProgramError},
 };
 
 // Use strum when testing to ensure our FromPrimitive
@@ -128,6 +128,12 @@ impl PrintProgramError for SystemError {
     }
 }
 
+impl From<SystemError> for ProgramError {
+    fn from(e: SystemError) -> Self {
+        Self::Custom(e as u32)
+    }
+}
+
 impl<T> DecodeError<T> for SystemError {
     fn type_of() -> &'static str {
         "SystemError"

+ 7 - 2
program/Cargo.toml

@@ -17,12 +17,17 @@ bpf-entrypoint = []
 
 [dependencies]
 solana-account-info = { workspace = true }
+solana-bincode = { workspace = true }
+solana-msg = { workspace = true }
 solana-program-entrypoint = { workspace = true }
 solana-program-error = { workspace = true }
-solana-pubkey = { workspace = true }
-solana-system-interface = { path = "../interface" }
+solana-pubkey = { workspace = true, features = ["sha2"] }
+solana-system-interface = { path = "../interface", features = ["serde"] }
 
 [dev-dependencies]
+mollusk-svm = "0.0.15"
+solana-account = { workspace = true }
+solana-system-interface = { path = "../interface", features = ["bincode"] }
 
 [lib]
 crate-type = ["cdylib"]

+ 126 - 3
program/src/processor.rs

@@ -1,9 +1,132 @@
 //! Program processor.
 
 use {
-    solana_account_info::AccountInfo, solana_program_error::ProgramResult, solana_pubkey::Pubkey,
+    solana_account_info::AccountInfo,
+    solana_msg::msg,
+    solana_program_error::{ProgramError, ProgramResult},
+    solana_pubkey::Pubkey,
+    solana_system_interface::{
+        error::SystemError, instruction::SystemInstruction, MAX_PERMITTED_DATA_LENGTH,
+    },
 };
 
-pub fn process(_program_id: &Pubkey, _accounts: &[AccountInfo], _input: &[u8]) -> ProgramResult {
-    Ok(())
+// Maximum input buffer length that can be deserialized.
+// https://github.com/anza-xyz/solana-sdk/blob/41c663a8ec7269aa1117a2e3b0e24ff9ee1ac4af/packet/src/lib.rs#L32
+const MAX_INPUT_LEN: u64 = 1232;
+
+macro_rules! accounts {
+    ( $infos:ident, $( $i:literal => $name:ident ),* $(,)? ) => {
+        $(
+            let $name = $infos.get($i).ok_or(ProgramError::NotEnoughAccountKeys)?;
+        )*
+    };
+}
+
+// Represents an address that may or may not have been generated from a seed.
+struct AddressInfo<'a, 'b> {
+    info: &'a AccountInfo<'b>,
+    base: Option<&'a AccountInfo<'b>>,
+}
+
+impl std::fmt::Debug for AddressInfo<'_, '_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("AddressInfo")
+            .field("address", &self.info.key)
+            .field("base", &self.base.map(|info| info.key))
+            .finish()
+    }
+}
+
+impl<'a, 'b> AddressInfo<'a, 'b> {
+    fn is_signer(&self) -> bool {
+        if let Some(base) = self.base {
+            base.is_signer
+        } else {
+            self.info.is_signer
+        }
+    }
+
+    fn create(
+        info: &'a AccountInfo<'b>,
+        with_seed: Option<(&'a AccountInfo<'b>, &str, &Pubkey)>,
+    ) -> Result<Self, ProgramError> {
+        let base = if let Some((base, seed, owner)) = with_seed {
+            // Re-derive the address. It must match the supplied address.
+            let address_with_seed = Pubkey::create_with_seed(base.key, seed, owner)?;
+            if *info.key != address_with_seed {
+                msg!(
+                    "Create: address {} does not match derived address {}",
+                    info.key,
+                    address_with_seed
+                );
+                Err(SystemError::AddressWithSeedMismatch)?
+            }
+            Some(base)
+        } else {
+            None
+        };
+
+        Ok(Self { info, base })
+    }
+}
+
+fn allocate(info: &AccountInfo, address: &AddressInfo, space: u64) -> Result<(), ProgramError> {
+    if !address.is_signer() {
+        msg!("Allocate: 'to' account {:?} must sign", address);
+        Err(ProgramError::MissingRequiredSignature)?
+    }
+
+    // If it looks like the account is already in use, bail.
+    if !info.data_is_empty() || !solana_system_interface::program::check_id(info.owner) {
+        msg!("Allocate: account {:?} already in use", address);
+        Err(SystemError::AccountAlreadyInUse)?
+    }
+
+    if space > MAX_PERMITTED_DATA_LENGTH {
+        msg!(
+            "Allocate: requested {}, max allowed {}",
+            space,
+            MAX_PERMITTED_DATA_LENGTH
+        );
+        Err(SystemError::InvalidAccountDataLength)?
+    }
+
+    /*
+    [TODO: CORE_BPF]:
+
+    This is going to behave differently than the builtin program right now,
+    since reallocations are limited to `MAX_PERMITTED_DATA_INCREASE``, which is
+    smaller than `MAX_PERMITTED_DATA_LENGTH`.
+
+    * `MAX_PERMITTED_DATA_LENGTH`   : 1_024 * 10 * 1_024
+    * `MAX_PERMITTED_DATA_INCREASE` : 1_024 * 10
+
+    https://github.com/solana-program/system/issues/30
+     */
+    info.realloc(space as usize, true)
+}
+
+fn process_allocate(accounts: &[AccountInfo], space: u64) -> ProgramResult {
+    accounts!(
+        accounts,
+        0 => account_info,
+    );
+    allocate(
+        account_info,
+        &AddressInfo::create(account_info, None)?,
+        space,
+    )
+}
+
+pub fn process(_program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
+    match solana_bincode::limited_deserialize::<SystemInstruction>(input, MAX_INPUT_LEN)
+        .map_err(|_| ProgramError::InvalidInstructionData)?
+    {
+        SystemInstruction::Allocate { space } => {
+            msg!("Instruction: Allocate");
+            process_allocate(accounts, space)
+        }
+        /* TODO: Remaining instruction implementations... */
+        _ => Err(ProgramError::InvalidInstructionData),
+    }
 }

+ 112 - 0
program/tests/allocate.rs

@@ -0,0 +1,112 @@
+mod setup;
+
+use {
+    mollusk_svm::result::Check,
+    solana_account::Account,
+    solana_account_info::MAX_PERMITTED_DATA_INCREASE,
+    solana_program_error::ProgramError,
+    solana_pubkey::Pubkey,
+    solana_system_interface::{
+        error::SystemError, instruction::allocate, MAX_PERMITTED_DATA_LENGTH,
+    },
+};
+
+const SPACE: u64 = 10_000;
+
+#[test]
+fn fail_account_not_signer() {
+    let mollusk = setup::setup();
+
+    let pubkey = Pubkey::new_unique();
+
+    let mut instruction = allocate(&pubkey, SPACE);
+    instruction.accounts[0].is_signer = false;
+
+    mollusk.process_and_validate_instruction(
+        &instruction,
+        &[(pubkey, Account::default())],
+        &[Check::err(ProgramError::MissingRequiredSignature)],
+    );
+}
+
+#[test]
+fn fail_account_already_in_use() {
+    let mollusk = setup::setup();
+
+    let pubkey = Pubkey::new_unique();
+
+    let account = Account {
+        data: vec![4; 32], // Has data
+        ..Account::default()
+    };
+
+    mollusk.process_and_validate_instruction(
+        &allocate(&pubkey, SPACE),
+        &[(pubkey, account)],
+        &[Check::err(ProgramError::Custom(
+            SystemError::AccountAlreadyInUse as u32,
+        ))],
+    );
+
+    let account = Account {
+        owner: Pubkey::new_unique(), // Not System
+        ..Account::default()
+    };
+
+    mollusk.process_and_validate_instruction(
+        &allocate(&pubkey, SPACE),
+        &[(pubkey, account)],
+        &[Check::err(ProgramError::Custom(
+            SystemError::AccountAlreadyInUse as u32,
+        ))],
+    );
+}
+
+#[test]
+fn fail_space_too_large() {
+    let mollusk = setup::setup();
+
+    let pubkey = Pubkey::new_unique();
+
+    let space_too_large = MAX_PERMITTED_DATA_LENGTH + 1;
+
+    mollusk.process_and_validate_instruction(
+        &allocate(&pubkey, space_too_large),
+        &[(pubkey, Account::default())],
+        &[Check::err(ProgramError::Custom(
+            SystemError::InvalidAccountDataLength as u32,
+        ))],
+    );
+}
+
+// [TODO: CORE_BPF]: This verifies the concern for the `realloc` issue.
+#[test]
+fn fail_space_too_large_for_realloc() {
+    let mollusk = setup::setup();
+
+    let pubkey = Pubkey::new_unique();
+
+    let space_too_large_for_realloc = MAX_PERMITTED_DATA_INCREASE + 1;
+
+    mollusk.process_and_validate_instruction(
+        &allocate(&pubkey, space_too_large_for_realloc as u64),
+        &[(pubkey, Account::default())],
+        &[Check::err(ProgramError::InvalidRealloc)], // See...?
+    );
+}
+
+#[test]
+fn success() {
+    let mollusk = setup::setup();
+
+    let pubkey = Pubkey::new_unique();
+
+    mollusk.process_and_validate_instruction(
+        &allocate(&pubkey, SPACE),
+        &[(pubkey, Account::default())],
+        &[
+            Check::success(),
+            Check::account(&pubkey).space(SPACE as usize).build(),
+        ],
+    );
+}

+ 8 - 0
program/tests/setup.rs

@@ -0,0 +1,8 @@
+use mollusk_svm::Mollusk;
+
+pub fn setup() -> Mollusk {
+    Mollusk::new(
+        &solana_system_interface::program::id(),
+        "solana_system_program",
+    )
+}