Skip to content

fix(drive)!: merge repeated writes of one balance in a batch so an action fee no longer loses a purchase price - #4987

Merged
QuantumExplorer merged 9 commits into
v4.2-devfrom
claude/first-item-1d405b
Sep 25, 2026
Merged

QuantumExplorer merged 9 commits into
v4.2-devfrom
claude/first-item-1d405b

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Document action fees (#4851, protocol version 14) move credits from the payer to the contract's fee pots with operations appended to the transition's own ones. Every identity balance and fee pot operation computes the new value from the one committed before its batch, and GroveDB keeps only the last write of a key. When the transition already writes the payer's balance, one of the two writes is lost and the credits no longer add up:

  • a purchase that owes a purchase fee: the price never leaves the buyer, but the seller is still paid;
  • a contested document's creation that owes a creation fee: the voting fund never leaves the creator, but the vote poll is still funded;
  • a purchase from a contract owner who sponsors the gas: the sale price and the moderators part of the fee both write the owner's balance, and one is lost.

Only protocol version 14, which no released network runs, is affected.

What was done?

apply_drive_operations 1 merges every write of one identity balance, one fee pot or one prefunded specialized balance in a batch into one net operation (DriveOperation::merge_balance_writes in rs-drive/src/util/batch/drive_op_batch/mod.rs):

  • AddToIdentityBalance / RemoveFromIdentityBalance on the same identity, AddToPot / DeductFromPot on the same contract and pot, and CreateNewPrefundedBalance / DeductFromPrefundedBalance on the same prefunded balance, become one operation for their net change, at the place of the first one;
  • writes that cancel out produce no operation;
  • a key written once keeps its operation untouched, so every other batch is applied exactly as before;
  • estimates are merged the same way, so what fee validation estimates has the shape of what execution applies.

Token writes are refused, not merged (DriveOperation::refuse_repeated_token_balance_writes). They compute from the committed value too, but a transfer writes two balances and a mint or a burn a balance and the supply, so they cannot be netted into operations of their own kinds. A batch writing one token balance or one token supply twice now fails instead of losing a write. No state transition makes such a batch: token operations come only from batch transitions, one per batch, and each writes a key at most once.

Comments where the same trap waits (no behaviour change). The total system credits and address balances are written the same way, from the committed value, but no batch repeats their keys today, so merge_balance_writes leaves them out. SystemOperationType, AddressFundsOperationType and merge_balance_writes now say so and what keeps it true. partially_use_asset_lock notes that its fee loop is only correct because repeated fee strategy steps are refused (FeeStrategyDuplicateError).

validate_fees_of_event 1 estimates for the payer it settles on. It used to estimate every batch with the signer paying all action fees, owner part included, before deciding whether the contract owner sponsors the gas, so for a sponsored batch the estimate described operations execution never applies. Now:

  • a signer who cannot fund the principal is refused first, before anything is estimated;
  • with a sponsor, the batch is estimated with the sponsor paying, and gas_sponsor_pays is decided on that estimate;
  • when the signer pays, a signer whose balance cannot fund the principal plus the action fees they owe is refused unpaid (40210) before estimating, so no merged removal an estimate makes exceeds a balance; then the batch is estimated with the signer paying;
  • fee validation hands execution the payer it settled on: settle_fees_of_event_v1 returns SettledFees (the estimate for the payer and the paying sponsor), validate_fees_of_event_v1 maps it to the estimate for check tx and its other callers, and execute_event 1 charges the settled payer instead of asking gas_sponsor_pays again on the returned estimate.

Example, a purchase from the contract owner, who sponsors the gas and is owed an owner part of MAX_CREDITS - MODERATORS_PART - 1:

Before (one signer-paid estimate, merged): Remove { buyer, price + owner part + moderators part }
  exceeds the MAX_CREDITS an estimate assumes -> check tx: internal insufficient-balance error
After (estimated for the sponsor): Remove { buyer, price }, Add { owner, price - moderators part }
  -> admitted; the owner is never charged the owner part

Because the fix sits in apply_drive_operations, it covers every protocol version 14 batch, not only action fees. execute_event 1 and validate_fees_of_event 1 are unchanged apart from a comment: the fees still travel in the transition's batch.

Example, a buyer with 1 Dash buying a card priced 0.01 Dash whose purchase fee is 0.0001 (owner) + 0.001 (moderators):

operations of the batch
  RemoveFromIdentityBalance { buyer,  0.01   }   // the price
  AddToIdentityBalance      { seller, 0.01   }
  RemoveFromIdentityBalance { buyer,  0.0011 }   // the action fee
  AddToPot { card game, owner,      0.0001 }
  AddToPot { card game, moderators, 0.001  }

Before: both removals read the buyer's committed 1 Dash and the last write wins
  buyer 0.9989 Dash (the price is never taken), seller +0.01 Dash, pots +0.0011 Dash
After: merged into RemoveFromIdentityBalance { buyer, 0.0111 }
  buyer 0.9889 Dash, seller +0.01 Dash, pots +0.0011 Dash

The other two cases, as the new tests see them:

Case Before After
Contested creation, creation fee 0.0011 Dash, voting fund 0.1 Dash creator pays fee + gas only creator pays fund + fee + gas
Purchase at 0.01 Dash from a contract owner who sponsors the gas owner ends 0.01 Dash short (the sale price is lost) owner gets the price, pays the 0.001 Dash moderators part and the gas

Generation edited in place

apply_drive_operations 1 is selected only by DRIVE_VERSION_V9, which only protocol version 14 uses, and that version is unreleased, so it is amended in place. Generation 0 (protocol versions 1 to 13) is untouched. Any change at protocol version 14 still splits a devnet running 4.2.0-beta.4 from nodes running this, hence the !.

Docs: the fees chapter of the book, the action_fee_operations and AddToPot doc comments, and the protocol version 14 changelog comments (v14.rs, drive_versions/v9.rs).

How Has This Been Tested?

  • New drive unit tests for the merge: repeated identity writes merged in place of the first, pot writes merged with a cancelling pair dropped, prefunded balance writes merged, a batch writing each key once left as it is; and for the token refusal: a balance or a supply written twice is refused, token writes on distinct keys are admitted.
  • New drive-abci tests in batch/tests/document/action_fees.rs, each run through check tx at both levels (assert_check_tx_valid_at_all_levels, which also guards the committed state) and then executed, checking the payer's and seller's balances, both pots and the total credits:
    • should_charge_the_buyer_both_the_price_and_the_purchase_fee
    • should_pay_a_seller_who_sponsors_the_gas_the_price_less_the_moderators_part_and_the_gas
    • should_charge_the_creator_of_a_contested_document_both_the_voting_fund_and_the_creation_fee
    • should_admit_a_sponsored_purchase_whose_waived_owner_part_no_signer_could_pay: owner part MAX_CREDITS - MODERATORS_PART - 1; passes check tx and executes with the owner part waived; fails check tx with an internal insufficient-balance error when the estimate is made for the signer and merged (found in review)
    • should_charge_the_buyer_when_the_sponsor_covers_only_the_buyers_estimate: a purchase from a third-party seller owing only a moderators part, whose buyer-paid estimate is lower (the fee merges into the price); the preferred sponsor is settled one credit short of their own estimate, measured at execution with them paying. The sponsor pays nothing, fee validation returns the buyer's estimate (which the sponsor would cover), and the buyer pays price, fee and gas. If execution asks the sponsor question again on the returned estimate, it charges the passed-over sponsor
    • should_refuse_unpaid_a_buyer_who_cannot_fund_the_price_and_the_purchase_fee: the same fee paid by the buyer is refused unpaid with 40210 in check tx and at execution; without the pre-check the merged estimate fails check tx with an internal error
  • With the merge disabled, all three fail: the buyer and the creator keep the 0.01 Dash price and the 0.1 Dash voting fund, and the sponsoring seller ends 0.01 Dash short.
  • cargo test -p drive-abci --lib: 3530 passed, 0 failed, on the current head.
  • cargo test -p drive --lib -- drive_op_batch fee_pots identity::update prefunded_specialized_balances tokens: 286 passed.
  • cargo clippy -p drive -p drive-abci -p platform-version --all-targets -- -D warnings clean, also after merging the latest v4.2-dev; cargo fmt --all --check clean.
  • Not run locally: the full drive suite and the strategy tests.

Breaking Changes

Consensus change at protocol version 14 only: a batch that writes one identity balance or fee pot more than once is now applied with the writes merged. No released protocol version changes.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request (no structure change)

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · fe9dfd1

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build failed
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

Summary by CodeRabbit

  • Bug Fixes
    • Batched transactions now combine changes to identity balances, contract fee pots, and prefunded specialized balances into one net update.
    • Batches that write the same token balance or token supply more than once are now refused.
    • Fee estimates now reflect the payer selected during fee validation, including applicable action fees. When a sponsor cannot cover fees and sponsorship is not strict, the identity pays using its estimate.
  • Documentation
    • Clarified gas-payer selection and how balance and fee-pool updates are handled during batch processing.

QuantumExplorer and others added 2 commits September 24, 2026 22:33
Every identity balance and contract fee pot operation computes the new
value from the one committed before its batch, and GroveDB keeps only
the last write of a key. A document action fee is appended to the
transition's own operations, so when those already write the payer's
balance one of the two writes was lost:

- a purchase that owes a purchase fee kept the price in the buyer's
  balance while the seller was still paid;
- a contested document's creation that owes a creation fee kept the
  voting fund in the creator's balance;
- a purchase from a contract owner who sponsors its gas lost either the
  sale price or the moderators part of the fee.

The credits then no longer add up.

apply_drive_operations 1 (selected only by protocol version 14, edited
in place) now merges every write of one identity balance or one fee pot
in a batch into a single net operation, at the place of the first one.
A key written once keeps its operation untouched, so every other batch
is applied exactly as before.

Tests: merge unit tests in drive; a buyer-pays purchase, a purchase from
a sponsoring seller and a contested creation, each owing an action fee,
checked through check tx and execution, all three failing without the
merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 24, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 24, 2026
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-09-25T16:48:05.994Z

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 9c41b6eb-46e7-4344-a165-54ebfb1449ae

📥 Commits

Reviewing files that changed from the base of the PR and between fe800d7 and bb2848f.

📒 Files selected for processing (5)
  • packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v1/mod.rs
  • packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v1/mod.rs
  • packages/rs-drive/src/util/batch/drive_op_batch/mod.rs
  • packages/rs-platform-version/src/version/drive_versions/v9.rs
  • packages/rs-platform-version/src/version/v14.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/rs-platform-version/src/version/v14.rs
  • packages/rs-platform-version/src/version/drive_versions/v9.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Fee validation now returns the selected payer with its estimate, and execution charges that payer. Drive batches net prefunded specialized-balance writes and refuse repeated writes to a token balance or supply. Tests cover fee, sponsorship, purchase, and contested-domain outcomes.

Changes

Fee settlement and batch accounting

Layer / File(s) Summary
Settle fee estimate and payer
packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/validate_fees_of_event/v1/mod.rs, packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v1/mod.rs, packages/rs-drive/src/state_transition_action/batch/mod.rs, packages/rs-platform-version/src/version/v14.rs, book/src/fees/overview.md
Fee validation returns the selected payer with a payer-specific estimate. Execution charges the returned payer and uses the returned estimate for paid outcomes.
Merge balance writes and refuse repeated token writes
packages/rs-drive/src/util/batch/drive_op_batch/*, packages/rs-platform-version/src/version/drive_versions/v9.rs, packages/rs-platform-version/src/version/v14.rs, book/src/fees/overview.md
Batch processing nets writes to identity balances, contract fee pots, and prefunded specialized balances. It refuses repeated writes to the same token-holder balance or token supply. Tests cover netting and token-write handling.
Validate action-fee and sponsorship outcomes
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/action_fees.rs, packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/gas_sponsorship.rs
Tests cover buyer-funded and sponsored purchases, sponsor shortfalls, insufficient balances, and contested-domain creation. Helpers process transitions and inspect balances, fee pots, and tree credits.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant FeeValidation
  participant Sponsor
  participant Identity
  participant execute_event_v1
  FeeValidation->>Sponsor: Estimate sponsor gas and action fees
  FeeValidation->>Identity: Estimate identity costs if sponsor does not pay
  FeeValidation->>execute_event_v1: Return settled payer and estimate
  execute_event_v1->>Sponsor: Charge sponsor when selected
  execute_event_v1->>Identity: Charge identity when selected
Loading

Suggested reviewers: thepastaclaw

Merge Risk: ⚪ Minimal · up to bb284

The identified compatibility and transfer concerns do not block merging after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to bb284

The change addresses a credit-accounting failure and keeps fee validation and execution aligned on the payer. No introduced security defect was established, but replay, concurrent execution, and recovery behavior were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Affected transitions can move credits between an identity, a contract's fee pots, and prefunded balances; incorrect payer selection or repeated-key handling would therefore affect credit ownership, not just fee estimates.

Trust Boundaries and Controls

  • observed — The changed execution method is internally visible, not a new directly exposed endpoint. Its payer comes from settlement rather than directly from the event's sponsor field; settlement checks sponsor coverage and falls back to identity payment when a non-strict sponsor does not cover the charge.

Resilience and Maintainability Implications

  • observed — The merge keys distinguish identity, contract-and-pot, and prefunded balances. Zero nets produce no write, while overflow is rejected; this supports intra-batch conservation but does not by itself establish replay or concurrent-transition guarantees.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.19% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: merging repeated balance writes in a batch to prevent purchase prices from being lost when action fees apply.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 4th in line, estimated start in ~1.3 h (commit fe9dfd1)
Estimated review time once started: ~55 min (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 2 only (queue backlog)

The balance merge is correctly confined to protocol version 14, but it introduces a confirmed fee-estimation regression that rejects an affordable sponsored purchase. The boundary-case test fails at the reviewed head and passes when only estimation retains its previous behavior; the original test also passes after restoring all temporary changes. The alternate payout-path issue is pre-existing and retained only as a separate follow-up, while the mixed-sign precondition finding does not establish a defect in the intended netting behavior.

🔴 1 blocking

Review provenance

Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — DriveOperation::merge_balance_writes and apply_drive_operations_v1 introduce intricate batch-wide netting of identity balances and fee pots, changing consensus-critical funds movement through signed aggregation, cancellation, overflow handling, and operation ordering.
  • Phase 1 reviewers: not run (skipped for throughput: 18 PRs queued, above the 10 limit)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v1/mod.rs`:
- [BLOCKING] packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v1/mod.rs:62: Keep waived owner fees from invalidating sponsored-purchase estimates
  `validate_fees_of_event_v1` estimates the transition together with `action_fee_operations(identity.id, ...)` before deciding whether the contract owner sponsors it. Merging those operations combines the purchase price with hypothetical signer-paid fees, and `remove_from_identity_balance_operations_v0` rejects the combined debit when it exceeds the stateless estimate's `MAX_CREDITS` balance. I independently reproduced this by changing the sponsored-purchase fixture's declared owner fee to `MAX_CREDITS - MODERATORS_PART - 1`, which satisfies the fee limits: `FirstTimeCheck` returns an internal insufficient-balance error for removing 9223372037854775806 from 9223372036854775807. The actual purchase is affordable because the buyer pays only the price and the sponsoring contract owner pays only the moderators part plus gas; the owner part is waived. Keeping execution merging while temporarily restoring the previous estimation behavior makes the same boundary test pass, including its balance assertions. Make fee estimation account for sponsorship without rejecting on this hypothetical signer debit, and add the boundary case as a regression test.
Out-of-scope follow-up suggestions (2)

These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.

  • Epoch reward payouts can still overwrite additions to a shared recipient — add_epoch_pool_to_proposers_payout_operations_v0 accumulates reward-share and proposer balance additions, lowers them through convert_drive_operations_to_grove_operations, and wraps the resulting replacements in GroveDBOpBatch. The conversion path does not merge repeated balance additions, and the reward-share contract's unique index on ($ownerId, payToId) permits different masternodes to share a recipient. Those already-lowered replacements remain opaque to the new merge. Both the caller and conversion behavior are present in the base commit and unchanged by this PR, so this is a separate credit-accounting issue rather than a regression required to fix the action-fee scenarios.
    • Follow-up: Track a separate version-gated repair for shared-recipient epoch payouts, with a test exercising the production conversion path.
  • Normalize balances at both Drive batch-lowering entry points — Out of scope as a blocking request. Comparing base 9eb59ec with the reviewed head confirms that the epoch payout caller and alternate conversion path already lower repeated additions independently. This PR neither introduces nor worsens that path, and its action-fee repair does not depend on it. The concrete accounting concern is retained as a separate follow-up.
    • Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Sep 24, 2026
…timated

Fee validation estimates the signer paying every action fee before it
settles on a gas sponsor, and a contract owner who sponsors a purchase of
their own document is never charged the owner part. An estimate prices
each identity balance removal against MAX_CREDITS, so merging the purchase
price with that hypothetical fee could exceed it and fail the estimate
(an internal insufficient-balance error in check tx) for a purchase the
sponsor pays in full.

An estimate now keeps the writes as they are: separately they cost at
least what the merged write does, and none of them exceeds the largest
balance on its own. Only an applied batch is merged.

Regression test: a sponsored purchase from the contract owner whose owner
part is MAX_CREDITS - MODERATORS_PART - 1 passes check tx and executes;
it fails check tx when estimates are merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Sep 25, 2026
@QuantumExplorer QuantumExplorer changed the title fix(drive)!: merge repeated writes of one balance in a batch so an action fee no longer loses a purchase price (PV14) fix(drive)!: merge repeated writes of one balance in a batch so an action fee no longer loses a purchase price Sep 25, 2026
QuantumExplorer and others added 6 commits September 25, 2026 18:19
…n settles on

Fee validation estimated every batch with the signer paying all action
fees, owner part included, before deciding whether the contract owner
sponsors the gas, so the estimate described a batch execution never
applies when the sponsor pays. It now estimates for the payer:

- with the sponsor paying first, and decides gas_sponsor_pays on that;
- with the signer paying only when the sponsor does not pay, after
  refusing a signer whose balance cannot fund the principal and the
  action fees they owe (known without an estimate);
- when the sponsor is passed over and the signer's estimate is the
  smaller, the sponsor's is returned: execution asks gas_sponsor_pays
  again on the returned estimate and must pass the sponsor over too.

With estimates made for the real payer, apply_drive_operations 1 merges
estimates as well as applied batches again, so what is estimated has the
shape of what is applied. The pre-check keeps every merged removal an
estimate makes for the signer within their balance.

Regression test: a buyer paying the gas who cannot fund the price and a
purchase fee near MAX_CREDITS is refused unpaid for an insufficient
balance (40210) in check tx and at execution; without the pre-check the
merged estimate fails check tx with an internal error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r's estimate

Execution asks gas_sponsor_pays again on the estimate fee validation
returns, so when fee validation passes a sponsor over and the signer's
estimate is the smaller one, it returns the sponsor's. The new test
builds that case: a purchase from a third-party seller owing only a
moderators part, whose buyer-paid estimate is lower because the fee
merges into the price, with the preferred sponsor settled one credit
short of their own estimate. It checks the sponsor pays nothing, the
returned estimate is the sponsor's, and the buyer pays price, fee and
gas. Without the rule, execution charges the passed-over sponsor.

The sponsor's estimate is measured at execution (check tx skips a
batch's state validation) with the sponsor paying, and re-measured as
their balance is lowered: reading that balance costs less as it shrinks.

card_for_sale now takes the seller and the purchase fee; purchase_by
takes the buyer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efunded balances, refuse repeated token writes (PV14)

Fee validation hands execution the payer it settled on. Execution used to
ask gas_sponsor_pays again on the estimate fee validation returned, which
forced fee validation to return the sponsor's estimate whenever it passed
the sponsor over and the signer's estimate was the smaller one.
settle_fees_of_event_v1 now returns SettledFees (the estimate for the
payer and the paying sponsor), validate_fees_of_event_v1 maps it to the
estimate for its other callers, and execute_event 1 charges the settled
payer. The returned-estimate rule is gone: a passed-over sponsor's batch
now returns the signer's own estimate.

apply_drive_operations 1 also merges repeated writes of one prefunded
specialized balance (a contested document's voting fund), which compute
from the committed value like identity balances and fee pots. Token
writes do the same, but a transfer writes two balances and a mint or a
burn a balance and the supply, so they cannot be netted into operations
of their own kinds; a batch writing one token balance or token supply
twice is refused instead. No state transition makes such a batch: token
operations come only from batch transitions, one per batch, each writing
a key at most once.

Tests: drive unit tests for the prefunded merge and the token refusal;
the passed-over-sponsor test now checks the returned estimate is the
buyer's, which the sponsor covers, and fails if execution asks the
sponsor question again on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-1d405b

# Conflicts:
#	packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v1/mod.rs
#	packages/rs-platform-version/src/version/drive_versions/v9.rs
#	packages/rs-platform-version/src/version/v14.rs
System credits and address balances are written like identity balances,
from the value committed before the batch, but merge_balance_writes does
not merge them because no batch repeats their keys today. Say so where
the operations are defined and in merge_balance_writes, with what keeps
it true (one system credits write per transition or block; no address
both input and output, maps for inputs and outputs, input fees in a batch
of their own).

partially_use_asset_lock deducts each fee strategy step from the input's
original balance and sets it absolutely, which holds only because basic
structure validation refuses repeated steps; note that next to the loop.

Comments only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

@QuantumExplorer
QuantumExplorer merged commit ffd4fb4 into v4.2-dev Sep 25, 2026
39 of 41 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/first-item-1d405b branch September 25, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants