Add documentation to the contract¶
In this chapter, we will show how to add documentation to our smart contract directly in the code.
We will use a contract that resembles the one in section Functions and Procedures.
You can load the file below into the REPL to follow along with the tutorial.
Contract Name and Description¶
First let's get started with something simple, we can specify the contract name and description by
using docGroup
and description
like below:
myContract :: IndigoContract Parameter Storage
myContract param = defContract $ docGroup "My Contract" do
contractGeneralDefault
description
"This documentation describes an example on how to functions and \
\procedures of Indigo."
You can generate the documentation of this contract by using:
saveDocumentation @Parameter @Storage myContract "my-documentation.md"
Note
You can also see the documentation immediately in the REPL using:
printDocumentation @Parameter @Storage myContract
If there is no error, you will be able to see a file named my-documentation.md
.
We can see that, at the top of the documentation, there are the name and description of our contract. There is also Table of Contents which is generated automatically for us.
# My Contract
This documentation describes an example on how to functions and procedures of Indigo.
<a name="section-Table-of-contents"></a>
## Table of contents
Beside docGroup
and description
, there are also other doc items that we could use:
anchor <link>
: Create custom anchor in the generated documentation.example <value>
: Use inside an entrypoint to specify an example value for that entrypoint.
There are also many other things that are generated automatically for us, including:
Entrypoints
and Definitions
.
Entrypoints' documentation¶
We can use description
and example
as described above to add some information about
our entrypoints in the documentation.
checkZero
:: forall tp.
( tp :~> Integer
, HasStorage Storage
)
=> IndigoEntrypoint tp
checkZero val = do
description "Increment storage by 1 if the input is zero, otherwise fail."
example (0 int)
result <- new$ (val == 0 int)
if result then
incrementStorage
else
failCustom_ @() #isNotZeroError
checkHasDigitOne
:: forall tp.
( tp :~> Natural
, HasStorage Storage
)
=> IndigoEntrypoint tp
checkHasDigitOne val = do
description "Increment storage by 1 if the input has one digit."
example (1 int)
currentVal <- new$ val
setStorageField @Storage #sLastInput currentVal
base <- new$ 10 nat
checkRes <- new$ False
while (val > base && not checkRes) do
currentVal =: currentVal / base
remainder <- new$ val % base
checkRes =: remainder == 1 nat
Now we should have description that looks like this:
* ***lastInput*** :[`Natural`](#types-Natural)
* ***total*** :[`Natural`](#types-Natural)
1. Call contract's `isZero` entrypoint passing the constructed argument.
</details>
<p>
Note
There are a lot of other useful things that are generated as well, such as:
- How to call the entrypoint: Instruction on how to call the entrypoint
- Type: Haskell and Michelson Types.
- Example: Example value for Michelson
- We can modify this value with example
as described above
Custom error messages¶
When handling an entrypoint's input, there are times when the user gives an unexpected value. In this case, we would like to fail with a nice error message.
This is where custom error messages come into play.
We can define custom error messages like below:
[errorDocArg| "isNotZeroError" exception "Fail when the input to `IsZero` entrypoint is not zero." ()|]
As you can see, there are 3 parts to defining a custom error message:
"isNotZeroError"
is the name of the error.exception
is the type of the error. There are 4 types of error you can specify:exception
: Normal expected error. Examples: "insufficient balance", "wallet does not exist".bad-argument
: Invalid argument passed to entrypoint. Examples: your entrypoint accepts an enum represented asnat
, and unknown value is provided.contract-internal
: Unexpected error. Most likely it means that there is a bug in the contract or the contract has been deployed incorrectly.unknown
: When the error type is beside the other 3.
"Fail when the input to `IsZero` entrypoint is not zero."
is the description of the error.
This is how you would use your custom error message.
failCustom_ @() #isNotZeroError
The error message entry will be generated like below:
<a name="errors-InternalError"></a>
---
### `InternalError`
Generate documentations for storage¶
Let's say that we have a storage type like this:
data Storage = Storage
{ sLastInput :: Natural
, sTotal :: Natural
}
deriving stock (Generic, Show)
deriving anyclass (IsoValue, HasAnnotation)
We may want to document this type in our documentation. Here's how we can do it:
[typeDoc| Storage "Contract storage description."|]
We also need to add docStorage
in the contract code as well:
myContract :: IndigoContract Parameter Storage
myContract param = defContract $ docGroup "My Contract" do
contractGeneralDefault
description
"This documentation describes an example on how to functions and \
\procedures of Indigo."
doc $ dStorage @Storage
entryCaseSimple param
( #cIsZero #= checkZero
, #cHasDigitOne #= checkHasDigitOne
This will generate the doc entry like below:
Reverse conversion from Michelson value to the Haskell value can be done by serializing Michelson value using `tezos-client hash data` command, resulting `Raw packed data` should be decoded from the hexadecimal representation using `decodeHex` and deserialized to the Haskell value via `lUnpackValue` function from [`Lorentz.Pack`](https://gitlab.com/morley-framework/morley/-/blob/2441e26bebd22ac4b30948e8facbb698d3b25c6d/code/lorentz/src/Lorentz/Pack.hs).
* Construct values for this contract directly on Michelson level using types provided in the documentation.
<a name="section-Storage"></a>
## Storage
Your final code should look like this:
module Indigo.Tutorial.ContractDocumentation.FunctionsWithDocs
( myContract
) where
import Indigo
data Parameter
= IsZero Integer
| HasDigitOne Natural
deriving stock (Generic, Show)
deriving anyclass (IsoValue)
[entrypointDoc| Parameter plain |]
data Storage = Storage
{ sLastInput :: Natural
, sTotal :: Natural
}
deriving stock (Generic, Show)
deriving anyclass (IsoValue, HasAnnotation)
[typeDoc| Storage "Contract storage description."|]
myContract :: IndigoContract Parameter Storage
myContract param = defContract $ docGroup "My Contract" do
contractGeneralDefault
description
"This documentation describes an example on how to functions and \
\procedures of Indigo."
doc $ dStorage @Storage
entryCaseSimple param
( #cIsZero #= checkZero
, #cHasDigitOne #= checkHasDigitOne
)
checkZero
:: forall tp.
( tp :~> Integer
, HasStorage Storage
)
=> IndigoEntrypoint tp
checkZero val = do
description "Increment storage by 1 if the input is zero, otherwise fail."
example (0 int)
result <- new$ (val == 0 int)
if result then
incrementStorage
else
failCustom_ @() #isNotZeroError
checkHasDigitOne
:: forall tp.
( tp :~> Natural
, HasStorage Storage
)
=> IndigoEntrypoint tp
checkHasDigitOne val = do
description "Increment storage by 1 if the input has one digit."
example (1 int)
currentVal <- new$ val
setStorageField @Storage #sLastInput currentVal
base <- new$ 10 nat
checkRes <- new$ False
while (val > base && not checkRes) do
currentVal =: currentVal / base
remainder <- new$ val % base
checkRes =: remainder == 1 nat
updateStorage checkRes
updateStorage :: HasStorage Storage => Var Bool -> IndigoProcedure
updateStorage result = defFunction do
if result then
setStorageField @Storage #sTotal $ 0 nat
else
incrementStorage
incrementStorage :: HasStorage Storage => IndigoProcedure
incrementStorage = defFunction do
currentTotal <- getStorageField @Storage #sTotal
setStorageField @Storage #sTotal (currentTotal + (1 nat))
-- | Custom error which can be used via: @failCustom_ #myError@
[errorDocArg| "isNotZeroError" exception "Fail when the input to `IsZero` entrypoint is not zero." ()|]
We can now proceed to the final chapter that explains how to setup a proper
Indigo project, which will improve your development with indigo
, and also give
an overview on the usage of the Indigo CLI:
Setting up a project.