I’ve talked before about how using the module systems in OCaml could easily allow for the creation of constitutional rules around tez supply and inflation. This post is meant to flesh out how these concepts can be isolated in the code. Besides the potential for constitutional rules, this design offers several benefits in terms of security and modularity of the protocol code.
The idea is to track where every tez “is” on the chain. Currently, a tez can be attached to:
- A tz1 address, broken down as
- spendable
- temporarily frozen because that address is baking
- a pending block, endorsement, nonce reveal tip or denunciation reward for that address
- pending transaction fees to be paid as part of baking
- A KT1 address
- held by the smart-contract
- part of the storage fees that were paid to a KT1 address (*)
- An unactivated, blinded, tz1 address
- A symbolic source where tez are created (where block rewards come from for instance)
- A symbolic sink where tez tare burnt (where half of the bond is destroyed in a denunciation)
The idea is for an “accounting” module to sit atop of the storage and handle all creation, destruction, freezing, unfreezing, and transfer of tez in the system.
For instance, a transaction is currently processed in apply.ml
by calling a spend
function which verifies the spendability and deduces the tez and then calling Contract.credit
to credit it to the account where the tez is being sent:
| Transaction { amount ; parameters ; destination } -> begin
spend ctxt source amount >>=? fun ctxt ->
begin match Contract.is_implicit destination with
| None -> return (ctxt, [], false)
| Some _ ->
Contract.allocated ctxt destination >>=? function
| true -> return (ctxt, [], false)
| false ->
Fees.origination_burn ctxt >>=? fun (ctxt, origination_burn) ->
return (ctxt, [ Delegate.Contract payer, Delegate.Debited origination_burn ], true)
end >>=? fun (ctxt, maybe_burn_balance_update, allocated_destination_contract) ->
Contract.credit ctxt destination amount >>=? fun ctxt ->
....
Forcing all spending and receipt of tez to flow into a single module would make it much easier to check that important invariants are being preserved by the code (e.g. a transaction never ends up crediting more tokens than it debits).
In this case, this can be achieved by only calling a transfer function and having the responsible module enforce that all transfers net out to 0 tez. In the case of a simple transaction, the apply module might instruct the accounting module to transfer tokens from one address to another.
It should be clear that such an accounting module would be responsible for context storage of balances. If other modules have the power to store their own balance, much of the isolation is lost.
There are several benefits of the refactoring of the code to create such a module:
Short term / immediate benefits:
- Tracking the total supply becomes very easy
- The supply could be provided by an RPC instead of requiring an indexer
- Baking and endorsement rewards can now be indexed on the total supply
- Some invariants become easy to monitor
- A sanity check can be performed on all tez creation in a block transforming any potential inflation bug into a liveness failure which is much easier to recover from.
- Simple invariants like transfers being neutral, etc can be checked
Medium term benefit
- Constitutional amendment restricting the editing of the accounting module by further amendments.
Longer term benefit (requires quite a bit more work)
- Segregate support for basic tz1 -> tz1 transactions to establish a base level of functionality for those accounts through future amendments.
Mostly, none of this is conceptually difficult, but it does require a pretty large refactoring of the code and it touches obviously sensitive parts so, if done, it needs to be done carefully.
(*) As of the current protocol, these fees can not reenter the circulating supply, but it might make sense for them to if contracts can be deleted.