Skip to content

Commit 6ccbd02

Browse files
authored
feat: epoch stake syscalls (#141)
1 parent b57d070 commit 6ccbd02

File tree

9 files changed

+173
-15
lines changed

9 files changed

+173
-15
lines changed

Cargo.lock

Lines changed: 20 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ audit:
2929
build-test-programs:
3030
@cargo build-sbf --manifest-path test-programs/cpi-target/Cargo.toml
3131
@cargo build-sbf --manifest-path test-programs/custom-syscall/Cargo.toml
32+
@cargo build-sbf --manifest-path test-programs/epoch-stake/Cargo.toml
3233
@cargo build-sbf --manifest-path test-programs/primary/Cargo.toml
3334

3435
# Pre-publish checks

harness/src/epoch_stake.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use {solana_pubkey::Pubkey, std::collections::HashMap};
2+
3+
/// A simple map of vote accounts to their epoch stake.
4+
///
5+
/// Developers can work with this map directly to configure stake for testing.
6+
/// The total epoch stake is calculated by summing all vote account stakes.
7+
pub type EpochStake = HashMap<Pubkey, u64>;
8+
9+
/// Create an `EpochStake` instance with a few mocked-out vote accounts to
10+
/// achieve the provided total stake.
11+
pub fn create_mock_epoch_stake(target_total: u64) -> EpochStake {
12+
let mut epoch_stake = HashMap::new();
13+
14+
if target_total == 0 {
15+
return epoch_stake;
16+
}
17+
18+
let num_accounts = target_total.div_ceil(1_000_000_000);
19+
20+
let base_stake = target_total / num_accounts;
21+
let remainder = target_total % num_accounts;
22+
23+
std::iter::repeat(base_stake)
24+
.take(num_accounts as usize - 1)
25+
.chain(std::iter::once(base_stake + remainder))
26+
.for_each(|stake| {
27+
epoch_stake.insert(Pubkey::new_unique(), stake);
28+
});
29+
30+
epoch_stake
31+
}

harness/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@
441441
442442
pub mod account_store;
443443
mod compile_accounts;
444+
pub mod epoch_stake;
444445
pub mod file;
445446
#[cfg(any(feature = "fuzz", feature = "fuzz-fd"))]
446447
pub mod fuzz;
@@ -453,8 +454,8 @@ pub use mollusk_svm_result as result;
453454
use mollusk_svm_result::Compare;
454455
use {
455456
crate::{
456-
account_store::AccountStore, compile_accounts::CompiledAccounts, program::ProgramCache,
457-
sysvar::Sysvars,
457+
account_store::AccountStore, compile_accounts::CompiledAccounts, epoch_stake::EpochStake,
458+
program::ProgramCache, sysvar::Sysvars,
458459
},
459460
agave_feature_set::FeatureSet,
460461
mollusk_svm_error::error::{MolluskError, MolluskPanic},
@@ -482,6 +483,7 @@ pub(crate) const DEFAULT_LOADER_KEY: Pubkey = solana_sdk_ids::bpf_loader_upgrade
482483
pub struct Mollusk {
483484
pub config: Config,
484485
pub compute_budget: ComputeBudget,
486+
pub epoch_stake: EpochStake,
485487
pub feature_set: FeatureSet,
486488
pub logger: Option<Rc<RefCell<LogCollector>>>,
487489
pub program_cache: ProgramCache,
@@ -521,6 +523,7 @@ impl Default for Mollusk {
521523
Self {
522524
config: Config::default(),
523525
compute_budget,
526+
epoch_stake: EpochStake::default(),
524527
feature_set,
525528
logger: None,
526529
program_cache,
@@ -539,9 +542,18 @@ impl CheckContext for Mollusk {
539542

540543
struct MolluskInvokeContextCallback<'a> {
541544
feature_set: &'a FeatureSet,
545+
epoch_stake: &'a EpochStake,
542546
}
543547

544548
impl InvokeContextCallback for MolluskInvokeContextCallback<'_> {
549+
fn get_epoch_stake(&self) -> u64 {
550+
self.epoch_stake.values().sum()
551+
}
552+
553+
fn get_epoch_stake_for_vote_account(&self, vote_address: &Pubkey) -> u64 {
554+
self.epoch_stake.get(vote_address).copied().unwrap_or(0)
555+
}
556+
545557
fn is_precompile(&self, program_id: &Pubkey) -> bool {
546558
agave_precompiles::is_precompile(program_id, |feature_id| {
547559
self.feature_set.is_active(feature_id)
@@ -647,6 +659,7 @@ impl Mollusk {
647659
let invoke_result = {
648660
let mut program_cache = self.program_cache.cache();
649661
let callback = MolluskInvokeContextCallback {
662+
epoch_stake: &self.epoch_stake,
650663
feature_set: &self.feature_set,
651664
};
652665
let runtime_features = self.feature_set.runtime_features();

harness/tests/custom_syscall.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ fn test_custom_syscall() {
4444
.unwrap();
4545
mollusk.add_program(
4646
&program_id,
47-
"custom_syscall_program",
47+
"test_program_custom_syscall",
4848
&mollusk_svm::program::loader_keys::LOADER_V3,
4949
);
5050
mollusk

harness/tests/epoch_stake.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use {
2+
mollusk_svm::{result::Check, Mollusk},
3+
solana_account::Account,
4+
solana_instruction::{AccountMeta, Instruction},
5+
solana_pubkey::Pubkey,
6+
};
7+
8+
#[test]
9+
fn test_epoch_stake() {
10+
std::env::set_var("SBF_OUT_DIR", "../target/deploy");
11+
12+
let program_id = Pubkey::new_unique();
13+
let mut mollusk = Mollusk::new(&program_id, "test_program_epoch_stake");
14+
15+
let key = Pubkey::new_unique();
16+
17+
let mut total_stake: u64 = 0;
18+
19+
for i in 1..=3 {
20+
let stake = 1_000_000_000_000_000 * i;
21+
total_stake += stake;
22+
23+
let vote_address = Pubkey::new_unique();
24+
25+
mollusk.epoch_stake.insert(vote_address, stake);
26+
assert_eq!(total_stake, mollusk.epoch_stake.values().sum::<u64>());
27+
28+
let instruction = Instruction::new_with_bytes(
29+
program_id,
30+
&vote_address.to_bytes(),
31+
vec![AccountMeta::new(key, false)],
32+
);
33+
34+
mollusk.process_and_validate_instruction(
35+
&instruction,
36+
&[(key, Account::new(1_000, 16, &program_id))],
37+
&[
38+
Check::success(),
39+
Check::account(&key)
40+
.data(&{
41+
let mut data = vec![0; 16];
42+
data[0..8].copy_from_slice(&total_stake.to_le_bytes());
43+
data[8..16].copy_from_slice(&stake.to_le_bytes());
44+
data
45+
})
46+
.build(),
47+
],
48+
);
49+
}
50+
}

test-programs/custom-syscall/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "custom-syscall-program"
2+
name = "test-program-custom-syscall"
33
version = "0.1.0"
44
edition = "2021"
55

@@ -18,4 +18,3 @@ check-cfg = [
1818
'cfg(feature, values("custom-heap", "custom-panic"))',
1919
'cfg(target_os, values("solana"))',
2020
]
21-
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "test-program-epoch-stake"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
solana-account-info = { workspace = true }
8+
solana-program-entrypoint = { workspace = true }
9+
solana-program-error = { workspace = true }
10+
solana-pubkey = { workspace = true }
11+
12+
[lib]
13+
crate-type = ["cdylib", "lib"]
14+
15+
[lints.rust.unexpected_cfgs]
16+
level = "warn"
17+
check-cfg = [
18+
'cfg(feature, values("custom-heap", "custom-panic"))',
19+
'cfg(target_os, values("solana"))',
20+
]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![cfg(target_os = "solana")]
2+
3+
use {solana_account_info::AccountInfo, solana_program_error::ProgramError, solana_pubkey::Pubkey};
4+
5+
extern "C" {
6+
fn sol_get_epoch_stake(vote_address: *const u8) -> u64;
7+
}
8+
9+
unsafe fn get_epoch_total_stake() -> u64 {
10+
sol_get_epoch_stake(std::ptr::null::<Pubkey>() as *const u8)
11+
}
12+
13+
unsafe fn get_epoch_stake_for_vote_account(vote_address: &Pubkey) -> u64 {
14+
sol_get_epoch_stake(vote_address as *const _ as *const u8)
15+
}
16+
17+
solana_program_entrypoint::entrypoint!(process_instruction);
18+
19+
fn process_instruction(
20+
_program_id: &Pubkey,
21+
accounts: &[AccountInfo],
22+
input: &[u8],
23+
) -> Result<(), ProgramError> {
24+
let vote_address = Pubkey::new_from_array(input.try_into().unwrap());
25+
26+
let total_stake = unsafe { get_epoch_total_stake() };
27+
let vote_account_stake = unsafe { get_epoch_stake_for_vote_account(&vote_address) };
28+
29+
let mut data = accounts[0].try_borrow_mut_data()?;
30+
data[0..8].copy_from_slice(&total_stake.to_le_bytes());
31+
data[8..16].copy_from_slice(&vote_account_stake.to_le_bytes());
32+
33+
Ok(())
34+
}

0 commit comments

Comments
 (0)