Interop

LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types.

Michelson types and annotations

Michelson types consist of or's and pair's, combined with field annotations. Field annotations add contraints on a Michelson type, for example a pair of (pair (int %foo) (string %bar)) will only work with the exact equivalence or the same type without the field annotations.

To clarify:

(pair (int %foo) (string %bar))

works with

(pair (int %foo) (string %bar))

works with

(pair int string)

works not with

(pair (int %bar) (string %foo))

works not with

(pair (string %bar) (int %foo))
info

In the case of annotated entrypoints - the annotated or tree directly under parameter in a contract - you should annotations, as otherwise it would become unclear which entrypoint you are referring to.

Entrypoints and annotations

It's possible for a contract to have multiple entrypoints, which translates in LIGO to a parameter with a variant type as shown here:

type storage is int
type parameter is
| Left of int
| Right of int
function main (const p: parameter; const x: storage): (list(operation) * storage) is
((nil: list(operation)), case p of
| Left(i) -> x - i
| Right(i) -> x + i
end)

This contract can be called by another contract, like this one:

type storage is int
type parameter is int
type x is Left of int
function main (const p: parameter; const s: storage): (list(operation) * storage) is block {
const contract: contract(x) =
case (Tezos.get_entrypoint_opt("%left", ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx":address)): option(contract(x))) of
| Some (c) -> c
| None -> (failwith("not a correct contract") : contract(x))
end;
const result: (list(operation) * storage) = ((list [Tezos.transaction(Left(2), 2mutez, contract)]: list(operation)), s)
} with result

Notice how we directly use the %left entrypoint without mentioning the %right entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to.

This currently only works for or's or variant types in LIGO.

Interop with Michelson

To interop with existing Michelson code or for compatibility with certain development tooling, LIGO has two special interop types: michelson_or and michelson_pair. These types give the flexibility to model the exact Michelson output, including field annotations.

Take for example the following Michelson type that we want to interop with:

(or
(unit %z)
(or %other
(unit %y)
(pair %other
(string %x)
(pair %other
(int %w)
(nat %v)))))

To reproduce this type we can use the following LIGO code:

type w_and_v is michelson_pair(int, "w", nat, "v")
type x_and is michelson_pair(string, "x", w_and_v, "other")
type y_or is michelson_or(unit, "y", x_and, "other")
type z_or is michelson_or(unit, "z", y_or, "other")

If you don't want to have an annotation, you need to provide an empty string.

info

Alternatively, if annotations are not important you can also use plain tuples for pair's instead. Plain tuples don't have any annotations.

To use variables of type michelson_or you have to use M_left and M_right. M_left picks the left or case while M_right picks the right or case. For michelson_pair you need to use tuples.

const z: z_or = (M_left (unit) : z_or);
const y_1: y_or = (M_left (unit): y_or);
const y: z_or = (M_right (y_1) : z_or);
const x_pair: x_and = ("foo", (2, 3n));
const x_1: y_or = (M_right (x_pair): y_or);
const x: z_or = (M_right (y_1) : z_or);

Helper functions

Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually.

info

In both cases it will increase the size of the smart contract and the conversion will happen when running the smart contract.

Converting left combed Michelson data structures

Here's an example of a left combed Michelson data structure using pairs:

(pair %other
(pair %other
(string %s)
(int %w)
)
(nat %v)
)

Which could respond with the following record type:

type l_record is record [
s: string;
w: int;
v: nat
]

If we want to convert from the Michelson type to our record type and vice versa, we can use the following code:

type michelson is michelson_pair_left_comb(l_record)
function of_michelson (const f: michelson) : l_record is
block {
const p: l_record = Layout.convert_from_left_comb(f)
}
with p
function to_michelson (const f: l_record) : michelson is
block {
const p: michelson = Layout.convert_to_left_comb ((f: l_record))
}
with p

In the case of a left combed Michelson or data structure, that you want to translate to a variant, you can use the michelson_or_left_comb type.

For example:

type vari is
| Foo of int
| Bar of nat
| Other of bool
type r is michelson_or_left_comb(vari)

And then use these types in Layout.convert_from_left_comb or Layout.convert_to_left_comb, similar to the pairs example above, like this:

function of_michelson_or (const f: r) : vari is
block {
const p: vari = Layout.convert_from_left_comb(f)
}
with p
function to_michelson_or (const f: vari) : r is
block {
const p: r = Layout.convert_to_left_comb((f: vari))
}
with p

Converting right combed Michelson data structures

In the case of right combed data structures, like:

(pair %other
(string %s)
(pair %other
(int %w)
(nat %v)
)
)

you can almost use the same code as that for the left combed data structures, but with michelson_or_right_comb, michelson_pair_right_comb, Layout.convert_from_right_comb, and Layout.convert_to_left_comb respectively.

Manual data structure conversion

If you want to get your hands dirty, it's also possible to do manual data structure conversion.

The following code can be used as inspiration:

type z_to_v is
| Z
| Y
| X
| W
| V
type w_or_v is michelson_or(unit, "w", unit, "v")
type x_or is michelson_or(unit, "x", w_or_v, "other")
type y_or is michelson_or(unit, "y", x_or, "other")
type z_or is michelson_or(unit, "z", y_or, "other")
type test is record [
z: string;
y: int;
x: string;
w: bool;
v: int;
]
function make_concrete_sum (const r: z_to_v) : z_or is block {
const z: z_or = (M_left (unit) : z_or);
const y_1: y_or = (M_left (unit): y_or);
const y: z_or = (M_right (y_1) : z_or);
const x_2: x_or = (M_left (unit): x_or);
const x_1: y_or = (M_right (x_2): y_or);
const x: z_or = (M_right (x_1) : z_or);
const w_3: w_or_v = (M_left (unit): w_or_v);
const w_2: x_or = (M_right (w_3): x_or);
const w_1: y_or = (M_right (w_2): y_or);
const w: z_or = (M_right (w_1) : z_or);
const v_3: w_or_v = (M_right (unit): w_or_v);
const v_2: x_or = (M_right (v_3): x_or);
const v_1: y_or = (M_right (v_2): y_or);
const v: z_or = (M_right (v_1) : z_or);
}
with (case r of
| Z -> z
| Y -> y
| X -> x
| W -> w
| V -> v
end)
function make_concrete_record (const r: test) : (string * int * string * bool * int) is
(r.z, r.y, r.x, r.w, r.v)
function make_abstract_sum (const z_or: z_or) : z_to_v is
(case z_or of
| M_left (n) -> Z
| M_right (y_or) ->
(case y_or of
| M_left (n) -> Y
| M_right (x_or) ->
(case x_or of
| M_left (n) -> X
| M_right (w_or) ->
(case (w_or) of
| M_left (n) -> W
| M_right (n) -> V
end)
end)
end)
end)
function make_abstract_record (const z: string; const y: int; const x: string; const w: bool; const v: int) : test is
record [ z = z; y = y; x = x; w = w; v = v ]

Amendment

With the upcoming 007 amendment to Tezos this will change though, and also pair's can be ordered differently.