Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d8ca6d
feat(platform)!: introduce protocol version 15 with drive table v10 f…
DCG-Claude Sep 12, 2026
236d9ae
feat(drive)!: require fee history for storage refunds and credit thei…
DCG-Claude Sep 12, 2026
64afeee
test(drive): pin fee history handling of calculate_fee v0 and v1
DCG-Claude Sep 12, 2026
34d9470
test(drive): cover the recorded-owner refund credit primitive
DCG-Claude Sep 12, 2026
4dfd271
test(drive): close group actions through the production funnel with f…
DCG-Claude Sep 12, 2026
e6faacb
test(drive): pin the protocol 12 schema strip as the recorded refund …
DCG-Claude Sep 12, 2026
7318434
docs(book): describe fee history and refund ownership from protocol v…
DCG-Claude Sep 12, 2026
f48f202
test(drive): pass the fee history where tests replace epoch-flagged d…
DCG-Claude Sep 12, 2026
3fc164e
test(drive): name the per-owner refund fixtures for clippy
DCG-Claude Sep 12, 2026
2d85fb1
fix(drive): report refund credits that repay identity debt for the pr…
DCG-Claude Sep 12, 2026
c42d9c2
docs(book): say lifecycle refund settlement is not yet wired at proto…
DCG-Claude Sep 12, 2026
afcebce
docs(book): attribute storage refunds to the recorded owner in the fe…
DCG-Claude Sep 12, 2026
9235bac
refactor(drive): read each refund owner once when crediting its balance
DCG-Claude Sep 12, 2026
f6152d4
test(drive): unban, unsuspend and replace suspensions through the pro…
DCG-Claude Sep 22, 2026
61bfff0
fix(drive): price ephemeral TTL bytes in calculate_fee v1 as v0 does
DCG-Claude Sep 22, 2026
294a81d
test(drive): close the structure fixture's group action with fee hist…
DCG-Claude Sep 22, 2026
3dc7ce4
fix(drive)!: price a batch before committing the transaction drive owns
DCG-Claude Sep 23, 2026
ec5f8e7
test(drive): assert a rejected refund pricing leaves state untouched …
DCG-Claude Sep 23, 2026
be2648f
fix(drive)!: price document and contract writes before committing the…
DCG-Claude Sep 24, 2026
044853e
test(drive): assert rejected document and contract pricing leaves sta…
DCG-Claude Sep 24, 2026
aeb344a
fix(drive)!: price add_document, index-only deletes and warning repla…
DCG-Claude Sep 27, 2026
11b73de
test(drive): assert rejected add_document, index-only delete and warn…
DCG-Claude Sep 27, 2026
b15064c
refactor(drive): take the repaid debt of a storage refund from the ba…
DCG-Claude Sep 27, 2026
76e85b8
fix(drive): price the contract fetch when adding a document by contra…
DCG-Claude Sep 27, 2026
d33de98
fix(drive)!: price contested document inserts before committing
DCG-Claude Sep 27, 2026
6d9c5ce
test(drive): assert a rejected second contender leaves a no-locking c…
DCG-Claude Sep 27, 2026
324f10e
test(drive): give the refund regression fixtures fixed owner ids
DCG-Claude Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions book/src/fees/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ in `FeeStorageVersion`:
| `storage_seek_cost` | 2,000 | Cost of a single disk seek |

