Skip to content

feat: burn gas key balances on key/account deletion#14933

Merged
darioush merged 6 commits into
near:masterfrom
darioush:burn-tokens-delete
Jan 30, 2026
Merged

feat: burn gas key balances on key/account deletion#14933
darioush merged 6 commits into
near:masterfrom
darioush:burn-tokens-delete

Conversation

@darioush

@darioush darioush commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Since NEAR has an accounting of total supply it is necessary to track tokens burnt in gas key deletions.

@darioush darioush marked this pull request as ready for review January 26, 2026 23:02
@darioush darioush requested a review from a team as a code owner January 26, 2026 23:02
state_update: &mut TrieUpdate,
account_id: &AccountId,
) -> Result<(), StorageError> {
) -> Result<Balance, StorageError> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can make a separate utility function instead, it seemed easier to return it here since we already iterate keys.

@codecov

codecov Bot commented Jan 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.14634% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.76%. Comparing base (53f25e8) to head (765423b).
⚠️ Report is 17 commits behind head on master.

Files with missing lines Patch % Lines
core/store/src/utils/mod.rs 57.14% 3 Missing and 3 partials ⚠️
runtime/runtime/src/actions.rs 0.00% 1 Missing and 3 partials ⚠️
runtime/runtime/src/lib.rs 71.42% 0 Missing and 2 partials ⚠️
runtime/runtime/src/access_keys.rs 98.24% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #14933      +/-   ##
==========================================
+ Coverage   68.74%   68.76%   +0.01%     
==========================================
  Files         918      918              
  Lines      200153   200301     +148     
  Branches   200153   200301     +148     
==========================================
+ Hits       137605   137732     +127     
- Misses      56627    56636       +9     
- Partials     5921     5933      +12     
Flag Coverage Δ
pytests-nightly 1.30% <0.00%> (-0.01%) ⬇️
unittests 68.46% <84.14%> (-0.01%) ⬇️
unittests-nightly 68.27% <84.14%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

pugachAG
pugachAG previously approved these changes Jan 29, 2026
Comment thread core/primitives/src/transaction.rs Outdated
Copilot AI review requested due to automatic review settings January 29, 2026 21:27
@darioush darioush enabled auto-merge January 29, 2026 21:27
@darioush darioush added this pull request to the merge queue Jan 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds explicit accounting for tokens burned when gas keys (or accounts containing gas keys) are deleted, so the protocol can correctly track total supply changes attributable to gas-key balance destruction.

Changes:

  • Add tokens_burnt: Balance to ActionResult, including merge/default behavior.
  • Burn and accumulate remaining gas-key balances on DeleteKey (gas key) and DeleteAccount, and incorporate it into receipt tokens_burnt.
  • Update remove_account to return total gas-key balances removed; add unit tests and expand ExecutionOutcome::tokens_burnt docs.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
runtime/runtime/src/lib.rs Adds ActionResult.tokens_burnt and folds it into receipt burn accounting.
runtime/runtime/src/actions.rs Collects gas-key balances burned during account deletion via updated remove_account.
runtime/runtime/src/access_keys.rs Burns gas-key balances on gas-key deletion; adds tests for key/account deletion burn totals.
core/store/src/utils/mod.rs Changes remove_account to compute/return total gas-key balances removed.
core/primitives/src/transaction.rs Updates ExecutionOutcome::tokens_burnt documentation to mention gas-key deletion burns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.unwrap();
tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.price_surplus)?;
tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.refund_penalty)?;
tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?;

Copilot AI Jan 29, 2026

Copy link

Choose a reason for hiding this comment

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

result.tokens_burnt is added into tx_burnt_amount unconditionally, but state changes from deleting gas keys/accounts are rolled back when result.result is Err (state_update.rollback() above). This can report/charge burned gas-key balances for receipts that ultimately fail and revert, leaving the gas keys (and balances) intact. Consider only adding result.tokens_burnt when the receipt commits (e.g., gate on result.result.is_ok()), and add a regression test for a multi-action receipt where a gas-key deletion is followed by a failing action.

