By Daniel Hines
One of the chief complaints of smart contract developers on Tezos is running out of bytes - you have some awesome new application you want to write, but there's no way to write the code such that it fits within Tezos' size limit for contracts. In protocol Hangzhou, we introduce a small but powerful feature called global constants to address this issue.
The "global" in global constants refers to the fact that any Tezos user can register a Micheline value in a new table built into the protocol by submitting a new register_global_constantoperation and paying the cost of storage. The values are indexed by their hash, using the same algorithm used to hash big_map keys. This forms a kind of content-address able global "library" of Micheline expressions that can be referenced and used in smart contracts. Note I say Micheline, the data format, not Michelson the programming language (check out the respective Micheline and Michelson docs if the distinction isn't clear). Values stored in the table are not first type-checked, so you can store whatever you want, but the responsibility of using the values correctly is on the user.
Once registered, a constant can be used in a contract with a new constant primitive that takes a single valid hash as a string - something like constant "expruQN5r2umbZVHy6WynYM8f71F8zS4AERz9bugF8UkPBEqrHLuU8" (where expruQQN5... is the hash of the Micheline integer value 999). Global constants work just like macros - an expression like consant "<exprhash>" is the macro, and it expands to the value stored in table. Any Micheline node in the source code of a Michelson contract can be replaced with a constant. The macro is expanded before execution, but the protocol only stores the unexpanded form, allowing you to originate much larger contracts - about five times larger in Hangzhou's configuration (more on that below).
A Brief Example
Let's work through an example. Suppose we're writing a contract where the type of the parameter is very large, and repeated often in the code (a common case, especially in contracts compiled from a higher-level language). For the sake of simplicity, we'll use the type unit, represented by a single Micheline primitive - but in practice it could be much larger expression.
Here's an example of a contract:
We could register the Micheline expression unit for reference from anywhere in the contract:
You can see in the operation receipt the global address for unit isexprvKFFbc7SnPjkPZgyhaHewQhmrouNjNae3DpsQ8KuADn9i2WuJ8.and replace each instant with a constant, compressing the size of the contract.(In this example the expression unit is actually smaller than constant representing it, but you get the idea).
Constants can refer to other constants, and they'll be expanded recursively at type checking time. However, constant expansion is gas metered, so there is a protocol-dependent limit on the total size of a fully-expanded constant. The depth of a fully-expanded constant is limited to 10,000. Additionally, to protect the chain in non-gas-metered situations, there is a hard limit on both the total number of nodes and the number of bytes in a fully expanded constant. These are controlled by protocol constants, and in Hangzhou are set to 50,000 each.
As the first release of the feature, there are no RPC end points for querying which constants have been registered. However, you can always calculate the hash of an expression, and we suggest indexers use the operation receipts to map registered expressions to their hashes as a stop gap until proper RPC end points are implemented.
Other Use Cases
While originating bigger contracts is the primary use case, we hope to see other applications as well. One idea is fully audited "trusted hashes" for critical Michelson code that can easily be integrated into a wide array of contracts - think FA2 library code. If you have ideas for how cool applications or thoughts on how the global constant feature could be improved or expanded, let us know!