Draft: Proto: Add a Global Table of Constants
This MR implements the table of constants feature described in the corresponding TZIP.
The TZIP discusses the motivations for the change.
Overview
There are three main contributions in the MR:
- A new
Global_constants
module toStorage.ml
that represents a table mapping keys (as strings) to Micheline values.- A subset of functionality is re-exported from
Alpha_context.ml
.
- A subset of functionality is re-exported from
- A new manager operation
register_global_constant
that sets values in this table. - A new Michelson instruction
GET_GLOBAL
retrieves values from this table at runtime.
The MR also includes unit tests for each of the above, building on top of !2340 (merged) (thus, I recommend merging in 2340 first). Lastly, the MR makes an attempt at implementing a new client command executing the new manager operation; however, I got tripped up on a few idiosyncrasies with logging and gas accounting and will need correction.
There are several points of discussion:
Naming Scheme
Currently, arbitrary strings are used as the keys for the table. I don't think is a good idea, and it's merely a placeholder for a more considered naming scheme. I will post a few considerations in the Agora topic regarding this.
Gas and Storage Accounting
I'm not yet familiar with the intricacies of gas and storage accounting, and very likely did it wrong - a pro in this area will need to correct me.
Types, Storage, and Casting
The design in this MR works the following way:
When the user puts a value in the table, they also provide a type for that value. We first check that the type indeed matches the value, and then we store both the type and the value as Micheline. Upon interpreting a contract that uses a value from the table, the user provides the type that is used to decode the value. This means it's possible to cast compatible types, e.g. nat to int and vs. versa. As best I can tell, this isn't an issue.
A few points on this:
- Currently I check that the type matches the value in
apply.ml
. I think this is the wrong abstraction layer to do this in - I should push this down intoalpha_context.ml
. - If the the Micheline value is wholly incompatible with the supplied type, I just return
None
. Maybe it's better to fail entirely in this case? - If no value exists at the given address, then I return
None
. - Even though we don't currently use the stored type, it's important to keep it in case we ever want to migrate data in storage to some new format. For example, in the future we may want to come up with a scheme whereby values are only typed once upon writing to storage, and a typed IR is serialized/deserialized, saving significant gas costs. This will be much easier to achieve if we have the types on hand.
Missing UX
Users will want some kind of RPC and client commands to view values at a given address, but this is not yet implemented. Are there other things users are going to want from this feature?