Suggested change
tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?;
if result.result.is_ok() {
tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?;
}

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@pugachAG This seems like legit issue, I made this change and added a test.
Please let me know what you think.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

legit issue indeed, the test LGTM, thanks!

Comment thread core/store/src/utils/mod.rs
Comment thread core/primitives/src/transaction.rs Outdated
@darioush darioush removed this pull request from the merge queue due to a manual request Jan 29, 2026
@darioush darioush dismissed pugachAG’s stale review January 29, 2026 22:55

new code added

@darioush darioush requested a review from pugachAG January 29, 2026 22:58
self.tokens_burnt = self
.tokens_burnt
.checked_add(next_result.tokens_burnt)
.ok_or(IntegerOverflowError)?;

@darioush darioush Jan 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@pugachAG I think this is the correct fix for the issue. I think the result should have tokens_burnt = 0 if it is Err, so fixing it in merge makes more sense to me.
We can add the prior check as a defensive check too. Please let me know what you think.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see fundamentally why failed action cannot burn any tokens in the future use cases, so I'd rather make sure that tokens_burnt is properly set when handling the action. Not a strong opinion though, up to you.

self.tokens_burnt = self
.tokens_burnt
.checked_add(next_result.tokens_burnt)
.ok_or(IntegerOverflowError)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see fundamentally why failed action cannot burn any tokens in the future use cases, so I'd rather make sure that tokens_burnt is properly set when handling the action. Not a strong opinion though, up to you.

.unwrap();
tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.price_surplus)?;
tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.refund_penalty)?;
tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

legit issue indeed, the test LGTM, thanks!

@darioush darioush added this pull request to the merge queue Jan 30, 2026
Merged via the queue into near:master with commit 425a4b1 Jan 30, 2026
53 of 54 checks passed
@darioush darioush deleted the burn-tokens-delete branch January 30, 2026 13:48
darioush added a commit to darioush/nearcore that referenced this pull request Feb 4, 2026
Since NEAR has an accounting of total supply it is necessary to track
tokens burnt in gas key deletions.

---------

Co-authored-by: Copilot <[email protected]>
pull Bot pushed a commit to See887777/nearcore that referenced this pull request Jun 3, 2026
# Gas Keys ([NEP-611](near/NEPs#611))
Gas keys are a new type of access key that carries a pre-funded NEAR
balance and multiple independent nonce sequences. When a transaction is
signed with a gas key, gas costs are deducted from the gas key's balance
rather than the signer's account balance (deposits like NEAR transfers
still come from the account).

## Why do we need gas keys?
- Enabling SPICE without a DoS vector. The SPICE project (Separation of
Execution from Consensus) decouples transaction execution from block
production: transactions are included in blocks first, and executed
asynchronously later. This means chunk producers can no longer verify
account balances at inclusion time. An attacker could drain an account's
balance in one transaction, then flood the chain with many transactions
signed by that account. All would be accepted (since execution hasn't
caught up), consuming block space for free when they inevitably fail.
Gas keys close this gap by requiring gas to be pre-committed in the key
itself, so chunk producers can verify gas availability without access to
the latest state.

- Parallel transaction submission. Today, programmatic users who submit
many transactions in parallel must manage multiple access keys (each
with its own nonce sequence). A gas key supports up to 1,024 independent
nonce slots via a single key, removing that complexity.

# Context

- NEP (if exists): near/NEPs#611
- Implementation: 
 - near#14868
 - near#14869
 - near#14883
 - near#14891
 - near#14892
 - near#14911
 - near#14924
 - near#14925
 - near#14930
 - near#14933
 - near#14969
 - near#14985
 - near#15033
 - near#15034
 - near#15067
 - near#15068
 - near#15100
 - near#15101
 - near#15186

# Testing and QA
- unit tests
- test-loop:
https://github.com/near/nearcore/blob/master/test-loop-tests/src/tests/gas_keys.rs
- forknet (automated): near#15207
- forknet (manual testing, planned)

# Checklist
- [x] Link to nightly nayduck run:
https://nayduck.nearone.org/#/run/4756
- [x] Update CHANGELOG.md to include this protocol feature in the
`Unreleased` section.
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.

3 participants