# Morley Lorentz EDSL [![Hackage](https://img.shields.io/hackage/v/lorentz.svg)](https://hackage.haskell.org/package/lorentz) ## Table of contents * [Overview](https://gitlab.com/morley-framework/morley/-/tree/master/code/lorentz#overview) * [Writing contracts in Lorentz](https://gitlab.com/morley-framework/morley/-/tree/master/code/lorentz#writing-contracts-in-lorentz) * [Lorentz contract example](https://gitlab.com/morley-framework/morley/-/tree/master/code/lorentz#lorentz-example) * [FAQ](https://gitlab.com/morley-framework/morley/-/tree/master/code/lorentz#faq) ## Overview 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 [`Morley.Michelson.Printer`](http://hackage.haskell.org/package/morley/docs/Morley-Michelson-Printer.html). As an addition, you can optimize the transpiled Michelson contract before printing using functions from [`Morley.Michelson.Optimizer`](http://hackage.haskell.org/package/morley/docs/Morley-Michelson-Optimizer.html). 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`](http://hackage.haskell.org/package/lorentz/docs/Lorentz.html) modules. Examples of using Lorentz eDSL reside in the [`morley-ledgers`](https://gitlab.com/morley-framework/morley-ledgers) or [`tezos-btc`](https://github.com/tz-wrapped/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](https://serokell.io/blog/lorentz-implementing-smart-contract-edsl-in-haskell). ## 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: ```haskell (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: ```haskell type ContractOut storage = '[([Operation], storage)] type ContractCode parameter storage = '[(parameter, storage)] :-> ContractOut storage ``` Lorentz reimplements all existing [instructions](http://hackage.haskell.org/package/lorentz/docs/Lorentz-Instr.html) and [macros](http://hackage.haskell.org/package/lorentz/docs/Lorentz-Macro.html) from Michelson. Apart from reimplementing existing Michelson functionality, Lorentz provides bunch of additional features: * Pattern-matching on ADTs. ```haskell 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|] ) ``` * `RebindableSyntax` for Lorentz instructions, including `do` notation and `if` operator ```haskell sumIsGt10 :: Integer : Integer : s :-> Bool : s sumIsGt10 = do add dip $ push @Integer 10 gt foo :: Integer : s :-> Integer : s foo = do dup sumIsGt10 if Holds then do push @Integer 1 else do push @Integer -1 ``` * Records with getters. ```haskell 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 concat ``` * Automatic contracts documentation generation. Lorentz provides primitives for embedding documentation in the contract code and functions to produce documentation in Markdown, they can be found in [`Lorentz.Doc`](http://hackage.haskell.org/package/lorentz/docs/Lorentz-Doc.html) and [`Morley.Michelson.Doc`](http://hackage.haskell.org/package/morley/docs/Morley-Michelson-Doc.html) modules. Documentation examples can be found [here](https://gitlab.com/morley-framework/morley/-/tree/autodoc/master/autodoc). ## Sample smart contract written in Lorentz Example contract with autodoc and custom data-type tree: ```haskell 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." unpair 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](https://gitlab.com/morley-framework/morley/-/blob/master/code/lorentz/doc/sampleAutodoc.md). ### Transpiled Michelson contract ``` parameter (or (or :method %measureBoaConstrictor unit (or unit unit)) (unit %zero)); storage nat; code { CAST (pair (or (or unit (or unit unit)) unit) nat); DUP; CAR; 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 }; ``` ## FAQ * Q: I added a new parameter case to contract and GHC went mad. A: Ensure that your number of entry points does not exceed the limit set in [`Morley.Util.TypeTuple.Instances`](http://hackage.haskell.org/package/morley/docs/Morley-Util-TypeTuple-Instances.html). * Q: I added one more datatype that is used in the contract and GHC reports with errors related to `Rep` type family. A: Make sure your datatype derives `Generic` instance and all primitive types used in it have `IsPrimitiveValue` set to `True`.