Skip to content

Attribute for entries

E. Rivas requested to merge er433/attrs/entry-and-views into dev

Motivation and Context

This MR is an up-to-date version of an older MR introducing an attribute for entries, and which could be useful until proper contracts arrive to LIGO.

Description

These changes do not change syntax, instead we introduce an attribute @entry to mark a function as an entry inside the module/namespace, or in the top-level.

The following examples currently work in the branch.

Top-level JsLIGO

For example, the file contract.jsligo

type storage = int;

// @entry
const increment = (action: int, store: storage) : [list <operation>, storage] => [list([]), store + action];

// @entry
const decrement = (action: int, store: storage) : [list <operation>, storage] => [list([]), store - action];

// @entry
const reset = (_u: unit, _store: storage) : [list<operation>, storage] => [list([]), 0];

can be compiled directly:

$ ligo compile contract contract.jsligo 
{ parameter (or (or (int %decrement) (int %increment)) (unit %reset)) ;
  storage int ;
  code { UNPAIR ;
         IF_LEFT { IF_LEFT { SWAP ; SUB } { ADD } } { DROP 2 ; PUSH int 0 } ;
         NIL operation ;
         PAIR } }

Namespace contract JsLIGO

$ cat src/test/contracts/interpreter_tests/test_originate_module.jsligo 
namespace C {
  type storage = int;

  // @entry
  const increment = (action: int, store: storage) : [list <operation>, storage] => [list([]), store + action];

  // @entry
  const decrement = (action: int, store: storage) : [list <operation>, storage] => [list([]), store - action];
};

const test_increment = (() => {
  let initial_storage = 42;
  let [taddr, _, _] = Test.originate_module(contract_of(C), initial_storage, 0 as tez);
  let contr = Test.to_contract(taddr);
  let _ = Test.transfer_to_contract_exn(contr, (Increment (1)), 1 as mutez);
  return assert(Test.get_storage(taddr) == initial_storage + 1);
}) ();
$ ligo run test src/test/contracts/interpreter_tests/test_originate_module.jsligo 
Everything at the top-level was executed.   
- test_increment exited with value ().

Module contract CameLIGO

$ cat src/test/contracts/interpreter_tests/test_originate_module.mligo 
let () = Test.unset_print_values ()

module Bar = struct
  module Foo = struct
    [@entry] let add n s : operation list * int = [], s + n
    [@entry] let sub n s : operation list * int = [], s - n
    [@view] let get () s : int = s
    [@view] let get_diff k s : int = s - k
  end
end

let test =
  let ta, m, _ = Test.originate_module (contract_of Bar.Foo) 0 0tez in
  let () = Test.println "Deployed the contract:" in
  let () = Test.println (Test.to_string m) in
  let () = Test.println ("With storage: " ^ Test.to_string (Test.get_storage ta)) in
  let _ = Test.transfer_to_contract_exn (Test.to_contract ta) (Add 42) 0tez in
  let () = Test.println ("Storage after call: " ^ Test.to_string (Test.get_storage ta)) in
  ()
$ ligo run test src/test/contracts/interpreter_tests/test_originate_module.mligo 
Deployed the contract:                      
{ parameter (or (int %add) (int %sub)) ;
  storage int ;
  code { UNPAIR ; IF_LEFT { ADD } { SWAP ; SUB } ; NIL operation ; PAIR } ;
  view "get" unit int { CDR } ;
  view "get_diff" int int { UNPAIR ; SWAP ; SUB } }
With storage: 0
Storage after call: 42

Note the usage of contract_of(M) as a helper for Test.originate_module.

Component

  • compiler
  • website
  • webide
  • vscode-plugin
  • debugger

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Performance improvement (non-breaking change that improves performance)
  • None (change with no changelog)

Changelog

New attribute @entry allows to define entries for a module/namespace, or from the top-level of a file.

For example, the file contract.jsligo

type storage = int;

// @entry
const increment = (action: int, store: storage) : [list <operation>, storage] => [list([]), store + action];

// @entry
const decrement = (action: int, store: storage) : [list <operation>, storage] => [list([]), store - action];

// @entry
const reset = (_u: unit, _store: storage) : [list<operation>, storage] => [list([]), 0];

can be compiled directly:

$ ligo compile contract contract.jsligo 
{ parameter (or (or (int %decrement) (int %increment)) (unit %reset)) ;
  storage int ;
  code { UNPAIR ;
         IF_LEFT { IF_LEFT { SWAP ; SUB } { ADD } } { DROP 2 ; PUSH int 0 } ;
         NIL operation ;
         PAIR } }

Moreover, when using the testing framework, a contract_of keyword can be used to originate a module as a contract with the help of the new function Test.originate_module:

$ cat src/test/contracts/interpreter_tests/test_originate_module.jsligo 
namespace C {
  type storage = int;

  // @entry
  const increment = (action: int, store: storage) : [list <operation>, storage] => [list([]), store + action];

  // @entry
  const decrement = (action: int, store: storage) : [list <operation>, storage] => [list([]), store - action];
};

const test_increment = (() => {
  let initial_storage = 42;
  let [taddr, _, _] = Test.originate_module(contract_of(C), initial_storage, 0 as tez);
  let contr = Test.to_contract(taddr);
  let _ = Test.transfer_to_contract_exn(contr, (Increment (1)), 1 as mutez);
  return assert(Test.get_storage(taddr) == initial_storage + 1);
}) ();

$ ligo run test src/test/contracts/interpreter_tests/test_originate_module.jsligo 
Everything at the top-level was executed.   
- test_increment exited with value ().

Breaking changes

Notice that this feature is breaking as contract_of becomes a keyword, rending invalid contracts that use this name as a variable. In this case, the fix is simple, and just amounts to change the name of the variable, or use @contract_of:

$ cat contract.jsligo 
// @entry
const increment = (action: int, store: int) : [list <operation>, int] => [list([]), store + action];

const contract_of = 42;
$ ligo compile contract contract.jsligo 
File "contract.jsligo", line 4, characters 6-17:
  3 | 
  4 | const contract_of = 42;
Ill-formed value declaration.
At this point, a pattern is expected, e.g. a variable.

After modifying contract.jsligo with @contract_of:

$ cat contract.jsligo 
// @entry
const increment = (action: int, store: int) : [list <operation>, int] => [list([]), store + action];

const @contract_of = 42;
$ ligo compile contract contract.jsligo 
{ parameter int ;        
  storage int ;
  code { UNPAIR ; ADD ; NIL operation ; PAIR } }

This MR also modifies the parameters for the sub-command evaluate-call (-e is not used anymore to signal the function to compile).

Checklist:

  • Changes follow the existing coding style (use dune @fmt to check).
  • Tests for the changes have been added (for bug fixes / feature).
  • Documentation has been updated.
  • Changelog description has been added (if appropriate).
  • Start titles under ## Changelog section with #### (if appropriate).
  • There is no image or uploaded file in changelog
  • Examples in changed behaviour have been added to the changelog (for breaking change / feature).
Edited by E. Rivas

Merge request reports