Global constants
Since the update to Hangzhou protocol, global constants can be registered on chain. These global constants are Micheline values stored on chain, and they can be referred to from a contract we are deploying. Using global constants, we will be able to originate contracts that (after expansion) surpass size limit for contracts.
API primer
The primitive Tezos.constant
allows you to use a predefined constant already registered on chain.
It accepts a hash in the form of a string and will require a type annotation.
Using global constants
Global constants are introduced using Tezos.constant
. This function
expects a constant hash, i.e. a hash that corresponds to a
particular constant that was registered on network.
For instance, the constant hash
expruCKsgmUZjC7k8NRcwbcGbFSuLHv5rUyApNd972MwArLuxEZQm2
corresponds
to the Michelson code
{ PUSH int 2 ; PUSH int 3 ; DIG 2 ; MUL ; ADD }
In a contract, we can make reference to a global constant by using the
Tezos.constant
operation:
let c : int -> int = Tezos.constant "expruCKsgmUZjC7k8NRcwbcGbFSuLHv5rUyApNd972MwArLuxEZQm2"
[@entry]
let main (_p : unit) (s : int) : operation list * int =
([], c s)
const c : ((_p : int) => int) = Tezos.constant("expruCKsgmUZjC7k8NRcwbcGbFSuLHv5rUyApNd972MwArLuxEZQm2")
@entry
let main = (_p : unit, s : int) : [list<operation>, int] =>
[list([]), c(s)]
Note that the constant's type needs to be annotated.
When we compile a contract, we need to tell LIGO (and Michelson
type-checker) which are the constants that we are assuming to be
already registered in the context. They are passed to the compile contract
sub-command in the --constants
argument:
ligo compile contract ./gitlab-pages/docs/advanced/src/global-constants/global_call.mligo --constants "{ PUSH int 2 ; PUSH int 3 ; DIG 2 ; MUL ; ADD }"
ligo compile contract ./gitlab-pages/docs/advanced/src/global-constants/global_call.jsligo --constants "{ PUSH int 2 ; PUSH int 3 ; DIG 2 ; MUL ; ADD }"
Registering global constants with tezos-client
Global constants can be registered on the chain by using tezos-client
as follows:
tezos-client register global constant "{ PUSH int 2 ; PUSH int 3 ; DIG 2 ; MUL ; ADD }" from bootstrap1
which will register the global constant with source bootstrap1
.
Compiling global constants
In general, we can compile a constant doing the following steps:
-
Use
compile expression
sub-command to compile the expression we are interested in capturing in a constant. -
Register the output of
compile expression
usingtezos-client
. -
Capture the constant hash given by
tezos-client
and use it in the code withTezos.constant
. -
Compile the contract that uses the hash constant by passing the argument
--constants
.
In case that tezos-client
is not available, LIGO provides a custom
sub-command to compile constants, which works similar to the compile expression
sub-command, but has the following differences:
-
The output is given as a escaped JSON string that can be given directly to the
--constants
argument ofcompile contract
(or put in a JSON list in a file passed to--file-constants
). -
The hash of the constant is also given in output, so that it can be used without need to call
tezos-client
.
For LIGO users, we recommend to use compile constant
as it
simplifies the usage flow (see example below).
Usage example
Given the following contract global_const
:
let helper ((s, x) : string * int) =
String.length s + x * 3 + 2
[@entry]
let main (p : string) (s : int) : operation list * int =
([], helper (p, s))
const helper = ([s, x]: [string, int]) =>
String.length(s) + x * 3 + 2;
@entry
const main = (p: string, s: int) : [list<operation>, int] =>
[list([]), helper ([p, s])];
We want to turn the function helper
into a global constant. The first
step is to ask LIGO to compile the constant:
ligo compile constant cameligo "helper" --init-file ./gitlab-pages/docs/advanced/src/global-constants/global_const.mligo
# Outputs:
# Michelson constant as JSON string:
# "{ UNPAIR ;\n PUSH int 2 ;\n PUSH int 3 ;\n DIG 3 ;\n MUL ;\n DIG 2 ;\n SIZE ;\n ADD ;\n ADD }"
# This string can be passed in `--constants` argument when compiling a contract.
#
# Remember to register it in the network, e.g.:
# > tezos-client register global constant "{ UNPAIR ;
# PUSH int 2 ;
# PUSH int 3 ;
# DIG 3 ;
# MUL ;
# DIG 2 ;
# SIZE ;
# ADD ;
# ADD }" from bootstrap1
#
# Constant hash:
# exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf
ligo compile constant jsligo "helper" --init-file ./gitlab-pages/docs/advanced/src/global-constants/global_const.jsligo
# Outputs:
# Michelson constant as JSON string:
# "{ UNPAIR ;\n PUSH int 2 ;\n PUSH int 3 ;\n DIG 3 ;\n MUL ;\n DIG 2 ;\n SIZE ;\n ADD ;\n ADD }"
# This string can be passed in `--constants` argument when compiling a contract.
#
# Remember to register it in the network, e.g.:
# > tezos-client register global constant "{ UNPAIR ;
# PUSH int 2 ;
# PUSH int 3 ;
# DIG 3 ;
# MUL ;
# DIG 2 ;
# SIZE ;
# ADD ;
# ADD }" from bootstrap1
#
# Constant hash:
# exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf
As we can see, the constant hash is:
exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf
exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf
We can now remove the helper
function from the code, and replace the
references to helper
by
(Tezos.constant "exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf" : (string * int) -> int)
(Tezos.constant("exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf") as ((_ps : [string, int]) => int))
The new version of global_call
looks as follows:
[@entry]
let main (p : string) (s : int) : operation list * int =
([], (Tezos.constant "exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf")(p, s))
@entry
const main = (p: string, s: int) : [list<operation>, int] =>
[ list([]), Tezos.constant("exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf")([p, s]) ];
Save the constant's Micheline value in a file consts.json
(that will be
used when calling compile contract
). This file must be a JSON list
consisting of the string returned by compile constant
:
["{ UNPAIR ;\n PUSH int 2 ;\n PUSH int 3 ;\n DIG 3 ;\n MUL ;\n DIG 2 ;\n SIZE ;\n ADD ;\n ADD }"]
["{ UNPAIR ;\n PUSH int 2 ;\n PUSH int 3 ;\n DIG 3 ;\n MUL ;\n DIG 2 ;\n SIZE ;\n ADD ;\n ADD }"]
We can compile the code using the compile contract
sub-command,
passing the file with constants in the flag --file-constants
:
ligo compile contract ./gitlab-pages/docs/advanced/src/global-constants/global_call_2.mligo --file-constants ./gitlab-pages/docs/advanced/src/global-constants/consts_cameligo.json
# Outputs:
# { parameter string ;
# storage int ;
# code { constant "exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf" ;
# NIL operation ;
# PAIR } }
ligo compile contract ./gitlab-pages/docs/advanced/src/global-constants/global_call_2.jsligo --file-constants ./gitlab-pages/docs/advanced/src/global-constants/consts_jsligo.json
# Outputs:
# { parameter string ;
# storage int ;
# code { constant "exprv547Y7U5wKLbQGmkDU9Coh5tKPzvEJjyUed7px9yGt9nrkELXf" ;
# NIL operation ;
# PAIR } }
Remember that to deploy this contract, first the global constant needs to be registered in the network where we want to deploy it.
Global constants in the testing framework
In the testing framework, global constants can be registered to the
context by using Test.register_constant
. This primitive takes a
michelson_program
representing the constant to be registered, and
returns an string that is the constant hash corresponding to the
constant registered.
The string returned by Test.register_constant
can be used via
Tezos.constant
, as in the examples above.
A simple usage case is the following, in which we obtain a
michelson_program
by using Test.eval
:
module C = struct
type storage = int
type parameter = unit
type return = operation list * storage
let f (x : int) = x * 3 + 2
let ct = Test.register_constant (Test.eval f)
[@entry]
let main (() : parameter) (store : storage) : return =
[], (Tezos.constant ct store)
end
let test =
let orig = Test.originate (contract_of C) 1 0tez in
let _ = Test.transfer_exn orig.addr (Main ()) 0tez in
assert (Test.get_storage orig.addr = 5)
namespace C {
type storage = int
type parameter = unit
const f = (x : int) => x * 3 + 2;
const ct = Test.register_constant(Test.eval(f));
@entry
const main = (p: parameter, s: storage) : [list<operation>, storage] =>
[list([]), Tezos.constant(ct)(s)];
}
const _test = () => {
let orig = Test.originate(contract_of(C), 1, 0tez);
Test.transfer_exn(orig.addr, Main(unit), 0tez);
assert (Test.get_storage(orig.addr) == 5);
};
const test = _test();