dinkar ganti
7 Dec 2022
•
3 min read
Tezos's core Smart Contract language is Michelson, but that is a lower-level language that is not amenable to larger-scale contract development. Ligo is a friendly smart contract for Tezos.
Here I am going to present a small contract that computes holidays. The code looks very close to OCaml, therefore should make sense to most OCaml developers.
(* For example, if we would like to setup a groundwater monitoring event after on the
first Monday of every quarter, we would use this utility. *)
(*
The contract code borrows directly from the lisp and the c++ code from the references mentioned below.
;; The following Lisp code is from ``Calendrical
;; Calculations'' by Nachum Dershowitz and Edward
;; M. Reingold, Software---Practice & Experience, vol. 20,
;; no. 9 (September, 1990), pp. 899--928 and from
;; ``Calendrical Calculations, II: Three Historical
;; Calendars'' by Edward M. Reingold, Nachum Dershowitz,
;; and Stewart M. Clamen, Software---Practice & Experience,
;; vol. 23, no. 4 (April, 1993), pp. 383--404.
;; This code is in the public domain, but any use of it
;; should publicly acknowledge its source.
*)
type month = int
type day = int
type year = int
type dayOfWeek =
| Sunday
| Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| Saturday
type date = {
month : month;
day : day;
year : year;
}
type storage = date
let initStorage = {month = 1; day = 1; year = 1}
let xDayOnOrBefore (d : day) (x : day) : day = d - ((d - x) mod 7)
The above code is pretty straightforward, however the focus of this article is to talk about the need for tail-recursive functions because Ligo does not support generally recursive functions.
(*
if ((((year % 4) == 0) && ((year % 100) != 0))
|| ((year % 400) == 0))
*)
let lastDayOfGregorianMonth (m : month) (y : year) : day =
if m = 0 then 0
else if m = 2 then
if (((int(y mod 4)) = 0 && (int ((y mod 100)) <> 0))
|| ((int (y mod 400)) = 0)) then
29
else
28
else if m = 4 then 30
else if m = 6 then 30
else if m = 9 then 30
else if m = 11 then 30
else 31
let rec daysInPriorMonthsAcc (m : month) (y : year) (accum : day) : day =
if m <= 0 then
accum
else
daysInPriorMonthsAcc (m - 1) y (accum + lastDayOfGregorianMonth m y)
let daysInPriorMonths (m : month) (y : year) (d : day) : day = daysInPriorMonthsAcc m y d
let cast (d : date) : int =
(daysInPriorMonths (d.month - 1) d.year d.day)
+ 365 * (d.year - 1)
+ ((d.year - 1) / 4)
- ((d.year - 1) / 100)
+ ((d.year - 1) / 400)
let rec getApproximateYear (y : year) (absoluteDate : int) : year =
let tmpDate : date = {
month = 1;
day = 1;
year = y + 1;
} in
if (absoluteDate > (cast tmpDate)) then
getApproximateYear (y + 1) absoluteDate
else
y
There is some more similar code, but the general idea is to convert all recursive functions to tail-recursive functions. Some more functions are presented here
let rec getApproximateMonth (y : year) (m : month) (absoluteDate : int) : month =
let tmpDate : date = {month = m; day = lastDayOfGregorianMonth m y; year = y} in
if (absoluteDate > cast(tmpDate)) then
if (m < 12) then
getApproximateMonth y (m + 1) absoluteDate
else
(failwith ((absoluteDate, cast(tmpDate)) : int * int))
else
m
let createGregorianDate (absoluteDate : int) : date =
let year = getApproximateYear (absoluteDate / 366) absoluteDate in
let month = getApproximateMonth year 1 absoluteDate in
let day = absoluteDate - (cast {month = month; day = 1; year = year}) + 1
in
{month = month; day = day; year = year}
let nthxday (n : day) (x : day) (m : month) (y : year) (d : day) : date =
if (n > 0) then
let
tmp =
if d = 0 then
cast {month = m; day = 1; year = y}
else
cast {month = m; day = d; year = y}
in
createGregorianDate (7 * (n - 1) + (xDayOnOrBefore (6 + tmp) x))
else
let lday = lastDayOfGregorianMonth m y in
let tmp =
if d = 0 then
cast {month = m; day = lday; year = y}
else
cast {month = m; day = d; year = y}
in
createGregorianDate (7 * (n + 1) + (xDayOnOrBefore tmp x))
Now for the samples that drive these contracts
type return = operation list * storage
let laborDay(y, _ : year * storage) : return =
let s = (nthxday 1 1 9 y 0)
in
([], s)
let main = laborDay
let memorialDay (y, _ : year * storage) : return = ([], nthxday (-1) 1 5 y 0)
let daylightSavingsStart (y, _ : year * storage) : return = ([], nthxday 2 0 3 y 0)
Some tests
let testApproximateYear (d, _ : int * storage) : return =
let
y = getApproximateYear (d / 366) d
in
let
s = {month = 1; day = 1; year = y}
in
([], s)
let testLastDayOfGregorianMonth (_, _ : year * storage) : (operation list * storage) =
let
d = lastDayOfGregorianMonth 2 2022
in let
s = {month = 2; day = d; year = 2022}
in
([], s)
let testCreateGregorianDate (d, _ : int * storage) : (operation list * storage) =
let
d2 = createGregorianDate d
in
([], d2)
Ligo is a great smart contract language for Tezos.
dinkar ganti
A demo app using libreoffice scripting framework: https://www.youtube.com/watch?v=INT6duJM7X8
See other articles by dinkar
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!