Storage fees are **refundable**: when data is deleted, a portion of the original
storage fee is returned to the identity that paid it (see [Refunds](#refunds)
below).
storage fee becomes a refund for the owner recorded in the stored bytes' storage
flags, which is not always the identity that paid the fee (see
[Refunds](#refunds) below).

The documents of a type that declares a `ttl` (protocol version 14) are the exception: they
carry no storage flags and refund nothing, their bytes are priced for the time they live, and
Expand Down Expand Up @@ -468,6 +469,38 @@ out to proposers.
There is a **dust limit**: refunds below 32 bytes worth of storage credits are
discarded to prevent micro-refund spam.

### Fee history and refund ownership (protocol version 15 onward)

A refund is priced with the fee history of the block that removes the bytes:
the `previous_fee_versions` map platform state carries, which the epoch change
hook extends whenever the fee version number changes. `Drive::calculate_fee`
v1 (`DRIVE_VERSION_V10`) consults that history for every owner-attributed
storage removal, on every fee version number, and returns an internal error
when a caller passes none. Earlier generations priced fee version number 1
against an empty history, so a caller that forgot the history silently
refunded at the first generation's storage rates; from protocol version 15
that omission halts instead of mispricing. Every shipped schedule shares fee
version number 1 and the same storage rates, so the credits themselves are
unchanged for every shipped input.

Refunds follow the recorded owner in the element's storage flags.
`Drive::credit_storage_refunds_to_owners_operations` credits each owner that
has a balance element without consulting any key or permission, so a frozen
but existing owner still receives its bookkeeping refund. Two shares of a
refund never reach a balance and are reported for the caller instead: the
part that clears an owner's negative credit (identity debt, which lives
outside the credit sum trees) and the part whose owner has no balance element
(the native stand-in for a wiped owner). The caller moves both into the
current epoch's processing pool with a single pool write and records every
refund against its storage epoch in the pending epoch refunds, so the credit
conservation check stays balanced. This primitive is the settlement step
block lifecycle paths that remove owner-attributed bytes are meant to use in
the block that removes them; at protocol version 15 the vote poll end cleanup
does not yet price or settle its refunds, and wiring it up is a separate
change. The protocol 12 schema migration, which shrank stored contracts
without refunding the stripped bytes, ran once at that activation and is the
recorded historical exception; it replays exactly as executed.

## Epoch-Based Fee Distribution

Fees do not go directly to the block proposer. Instead, they accumulate in
Expand Down Expand Up @@ -541,6 +574,15 @@ Fee versions are stored in the `FEE_VERSIONS` array and looked up by number. The
`uses_version_fee_multiplier_permille` field allows a global scaling factor
(permille = divide by 1000; a value of 1000 means no change).

`fee_version_number` keys the persisted fee history that refunds are priced
against. A schedule that changes storage rates needs a new number, because the
refund code resolves the schedule for an epoch through the history and (in
generations before protocol version 15) shortcut number 1 to the first
generation's rates. `FEE_VERSION1` and `FEE_VERSION2` share number 1 because
only a non-storage group changed between them; a test in `rs-drive`'s fee
operation module pins that every shipped schedule keeps the first generation's
storage rates.

## Key Source Files

| File | Contents |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,13 @@ mod tests {
use dpp::dashcore::Network;
use dpp::data_contract::document_type::random_document::CreateRandomDocument;
use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters};
use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
use dpp::identifier::Identifier;
use dpp::platform_value::Value;
use dpp::tests::json_document::json_document_to_contract;
use dpp::version::fee::FeeVersion;
use drive::drive::contract::moderation::types::ContractDocumentRemovalEntry;
use std::collections::BTreeMap;

const YAPPR_CONTRACT_PATH: &str =
"../rs-drive/tests/supporting_files/contract/yappr-likes/yappr-likes-contract.json";
Expand Down Expand Up @@ -509,6 +512,9 @@ mod tests {
fn should_report_a_deleted_post_of_a_deletable_document_join() {
let (platform, state, version, contract) =
setup_yappr_state_at(YAPPR_DELETABLE_POSTS_CONTRACT_PATH);
// The post is owner-flagged, so pricing its removal needs the fee history of the
// removing block, as every production caller passes.
let fee_history: CachedEpochIndexFeeVersions = BTreeMap::from([(0, FeeVersion::first())]);
platform
.drive
.delete_document_for_contract(
Expand All @@ -519,7 +525,7 @@ mod tests {
true,
None,
version,
None,
Some(&fee_history),
)
.expect("expected to delete the post");

Expand Down
Loading
Loading