From a0f8bd941e488ae51fe006ca29c7edb34176bb72 Mon Sep 17 00:00:00 2001 From: Sander Date: Tue, 19 May 2020 15:49:09 +0000 Subject: [PATCH] Interop docs --- gitlab-pages/docs/advanced/interop.md | 730 ++++++++++++++++++++++++++ gitlab-pages/website/sidebars.json | 5 +- src/test/md_file_tests.ml | 1 + 3 files changed, 734 insertions(+), 2 deletions(-) create mode 100644 gitlab-pages/docs/advanced/interop.md diff --git a/gitlab-pages/docs/advanced/interop.md b/gitlab-pages/docs/advanced/interop.md new file mode 100644 index 000000000..736efc8f8 --- /dev/null +++ b/gitlab-pages/docs/advanced/interop.md @@ -0,0 +1,730 @@ +--- +id: interop +title: Interop +--- + +import Syntax from '@theme/Syntax'; + +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: + +```michelson +(pair (int %foo) (string %bar)) +```` + +works with + +```michelson +(pair (int %foo) (string %bar)) +``` + +works with + +```michelson +(pair int string) +``` + +works not with + +```michelson +(pair (int %bar) (string %foo)) +``` + +works not with + +```michelson +(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: + + + +```pascaligo +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) +``` + + + + +```cameligo +type storage = int + +type parameter = + | Left of int + | Right of int + +let main ((p, x): (parameter * storage)): (operation list * storage) = + (([]: operation list), (match p with + | Left i -> x - i + | Right i -> x + i + )) + +``` + + + + +```reasonligo +type storage = int + +type parameter = + | Left(int) + | Right(int) + +let main = ((p, x): (parameter, storage)): (list(operation), storage) => { + ([]: list(operation), (switch(p) { + | Left(i) => x - i + | Right(i) => x + i + })) +}; + +``` + + + +This contract can be called by another contract, like this one: + + + + +```pascaligo group=get_entrypoint_opt +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 +``` + + + + + + +```cameligo group=get_entrypoint_opt +type storage = int + +type parameter = int + +type x = Left of int + +let main (p, s: parameter * storage): operation list * storage = ( + let contract: x contract = + match ((Tezos.get_entrypoint_opt "%left" ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address)): x contract option) with + | Some c -> c + | None -> (failwith "contract does not match": x contract) + in + (([ + Tezos.transaction (Left 2) 2mutez contract; + ]: operation list), s) +) +``` + + + + + +```reasonligo group=get_entrypoint_opt +type storage = int; + +type parameter = int; + +type x = Left(int); + +let main = ((p, s): (parameter, storage)): (list(operation), storage) => { + let contract: contract(x) = + switch (Tezos.get_entrypoint_opt("%left", ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address)): option(contract(x))) { + | Some c => c + | None => (failwith ("contract does not match"): contract(x)) + }; + ([ + Tezos.transaction(Left(2), 2mutez, contract) + ]: list(operation), s); +}; +``` + + + +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: + +```michelson +(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: + + + +```pascaligo +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") +``` + + + + +```cameligo +type w_and_v = (int, "w", nat, "v") michelson_pair +type x_and = (string, "x", w_and_v, "other") michelson_pair +type y_or = (unit, "y", x_and, "other") michelson_or +type z_or = (unit, "z", y_or, "other") michelson_or +``` + + + + +```reasonligo +type w_and_v = michelson_pair(int, "w", nat, "v") +type x_and = michelson_pair(string, "x", w_and_v, "other") +type y_or = michelson_or(unit, "y", x_and, "other") +type z_or = 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. + + + +```pascaligo +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); +``` + + + + +```cameligo +let z: z_or = (M_left (unit) : z_or) + +let y_1: y_or = (M_left (unit): y_or) +let y: z_or = (M_right (y_1) : z_or) + +let x_pair: x_and = ("foo", (2, 3n)) +let x_1: y_or = (M_right (x_pair): y_or) +let x: z_or = (M_right (y_1) : z_or) +``` + + + + +```reasonligo +let z: z_or = (M_left (unit) : z_or) + +let y_1: y_or = (M_left (unit): y_or) +let y: z_or = (M_right (y_1) : z_or) + +let x_pair: x_and = ("foo", (2, 3n)) +let x_1: y_or = (M_right (x_pair): y_or) +let 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: + +```michelson + (pair %other + (pair %other + (string %s) + (int %w) + ) + (nat %v) + ) +``` + +Which could respond with the following record type: + + + +```pascaligo +type l_record is record [ + s: string; + w: int; + v: nat +] +``` + + + + +```cameligo +type l_record = { + s: string; + w: int; + v: nat +} +``` + + + + +```reasonligo +type l_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: + + + +```pascaligo +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 +``` + + + + +```cameligo +type michelson = l_record michelson_pair_left_comb + +let of_michelson (f: michelson) : l_record = + let p: l_record = Layout.convert_from_left_comb f in + p + +let to_michelson (f: l_record) : michelson = + let p = Layout.convert_to_left_comb (f: l_record) in + p +``` + + + + +```reasonligo +type michelson = michelson_pair_left_comb(l_record); + +let of_michelson = (f: michelson) : l_record => { + let p: l_record = Layout.convert_from_left_comb(f); + p +}; + +let to_michelson = (f: l_record) : michelson => { + let p = Layout.convert_to_left_comb(f: l_record); + 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: + + + +```pascaligo +type vari is +| Foo of int +| Bar of nat +| Other of bool + +type r is michelson_or_left_comb(vari) +``` + + + + +```cameligo +type vari = +| Foo of int +| Bar of nat +| Other of bool + +type r = vari michelson_or_left_comb +``` + + + + +```reasonligo +type vari = +| Foo(int) +| Bar(nat) +| Other(bool) + +type r = 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 `pair`s example above, like this: + + + +```pascaligo +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 +``` + + + + +```cameligo +let of_michelson_or (f: r) : vari = + let p: vari = Layout.convert_from_left_comb f in + p + +let to_michelson_or (f: vari) : r = + let p = Layout.convert_to_left_comb (f: vari) in + p +``` + + + + +```reasonligo +let of_michelson_or = (f: r) : vari => { + let p: vari = Layout.convert_from_left_comb(f); + p +}; + +let to_michelson_or = (f: vari) : r => { + let p = Layout.convert_to_left_comb(f: vari); + p +} + +``` + + + +### Converting right combed Michelson data structures + +In the case of right combed data structures, like: + +```michelson + (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: + + + +```pascaligo group=helper_functions +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 ] + +``` + + + + + + +```cameligo group=helper_functions +type z_to_v = +| Z +| Y +| X +| W +| V + +type w_or_v = (unit, "w", unit, "v") michelson_or +type x_or = (unit, "x", w_or_v, "other") michelson_or +type y_or = (unit, "y", x_or, "other") michelson_or +type z_or = (unit, "z", y_or, "other") michelson_or + +type test = { + z: string; + y: int; + x: string; + w: bool; + v: int; +} + +let make_concrete_sum (r: z_to_v) : z_or = + match r with + | Z -> (M_left (unit) : z_or) + | Y -> (M_right (M_left (unit): y_or) : z_or ) + | X -> (M_right (M_right (M_left (unit): x_or): y_or) : z_or ) + | W -> (M_right (M_right (M_right (M_left (unit): w_or_v): x_or): y_or) : z_or ) + | V -> (M_right (M_right (M_right (M_right (unit): w_or_v): x_or): y_or) : z_or ) + +let make_concrete_record (r: test) : (string * int * string * bool * int) = + (r.z, r.y, r.x, r.w, r.v) + +let make_abstract_sum (z_or: z_or) : z_to_v = + match z_or with + | M_left n -> Z + | M_right y_or -> + (match y_or with + | M_left n -> Y + | M_right x_or -> ( + match x_or with + | M_left n -> X + | M_right w_or -> ( + match w_or with + | M_left n -> W + | M_right n -> V))) + + +let make_abstract_record (z: string) (y: int) (x: string) (w: bool) (v: int) : test = + { z = z; y = y; x = x; w = w; v = v } + +``` + + + + + +```reasonligo group=helper_functions +type z_to_v = +| Z +| Y +| X +| W +| V + +type w_or_v = michelson_or(unit, "w", unit, "v") +type x_or = michelson_or(unit, "x", w_or_v, "other") +type y_or = michelson_or(unit, "y", x_or, "other") +type z_or = michelson_or(unit, "z", y_or, "other") + +type test = { + z: string, + y: int, + x: string, + w: bool, + v: int +} + +let make_concrete_sum = (r: z_to_v) : z_or => + switch(r){ + | Z => (M_left (unit) : z_or) + | Y => (M_right (M_left (unit): y_or) : z_or ) + | X => (M_right (M_right (M_left (unit): x_or): y_or) : z_or ) + | W => (M_right (M_right (M_right (M_left (unit): w_or_v): x_or): y_or) : z_or ) + | V => (M_right (M_right (M_right (M_right (unit): w_or_v): x_or): y_or) : z_or ) + } + +let make_concrete_record = (r: test) : (string, int, string, bool, int) => + (r.z, r.y, r.x, r.w, r.v) + +let make_abstract_sum = (z_or: z_or) : z_to_v => + switch (z_or) { + | M_left n => Z + | M_right y_or => ( + switch (y_or) { + | M_left n => Y + | M_right x_or => ( + switch (x_or) { + | M_left n => X + | M_right w_or => ( + switch (w_or) { + | M_left n => W + | M_right n => V + }) + }) + }) + } + + +let make_abstract_record = (z: string, y: int, x: string, w: bool, v: int) : test => + { z : z, y, x, w, v } + +``` + + + +## Amendment +With the upcoming 007 amendment to Tezos this will change though, and also +`pair`'s can be ordered differently. \ No newline at end of file diff --git a/gitlab-pages/website/sidebars.json b/gitlab-pages/website/sidebars.json index 2d32e898b..7aef10a0e 100644 --- a/gitlab-pages/website/sidebars.json +++ b/gitlab-pages/website/sidebars.json @@ -19,8 +19,9 @@ "advanced/entrypoints-contracts", "advanced/include", "advanced/first-contract", - "advanced/michelson-and-ligo", - "advanced/inline" + "advanced/michelson-and-ligo", + "advanced/inline", + "advanced/interop" ], "Reference": [ "api/cli-commands", diff --git a/src/test/md_file_tests.ml b/src/test/md_file_tests.ml index 3ceb8c3a6..57ad2d8b2 100644 --- a/src/test/md_file_tests.ml +++ b/src/test/md_file_tests.ml @@ -123,6 +123,7 @@ let md_files = [ "/gitlab-pages/docs/advanced/entrypoints-contracts.md"; "/gitlab-pages/docs/advanced/timestamps-addresses.md"; "/gitlab-pages/docs/advanced/inline.md"; + "/gitlab-pages/docs/advanced/interop.md"; "/gitlab-pages/docs/api/cli-commands.md"; "/gitlab-pages/docs/api/cheat-sheet.md"; "/gitlab-pages/docs/reference/toplevel.md";