lorentz: EDSL for the Michelson Language

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Lorentz is a powerful meta-programming tool which allows one to write Michelson contracts directly in Haskell. It has the same instructions as Michelson, but operates on Haskell values and allows one to use Haskell features.

[Skip to Readme]


Versions 0.1.0, 0.2.0, 0.3.0, 0.4.0, 0.5.0, 0.6.0, 0.6.1, 0.6.2, 0.7.0, 0.7.1, 0.8.0, 0.9.0, 0.9.1, 0.10.0, 0.11.0, 0.11.0, 0.12.0, 0.12.1, 0.13.0, 0.13.1, 0.13.2, 0.13.3, 0.13.4, 0.14.0, 0.14.1, 0.15.0
Change log CHANGES.md
Dependencies aeson-pretty, base-noprelude (>=4.7 && <5), bimap, bytestring, constraints (>=0.11), containers, data-default, first-class-families (>=, fmt, interpolate, lens, morley, morley-prelude (>=0.3.0), mtl, named, optparse-applicative, singletons, template-haskell, text, text-manipulate, unordered-containers, vinyl, with-utf8 [details]
License MIT
Copyright 2019-2021 Tocqueville Group
Author camlCase, Serokell, Tocqueville Group
Maintainer Serokell <hi@serokell.io>
Category Language
Home page https://gitlab.com/morley-framework/morley
Bug tracker https://gitlab.com/morley-framework/morley/-/issues
Source repo head: git clone git@gitlab.com:morley-framework/morley.git
Uploaded by pasqu4le at 2021-03-31T19:20:15Z



Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Readme for lorentz-0.11.0

[back to package description]

Morley Lorentz EDSL


Table of contents


Lorentz is a powerful meta-programming tool which allows one to write Michelson contracts directly in Haskell.

Haskell's type checker and automatic type inference facilitate contracts implementation and reduce boilerplate related to types. Adoption of Algebraic Data Types makes work with complex objects safe and convenient. Later Lorentz contract can be dumped as a plain textual Michelson contract using functions from Michelson.Printer.

As an addition, you can optimize the transpiled Michelson contract before printing using functions from Michelson.Optimizer. E.g. this optimizer will replace IF {} {} with DROP. For more possible optimizations please refer to the module mentioned earlier.

You can find Lorentz instructions in Lorentz modules.

Examples of using Lorentz eDSL reside in the morley-ledgers or tezos-btc repos. For more information, refer to that README's in the corresponding repositories.

Also, to get more information about Lorentz you can read our blogpost.

Writing contracts in Lorentz

Basically, Lorentz function is just the haskell function that transforms one stack to another, this can be presented as the following operator:

(inp :: [Type]) :-> (out :: [Type])

In order to list types on the stack, we will use : operator. For example, Natural : Integer : Bool : '[].

Such design provides a nice code decomposition ability.

Contract code in Lorentz is Lorentz function with specific type:

type ContractOut storage = '[([Operation], storage)]
type ContractCode parameter storage = '[(parameter, storage)] :-> ContractOut storage

Lorentz reimplements all existing instructions and macros from Michelson.

Apart from reimplementing existing Michelson functionality, Lorentz provides bunch of additional features:

data TrafficLight
  = Red
  | Yellow
  | Green
  deriving stock Generic
  deriving anyclass IsoValue

showTrafficLight :: TrafficLight : s :-> MText : s
showTrafficLight = caseT
  ( #cRed /-> push [mt|Red|]
  , #cYellow /-> push [mt|Yellow|]
  , #cGreen /-> push [mt|Green|]
sumIsGt10 :: Integer : Integer : s :-> Bool : s
sumIsGt10 = do
  dip $ push @Integer 10

foo :: Integer : s :-> Integer : s
foo = do
  if Holds
  then do push @Integer 1
  else do push @Integer -1
data UserInfo = UserInfo
  { firstName :: MText
  , secondName :: MText
  , userId :: Natural
  } deriving stock Generic
    deriving anyclass IsoValue

getFullName :: UserInfo : s :-> MText : s
getFullName = do
  getField #firstName
  dip $ toField #secondName

Sample smart contract written in Lorentz

Example contract with autodoc and custom data-type tree:

data MeasurementMethod
  = ParrotStep
  | MonkeyStep
  | ElephantStep

$(customGeneric "MeasurementMethod" rightBalanced)

deriving anyclass instance IsoValue MeasurementMethod
deriving anyclass instance HasAnnotation MeasurementMethod

instance TypeHasDoc MeasurementMethod where
  typeDocName _ = "MeasurementMethod"
  typeDocMdDescription =
    "This type defines the way of measuring boa length. \
    \Single boa constrictor corresponds to 38 parrot steps, 31 monkey step \
    \and 9 elephant steps."

data Parameter
  = MeasureBoaConstrictor ("method" :! MeasurementMethod)
  | Zero ()
  deriving stock Generic
  deriving anyclass IsoValue

instance ParameterHasEntrypoints Parameter where
  type ParameterEntrypointsDerivation Parameter = EpdPlain

type Storage = Natural

measureBoaConstrictor :: ContractCode Parameter Storage
measureBoaConstrictor = docGroup (DName "Boa constrictor measurement") $ do
  doc $ DDescription "This contract measures boa constrictor."
  dip drop
  entryCase @Parameter (Proxy @PlainEntrypointsKind)
    ( #cMeasureBoaConstrictor /-> do
        doc $ DDescription "Measure the boa constrictor with given method."
        fromNamed #method
        caseT @MeasurementMethod
          ( #cParrotStep /-> push @Natural 38
          , #cMonkeyStep /-> push @Natural 31
          , #cElephantStep /-> push @Natural 9
    , #cZero /-> do
        doc $ DDescription "Put zero to the storage."
        drop; push @Natural 0
  nil; pair

Generated documentation

Generated documentation for this contract can be found here.

Transpiled Michelson contract

parameter (or (or :method %measureBoaConstrictor unit
                                                 (or unit
              (unit %zero));
storage nat;
code { CAST (pair (or (or unit (or unit unit)) unit) nat);
       DIP { CDR };
       DIP { DROP };
       IF_LEFT { IF_LEFT { DROP;
                           PUSH nat 38 }
                         { IF_LEFT { DROP;
                                     PUSH nat 31 }
                                   { DROP;
                                     PUSH nat 9 } } }
               { DROP;
                 PUSH nat 0 };
       NIL operation;
       PAIR };