Skip to content

Commit d109065

Browse files
authored
SVM: add new solana-svm-rent-collector crate (anza-xyz#2688)
1 parent 106d4cf commit d109065

File tree

7 files changed

+435
-0
lines changed

7 files changed

+435
-0
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ members = [
119119
"streamer",
120120
"svm",
121121
"svm-conformance",
122+
"svm-rent-collector",
122123
"svm-transaction",
123124
"svm/examples/paytube",
124125
"test-validator",
@@ -434,6 +435,7 @@ solana-streamer = { path = "streamer", version = "=2.1.0" }
434435
solana-svm = { path = "svm", version = "=2.1.0" }
435436
solana-svm-conformance = { path = "svm-conformance", version = "=2.1.0" }
436437
solana-svm-example-paytube = { path = "svm/examples/paytube", version = "=2.1.0" }
438+
solana-svm-rent-collector = { path = "svm-rent-collector", version = "=2.1.0" }
437439
solana-svm-transaction = { path = "svm-transaction", version = "=2.1.0" }
438440
solana-system-program = { path = "programs/system", version = "=2.1.0" }
439441
solana-test-validator = { path = "test-validator", version = "=2.1.0" }

svm-rent-collector/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "solana-svm-rent-collector"
3+
description = "Solana SVM Rent Collector"
4+
documentation = "https://docs.rs/solana-svm-rent-collector"
5+
version = { workspace = true }
6+
authors = { workspace = true }
7+
repository = { workspace = true }
8+
homepage = { workspace = true }
9+
license = { workspace = true }
10+
edition = { workspace = true }
11+
12+
[dependencies]
13+
solana-sdk = { workspace = true }

svm-rent-collector/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! Solana SVM Rent Collector.
2+
//!
3+
//! Rent management for SVM.
4+
5+
pub mod rent_state;
6+
pub mod svm_rent_collector;

svm-rent-collector/src/rent_state.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//! Account rent state.
2+
3+
/// Rent state of a Solana account.
4+
#[derive(Debug, PartialEq, Eq)]
5+
pub enum RentState {
6+
/// account.lamports == 0
7+
Uninitialized,
8+
/// 0 < account.lamports < rent-exempt-minimum
9+
RentPaying {
10+
lamports: u64, // account.lamports()
11+
data_size: usize, // account.data().len()
12+
},
13+
/// account.lamports >= rent-exempt-minimum
14+
RentExempt,
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! Plugin trait for rent collection within the Solana SVM.
2+
3+
use {
4+
crate::rent_state::RentState,
5+
solana_sdk::{
6+
account::{AccountSharedData, ReadableAccount},
7+
clock::Epoch,
8+
pubkey::Pubkey,
9+
rent::{Rent, RentDue},
10+
rent_collector::CollectedInfo,
11+
transaction::{Result, TransactionError},
12+
transaction_context::{IndexOfAccount, TransactionContext},
13+
},
14+
};
15+
16+
mod rent_collector;
17+
18+
/// Rent collector trait. Represents an entity that can evaluate the rent state
19+
/// of an account, determine rent due, and collect rent.
20+
///
21+
/// Implementors are responsible for evaluating rent due and collecting rent
22+
/// from accounts, if required. Methods for evaluating account rent state have
23+
/// default implementations, which can be overridden for customized rent
24+
/// management.
25+
pub trait SVMRentCollector {
26+
/// Check rent state transition for an account in a transaction.
27+
///
28+
/// This method has a default implementation that calls into
29+
/// `check_rent_state_with_account`.
30+
fn check_rent_state(
31+
&self,
32+
pre_rent_state: Option<&RentState>,
33+
post_rent_state: Option<&RentState>,
34+
transaction_context: &TransactionContext,
35+
index: IndexOfAccount,
36+
) -> Result<()> {
37+
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
38+
let expect_msg =
39+
"account must exist at TransactionContext index if rent-states are Some";
40+
self.check_rent_state_with_account(
41+
pre_rent_state,
42+
post_rent_state,
43+
transaction_context
44+
.get_key_of_account_at_index(index)
45+
.expect(expect_msg),
46+
&transaction_context
47+
.get_account_at_index(index)
48+
.expect(expect_msg)
49+
.borrow(),
50+
index,
51+
)?;
52+
}
53+
Ok(())
54+
}
55+
56+
/// Check rent state transition for an account directly.
57+
///
58+
/// This method has a default implementation that checks whether the
59+
/// transition is allowed and returns an error if it is not. It also
60+
/// verifies that the account is not the incinerator.
61+
fn check_rent_state_with_account(
62+
&self,
63+
pre_rent_state: &RentState,
64+
post_rent_state: &RentState,
65+
address: &Pubkey,
66+
_account_state: &AccountSharedData,
67+
account_index: IndexOfAccount,
68+
) -> Result<()> {
69+
if !solana_sdk::incinerator::check_id(address)
70+
&& !self.transition_allowed(pre_rent_state, post_rent_state)
71+
{
72+
let account_index = account_index as u8;
73+
Err(TransactionError::InsufficientFundsForRent { account_index })
74+
} else {
75+
Ok(())
76+
}
77+
}
78+
79+
/// Collect rent from an account.
80+
fn collect_rent(&self, address: &Pubkey, account: &mut AccountSharedData) -> CollectedInfo;
81+
82+
/// Determine the rent state of an account.
83+
///
84+
/// This method has a default implementation that treats accounts with zero
85+
/// lamports as uninitialized and uses the implemented `get_rent` to
86+
/// determine whether an account is rent-exempt.
87+
fn get_account_rent_state(&self, account: &AccountSharedData) -> RentState {
88+
if account.lamports() == 0 {
89+
RentState::Uninitialized
90+
} else if self
91+
.get_rent()
92+
.is_exempt(account.lamports(), account.data().len())
93+
{
94+
RentState::RentExempt
95+
} else {
96+
RentState::RentPaying {
97+
data_size: account.data().len(),
98+
lamports: account.lamports(),
99+
}
100+
}
101+
}
102+
103+
/// Get the rent collector's rent instance.
104+
fn get_rent(&self) -> &Rent;
105+
106+
/// Get the rent due for an account.
107+
fn get_rent_due(&self, lamports: u64, data_len: usize, account_rent_epoch: Epoch) -> RentDue;
108+
109+
/// Check whether a transition from the pre_rent_state to the
110+
/// post_rent_state is valid.
111+
///
112+
/// This method has a default implementation that allows transitions from
113+
/// any state to `RentState::Uninitialized` or `RentState::RentExempt`.
114+
/// Pre-state `RentState::RentPaying` can only transition to
115+
/// `RentState::RentPaying` if the data size remains the same and the
116+
/// account is not credited.
117+
fn transition_allowed(&self, pre_rent_state: &RentState, post_rent_state: &RentState) -> bool {
118+
match post_rent_state {
119+
RentState::Uninitialized | RentState::RentExempt => true,
120+
RentState::RentPaying {
121+
data_size: post_data_size,
122+
lamports: post_lamports,
123+
} => {
124+
match pre_rent_state {
125+
RentState::Uninitialized | RentState::RentExempt => false,
126+
RentState::RentPaying {
127+
data_size: pre_data_size,
128+
lamports: pre_lamports,
129+
} => {
130+
// Cannot remain RentPaying if resized or credited.
131+
post_data_size == pre_data_size && post_lamports <= pre_lamports
132+
}
133+
}
134+
}
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)