What Happens to Existing Approvals When an Asset's Rules Change?
A owned 100 units of an asset and approved S to spend up to 30 of them. After S sent 20 to B, A held 80 and B held 20. S's remaining allowance was 10. The article on Native Program validation examined how the asset transfer and the allowance reduction are checked as a single transaction.
Now suppose that, after this transaction, the program that checks the asset's spending rules changes. Should the new program let S spend the remaining 10? If the asset uses another program for a while and then returns to the original one, should the old approval become valid again?
Currently, nigo-protocol preserves existing state when changing a program's execution version, or revision, and opens a new state namespace when replacing it with a different program. State is not reset on every upgrade. But preserving a record does not necessarily preserve the meaning of the user's approval, and starting fresh does not necessarily make every condition for spending stricter.
How much continuity can the ledger guarantee when the rules change? This article separates the state boundaries the ledger enforces from the business meaning that those authorizing a program change must review.
Whose promise does the remaining 10 represent?
In nigo-protocol, assets are recorded with an owner and a quantity. The amount another person may spend on the owner's behalf is a separate record managed by a program. An allowance of 10 does not mean S owns 10 units. It means S may spend up to 10 of A's 80 units, subject to the relevant conditions.
We call a Native Program that checks an asset's transaction conditions a Controller. Here, assume that Controller P manages A's approval under standard allowance rules. The approval record expresses whose assets may be spent, by whom, and in what amount. P interprets that meaning and decides whether a transaction is permitted.
This article concerns Custom Native Assets, which are assets issued by users, configured to use a specific program and execution version. Assume no additional transfers, minting, or burning during policy changes, and zero fees. Until an additional spending transaction is introduced below, the balances therefore remain A=80 and B=20, totaling 100.
A program's unique identifier is its Program ID. Separating three kinds of changes makes it clearer what carries over.
| What changes | What is different | Approval records |
|---|---|---|
| Operator | Who holds operational authority | Preserved |
| P's revision | Execution version under the same ID | Preserved after structural checks |
| P → Q | A program with a different ID | Start in a new namespace |
This table describes when records are preserved. Keeping a record does not mean that a transaction is permitted under every circumstance. At the time of spending, the asset's current status and the program's validation conditions must also allow it.
Changing the operator does not transfer the user's approval
First, suppose only the person operating the asset changes. The key holding operational authority is used for administrative actions such as minting. Passing this key to a new operator is a different change of authority from A allowing S to spend assets.
Replacing the operator does not make A's assets belong to the new operator. It neither turns S's remaining allowance of 10 into the new operator's spending allowance nor deletes the record. A change in administrative authority should not also transfer the permissions an owner has granted.
There is also a party with final authority to approve rule changes. In nigo-protocol, unless that authority has been delegated, the current operator's signature authorizes policy changes. If policy approval authority has been delegated to an EVM governance contract, execution by the designated contract must authorize the change. After delegation, the operator's signature alone cannot directly change the policy. The governance contract may impose its own conditions, such as agreement from several people or a waiting period.
This approval path answers “Who may change P?” That differs from the question A's spending approval answers: “Who may spend 10 of my assets on my behalf?” Even after a policy change has been authorized, what happens to existing user approvals must be decided separately.
Changing a revision under the same ID preserves existing state
Suppose a new version of P fixes a bug or improves its validation logic. nigo-protocol distinguishes a program's identity, its Program ID, from the revision to execute. The asset in this article specifies an exact version of P, including the hash of a descriptor that identifies that version's code and execution conditions. This exact specification of the execution target is called a pin. Publishing and activating a new version does not automatically move the asset's pin. The party authorized to approve policy changes must explicitly change which version the asset uses.
Changing the execution version within the same Program ID preserves the existing program state.
Before allowing this change, the current Controller contract checks that the old and new versions declare the same collection
of state structures, represented by schemaSetRoot. It also checks that the target version is installed,
available at execution time, and has the required capabilities.
Matching structures, however, do not establish matching meanings. Both versions might read the integer 10, while one interprets it as “the amount still available” and the other as “the amount already spent.” Changing how the key for the same record is calculated can also cause problems. The ledger cannot detect these changes in business meaning merely by comparing structures.
A Program ID is not proof that the code has the same meaning, either. A new revision under the same ID may implement allowance checks differently, while a different ID may contain identical code. The ID is an administrative boundary that determines which program a body of state belongs to. Those authorizing changes to the program and the asset must review whether the existing keys and state retain their meaning under the new code.
If P's new revision preserves these meanings, S can continue using the remaining allowance of 10. Users need not approve again with every version change, but they must trust the authority that can change the code interpreting their approval records. There is no guarantee that the code they first approved will remain fixed forever, or that new code will permit only the same scope of spending.
Returning to old code is different from returning to old state
Suppose a compatible revision 2 reduces the allowance in the same approval record from 10 to 5. Even if the asset switches back to an available revision 1, that program should read the current value of 5. A rollback to an earlier code version does not restore the state to its former value of 10.
But changing the key used to locate an approval record between revisions can break this continuity. The following is a separate hypothetical scenario, starting from the remaining allowance of 10. It illustrates why business compatibility needs review; it is not a report of a bug observed in the current standard Controller.
- Revision 1 locates A's approval for S using a key called K1. The remaining allowance at K1 is 10.
- Revision 2 manages the same approval under a different key, K2, leaving K1 untouched.
- In revision 2, A grants S a new allowance of 5 and then revokes it. The allowance at K2 is 0, but the 10 at K1 is unchanged.
- Returning to revision 1 can make the allowance of 10 at K1 readable again. A believes the approval has been revoked, yet the old record is used again.
No assets were transferred in this scenario, so the balances remain A=80 and B=20. The problem is that, even though the two stored records have the same format, a revocation under the new code does not affect the approval under the old code. Revision changes within the same ID preserve the state namespace, so the ledger's namespace checks alone cannot prevent this. A revision review must consider not only the code that will run next, but also how code used after a rollback interprets the current state after intervening changes.
Replacing the program opens a new state namespace
Using Q, a program with a different Program ID, instead of P changes the boundary. The current Controller replacement rule in nigo-protocol is RESET: start in a new program state namespace. Q does not automatically inherit P's remaining allowance of 10.
This rule does not depend on P and Q necessarily having different business meanings. Even with identical code, the current contract requires RESET if the Program IDs differ. Instead of automatically proving that the two programs assign equivalent meanings to their state, the ledger draws a boundary that prevents old state from passing automatically to a different ID.
Immediately after replacement, A still holds 80 and B still holds 20. What starts fresh is the business state managed by the Controller. Balances and supply are not reset. To continue standard approval-based spending, A must grant S permission again under the new Controller. If the Controller also managed state such as an account allowlist, that state must be established again in the new namespace.
RESET is not a migration feature that converts old records into a new format, either. Under the standard approval model, users must approve again, and operators must prepare the necessary policy state. This reconstruction cost accompanies the decision to break automatic inheritance of the old state.
Does missing state always reduce authority?
An allowance can be designed so that no record means no permission to spend. But program state can represent restrictions as well as permissions. For example, suppose a program allows up to 100 units per period and has recorded that 90 have already been spent. If its replacement interprets a missing record as zero usage, the remaining limit within the same period rises from 10 to 100. This is a hypothetical policy illustrating the effect of RESET, separate from the A and S balance example above.
A policy that permits transactions whenever no freeze record exists can have a similar problem. The current Regulated Controller in nigo-protocol rejects ordinary asset transactions when the required account policy records are missing. It therefore does not treat missing records immediately after RESET as the removal of a freeze; the necessary account policies must be registered again. This behavior is a business rule of the Regulated Controller, not a property automatically inherited by every new Controller.
Reviewing a new Controller requires checking both which records must be recreated and what the initial state permits, including which restrictions disappear. RESET prevents old records from being reused as current records. Ensuring that the new code respects the user's consent and existing restrictions also requires business rules and procedures for approving changes.
Returning to P must not revive the old allowance
If replacement with a different ID breaks access to old records, that boundary must also hold when the asset returns to the first ID. Separating state namespaces by Program ID alone leaves a problem. Consider replacing P with Q and then returning to P. If P's old approval record was not deleted, the returning P could treat it as the current approval again. S's old allowance of 10, which replacement was meant to leave behind, would revive. Unlike the earlier revision 1→2→1 example within a single ID, this is a P→Q→P replacement that passes through a different ID.
Preventing this requires distinguishing which generation of state belongs to P, even when the program is the same. nigo-protocol attaches an epoch, or state generation, to Controller state. When the program supplies a logical key for an approval record, Core, which enforces the ledger's common rules, combines it with the current asset and epoch to calculate the actual state identifier. The program's ID is also part of that identifier.
Thus, the same logical key, “A's approval for S,” identifies a different record when the asset, program, or epoch differs. Core checks that the actual inputs and outputs belong to the current namespace, so a program cannot bypass this boundary simply by returning an old key.
The following table assumes that both the policy change counter and the state epoch begin at 0, and that only the P→Q→P replacements occur, with no intervening policy changes. The two replacements produce epochs 1 and 2.
| Point in time | State namespace in use | S's approval state |
|---|---|---|
| Initial use of P | P · epoch 0 | Remaining allowance: 10 |
| Replacement with Q | Q · epoch 1 | No new approval |
| Replacement with P again | P · epoch 2 | No new approval |
| A grants S a new allowance of 5 | P · epoch 2 | Remaining allowance: 5 |
The final P uses a new epoch of state even though it has the same Program ID as the first P. The allowance of 10 in epoch 0 is not added to the allowance of 5 in epoch 2 to make 15. If S now uses the new approval to send 5 to B, the balances become A=75 and B=25, and the new allowance becomes 0. The asset total is still 100; the old allowance of 10 is not used in this transaction.
Under the current contract, the new epoch takes the value of policyRevision, the asset's policy change counter, immediately after replacement.
If other policy changes occur in between, epoch values need not be consecutive numbers such as 0, 1, and 2.
What matters is that an old epoch value is never reused.
A version change within the same program or an operator change increments the policy change counter while preserving the state epoch.
These two numbers serve different purposes. The policy change counter prevents a previously prepared change authorization from being executed again. The request's expected counter must match the current counter, which increments after success. The state epoch defines the namespace of business records the current program may use. The policy change counter prevents replay of old change requests; the state epoch prevents records from a generation before replacement from being reused as current records. Neither mechanism establishes business compatibility between revisions that preserve the same epoch.
RESET alone does not physically delete records from old epochs. A record's continued presence in storage is different from its availability for use in a current transaction. Separate storage policies govern the cost of retaining those records and when they are deleted.
Rule changes are transactions that also need an order in the ledger
We started with a remaining allowance of 10, but the outcome depended on what changed. Changing the operator does not transfer user-granted spending authority to that operator. A revision change under the same ID preserves records, while their meaning and rollback compatibility need separate review. Replacement with a different ID opens a new epoch and isolates the old namespace, but the new policy determines which permissions and restrictions apply in the new namespace's initial state.
Assessing state continuity requires both rules for preserving or isolating records and correct code for interpreting them.
This distinction also matters for transaction ordering. If a transaction in which S spends the remaining allowance arrives together with a transaction replacing P with Q, the ledger must determine which policy and epoch govern validation of the spending transaction. A policy change cannot be treated as independent work merely because approval records differ.
Preserving the Same Ledger Under Parallel Execution extends this question to multiple transactions. To identify which transactions can run together, we need to know not only their inputs, but also the policies and state on which they depend.