Skip to content

program: replace rent_exempt_reserve with Rent #303

Merged
2501babe merged 9 commits intosolana-program:mainfrom
2501babe:20260303_dynamicrent
Mar 5, 2026
Merged

program: replace rent_exempt_reserve with Rent #303
2501babe merged 9 commits intosolana-program:mainfrom
2501babe:20260303_dynamicrent

Conversation

@2501babe
Copy link
Member

@2501babe 2501babe commented Mar 3, 2026

agave now has several feature gates to reduce rent costs. historically, stake has assumed rent does not change and stored rent-exemption in Meta.rent_exempt_reserve. this pr changes bpf stake to rely exclusively on Rent

as long as rent only goes down, this pr is a sufficient solution to all our problems. if rent were to go back up, we need a mechanism to adjust delegation down so that rent lamports and stake lamports are always mutually exclusive. the expectation at present is that this will be added to agave, rewriting delegations at the epoch boundary so that stake does not need to be aware of such happenings

the question of what exactly do do with rent_exempt_reserve is potentially a matter for debate. existing programs that use stake accounts probably treat it as canonical. thus, continuing to write something to this field is an obvious decision. whats non-obvious is what to write

i decided to unconditionally set rent_exempt_reserve to the current value used for new stake accounts. the downside is this has the potential to confuse programs when the actual rent required is lower. however i believe the most attractive property of this approach is consistency

once rent changes for the first time, all stake account rent_exempt_reserve values will become wrong. the problem with setting rent_exempt_reserve to true rent on stake account initialization or delegation is that when rent changes again, the stake accounts set up in that intervening period become wrong again anyway, and wrong in a different way than older accounts. we also create novel edge cases if rent ever goes up: fixing rent_exempt_reserve at a ceiling value means it always overshoots, but letting it float means it may overshoot and undershoot

the question becomes moot if we rewrite rent_exempt_reserve at epoch boundaries but it doesnt seem like a hard requirement that we do this

we may wish to consider setting rent_exempt_reserve to our chosen magic number in Meta::default() and Meta::auto(), but because this does not affect the stake program, it seems unecessary to do so

@2501babe 2501babe self-assigned this Mar 3, 2026
Comment on lines -133 to -136
let source_lamports = checked_add(
source_meta.rent_exempt_reserve,
source_stake.delegation.stake,
)?;
Copy link
Member Author

@2501babe 2501babe Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a nice little side effect, we now merge all lamports from an activating source into the delegation of an activating destination instead of leaving out non-rent non-stake lamports

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I hadn't even considered that part

@2501babe 2501babe force-pushed the 20260303_dynamicrent branch from bb19ad6 to 842a7ec Compare March 4, 2026 10:41
@2501babe
Copy link
Member Author

2501babe commented Mar 4, 2026

stake_instruction.rs failures:

failures:
    test_authorize_delegated_stake
    test_deactivate
    test_delegate_deserialize_vote_state::votestateversion_v1_14_11_ok_expects
    test_delegate_deserialize_vote_state::votestateversion_v3_ok_expects
    test_delegate_deserialize_vote_state::votestateversion_v4_ok_expects
    test_split
    test_stake_delegate
    test_withdraw_minimum_stake_delegation
    test_withdraw_stake_before_warmup

test result: FAILED. 47 passed; 9 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s

these are probably all spurious since interface.rs and program_test.rs were fine with no changes. the couple i looked at just do Meta::auto() and rely on rent_exempt_reserve being 0

edit: yep all nonsense. i would however recommend looking at the test_split changes as this test is fairly fiddly. the other test changes should be obvious

Comment on lines +418 to +422
#[deprecated(
since = "3.0.1",
note = "Stake account rent must be calculated via the `Rent` sysvar. \
This value will cease to be correct once lamports-per-byte is adjusted."
)]
Copy link
Member Author

@2501babe 2501babe Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

youll notice we use lamports_per_byte_year in interface.rs. the lamports_per_byte rename comes in solana-rent v4 which landed in agave v4 so i chose to use this term in comments. we may or may not bump solana-rent before release, but its out of scope for this pr

it not unsafe to keep using solana-rent v3 because the underlying onchain bytes just change to a lamports_per_byte_year twice the historical value and an exemption_threshold of 1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's totally fine, we've been adopting that language everywhere already

Comment on lines 546 to +547
let mut dest_meta = source_meta;
dest_meta.rent_exempt_reserve = destination_rent_exempt_reserve;
dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i kept this because we enforce split destination is 200 bytes, so if there is a larger source, we dont want to allow its potentially different rent_exempt_reserve to proliferate

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the explanation

@2501babe 2501babe marked this pull request as ready for review March 4, 2026 17:53
@2501babe 2501babe requested review from grod220 and joncinque March 4, 2026 17:57
@2501babe
Copy link
Member Author

2501babe commented Mar 4, 2026

@grod220 jon agreed to take this but i added you as a secondary reviewer in case youd like to keep abreast of these changes

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really great work!

the question becomes moot if we rewrite rent_exempt_reserve at epoch boundaries but it doesnt seem like a hard requirement that we do this

Looking at the code, I think we'll be better off not rewriting rent_exempt_reserve at the boundary. Like you say, a fixed value that's incorrect, but too high is better than a floating value that can be either too big or too small.

I looked at the stake pool program, and it should be fine with these changes, since as you mention, it'll just overshoot. I'll put in a PR over there to always use Rent too.

Comment on lines +418 to +422
#[deprecated(
since = "3.0.1",
note = "Stake account rent must be calculated via the `Rent` sysvar. \
This value will cease to be correct once lamports-per-byte is adjusted."
)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's totally fine, we've been adopting that language everywhere already

Comment on lines -133 to -136
let source_lamports = checked_add(
source_meta.rent_exempt_reserve,
source_stake.delegation.stake,
)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I hadn't even considered that part

Comment on lines 546 to +547
let mut dest_meta = source_meta;
dest_meta.rent_exempt_reserve = destination_rent_exempt_reserve;
dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the explanation

@2501babe 2501babe merged commit c7dd704 into solana-program:main Mar 5, 2026
23 checks passed
@2501babe 2501babe deleted the 20260303_dynamicrent branch March 5, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants