#improvements

By Daniel Hines

One of the chief complaints of smart contract developers on Tezos is running out of bytes - youhave some awesome new application you want to write, but there's no way to write the code suchthat it fits within Tezos' size limit for contracts. In protocol Hangzhou, we introduce a small but powerfulfeature called global constants to address this issue.

The "global" in global constants refers to the fact that any Tezos user can register a Michelinevalue 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 samealgorithm used to hash big_map keys. This forms a kind of content-addressable 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 Michelineand 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 theresponsibility of using the values correctly is on theuser.

Once registered, a constant can be used in a contract with a new constant primitive that takesa single valid hash as a string - something likeconstant "expruQN5r2umbZVHy6WynYM8f71F8zS4AERz9bugF8UkPBEqrHLuU8" (where expruQQN5... is the hash of theMicheline 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 contractcan be replaced with a constant. The macro is expanded before execution, but the protocol only stores store 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 repeatedoften 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:

$ tezos-client register global constant unit from bootstrap1 --burn-cap 0.01675
​
Node is bootstrapped.
Estimated gas: 1440 units (will add 100 for safety)
Estimated storage: 67 bytes added (will add 20 for safety)
Operation successfully injected in the node.
Operation hash is 'ooKXF2SkqBeKJtpFgoUuLLYXiqC3K2Khatz13XTecDBqwfufmFy'
NOT waiting for the operation to be included.
Use command
  tezos-client wait for ooKXF2SkqBeKJtpFgoUuLLYXiqC3K2Khatz13XTecDBqwfufmFy to be included --confirmations 5 --branch BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU
and/or an external block explorer to make sure that it has been included.
This sequence of operations was run:
  Manager signed operations:
    From: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx
    Fee to the baker: ꜩ0.000384
    Expected counter: 1
    Gas limit: 1540
    Storage limit: 87 bytes
    Balance updates:
      tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ................ -ꜩ0.000384
      fees(the baker who will include this operation,0) ... +ꜩ0.000384
    Register Global:
      Value: unit
      This global constant registration was successfully applied
      Balance updates:
        tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx ... -ꜩ0.01675
      Consumed gas: 1440
      Storage size: 67 bytes
      Global address: exprvKFFbc7SnPjkPZgyhaHewQhmrouNjNae3DpsQ8KuADn9i2WuJ8

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 attype checking time. However, constant expansion is gas metered, so there is aprotocol-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 boththe 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 endpoints for querying which constantshave been registered. However, you can always calculate the hash of an expression, and wesuggest indexers use the operation receipts to map registered expressionsto their hashes as a stop gap until proper RPC endpoints are implemented.

Other Use Cases

While originating bigger contracts is the primary use case, we hope to see other applicationsas well. One idea is fully audited "trusted hashes" for critical Michelson code thatcan 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 constantfeature could be improved or expanded, let us know!

Conclusion

Interested readers can find out more about the usage of constants inthis docs MR,or start at the beginning with the TZIP andMR

Happy hashing!

If you want to know more about Marigold, please follow us on social media (Twitter, Reddit, Linkedin)!

Scroll to top