Build your first decentralized application (aepp) on Aeternity blockchain — Sophia ML smart contract — Address Book
Æternity blockchain is live after long period of waiting, the community is big and the core developers are top skilled. The project offers smart contracts development platform for decentralized applications dapps or how they call them — æpps.
In this tutorial we are going to create a simple smart contract in Sophia programming language — address book aepp. We will create, compile, test and deploy the smart contract.
Prerequisites
- Docker
- ForgAE
- Aecli * (optional if we want to do something with the sdk like encode/decode address by publicKey or signTx)
Docker we need for running local aeternity node for compiling, testing and development environment. Assuming you already have docker installed on your machine we move on, if not — please follow the guide in their official website and then continue with the steps below.
ForgAE is an æternity development framework which helps with setting up a project. The framework makes the development of smart contracts in the aeternity network pretty easily. It provides commands for the compilation of smart contracts, running a local Epoch and unit testing the contracts.
We install it globally via:
npm install -g forgae
After installing Forgae we can verify everything went well with forgae --help
which will also get us familiarize with the commands and options it has.
Usage: forgae [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init [options] Initialize ForgAE
compile [options] Compile contracts
test [options] Running the tests
node [options] Running a local node. Without any argument node will be runned with --start argument
deploy [options] Run deploy script
We are almost done with the setup. The last tool we need is aecli
which is æternity's JavaScript SDK command-line interface. It is still not available as a npm package, so for us to use it we should clone the repository, install from source and then link it to our local npm manually.
git clone https://github.com/aeternity/aepp-cli-js.git
We then need to enter into the cloned repository with:
cd aepp-cli-js
Run npm link
in order to link the AECLI to aecli/bin/aecli.js
(If you have any folder permission issues, try running with sudo sudo npm link
)
npm link
command will create a symlink in the global node_modules folder and expose the CLI binary to be used through the terminal.npm link
and lastly we should runnpm install
so npm update its packages and symlinks.
npm install
Now if everything went well our environment is set and ready to start developing sophia smart contracts on æternity.
Project init
As always, first we need to create a directory where our project files will be placed:
mkdir addressbook-aepp
cd addressbook-aepp
Then we initialize our project with forgae init
which will create for us all the necessary files and directories.
forgae init
If we look at our project directory we will see that we now have contracts, deployment, test directories where our focus will mainly be, as well as other config files like package.json and docker-compose.yml.
Now we need to start docker and then start our local node with:
forgae node
The node command help developers run their local network on docker. The local network contains 3 nodes. To spawn a fully functional network takes couple of minutes. At the end of this command you will be presented with accounts that you can use in your unit tests.
documentation
To stop the local node, simply run
forgae node --stop
Write Sophia smart contracts
When we initialized our project with forgae init
it automatically created for us an ExampleContract.aes
file, which name speaks for what it is. Its content looks like this:
contract ExampleContract =
type state = ()
function main(x : int) = x
This example contract has one function which accepts a single argument of type int
and returns the value of the passed argument x
.
If this is your first time looking at Sophia smart contract, you probably notice that it is quite different from Solidity for example, and looks much similar to Python.
Aeternity’s Sophia is a ML-family language. It is strongly typed and has restricted mutable state. Sophia is developed to be used for creating smart contracts on Aeternity Blockchain, so some of the conventional languages’ features are missing, but other blockchain specific primitives, types and constructions are added and supported.
Okay, we move on to the fun part. We now hit the delete button and get rid of the ExampleContract.aes
.
We create our AddressBook.aes
sophia smart contract in the contracts
folder and open it with our favorite editor (in my case VIM).
We define the name of our smart contract, following the example above:
contract AddressBook =
Note: Indentation is important in Sophia language, so keep that in mind.
Sophia uses Python-style layout rules to group declarations and statements. A
layout block with more than one element must start on a separate line and be
indented more than the currently enclosing layout block. Blocks with a single
element can be written on the same line as the previous token.
Each element of the block must share the same indentation and no part of an element may be indented less than the indentation of the block.
Okay, the first thing we do after that is defining our person and state records and create the init
function.
contract AddressBook =
record person = {
first_name : string,
last_name : string,
age : int
}
record state = {
people : map(address, person)
}
public stateful function init() = { people = {} }
Let us focus on some info from the sophia documentation so we know what we’ve done so far:
Sophia does not have arbitrary mutable state, but only a limited form of
state associated with each contract instance.
- Each contract defines a type
state
encapsulating its mutable state. - The initial state of a contract is computed by the contract’s
init
function. Theinit
function is pure and returns the initial state as its
return value. At contract creation time, theinit
function is executed and
its result is stored as the contract state. - The value of the state is accessible from inside the contract
through an implicitly bound variablestate
. - State updates are performed by calling a function
put : state => ()
. - Aside from the
put
function (and similar functions for transactions
and events), the language is purely functional. - Functions modifying the state need to be annotated with the
stateful
keyword.
A contract may define a type state encapsulating its local state. The state must be initialised when instantiating a contract. This is done by the init function which can take arbitrary arguments and is called on contract instance creation.
sophia documentatio
We now have to create setter function for saving person’s details, and getter functions for getting them.
public stateful function addPerson(address: address, first_name: string, last_name: string, age: int) : bool =
put(state{
people[address] = {first_name = first_name, last_name = last_name, age = age}
})
true
For our getter functions we will need a helper function which will allow us to easily search in the map
we've created.
private function lookupByAddress(k : address, m, v) =
switch(Map.lookup(k, m))
None => v
Some(x) => x
and our getters:
public function getPerson(address : address) : person =
let personFound = lookupByAddress(address, state.people, {first_name = "false", last_name = "false", age = 0})
if (personFound.first_name == "false" && personFound.last_name == "false" && personFound.age == 0)
abort("No data for that person")
else
personFound
Now we are done with the smart contract. It should look like this:
AddressBook.aes — aeternity smart contract
Compile Sophia contracts
Compiling sophia smart contracts is done via:
forgae compileAnd if everything went well we should see a similar output
The
compile
command compiles Sophia contract. It's recommended to use.aes
file extension. Default directory is$projectDir/contracts
. The result of the compilation is the contract bytecode printed in the console. Additional--path
parameter is available, which can specify the path to the contract to be compiled.
Writing Unit Tests
First, we need to edit our deployment script, so it knows which contracts should be using. We only replace the ExampleContract
with our own AddressBook
in ./deployments/deploy.js
. It should now look like this:
AddressBook.aes deployment script
You probably have noticed that we pass the address
as 0xe9bbf604e611b5460a3b3999e9771b6f60417d73ce7c5519e12f7e127a1225ca
instead of aeternity's ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYcQEfikVSzgo6k2VghsWhgU
. That is because sophia is using the bytes hex representation of our base58 encoded address. We can use aecli
to decode our address and prepend 0x
afterwards :
aecli crypto decode ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYcQEfikVSzgo6k2VghsWhgU
We should receive this output:
Decoded address (hex): e9bbf604e611b5460a3b3999e9771b6f60417d73ce7c5519e12f7e127a1225ca
Writing unit test for aeternity sophia smart contract is similar to writing unit tests for Ethereum solidity smart contract.
The interesting and thing is passing arguments to functions. As you can see, we are now passing the our argsaddress
,first_name
,last_name
, and age
as tuple string.
args: `(${address}, "${first_name}", "${last_name}", ${age})`,
Note: If you are passing string, do not forget to add quotes ("
) around the string too (("Some string")
). More than one parameter can be passed separated by coma (("Some string", 123, 45, "Other string")
))deploy
Another quite handy thing is that the function call result comes with built in decode()
function, which you can use for decoding the function output by passing the type you want to decode it to e.g. primitives like int
, string
or bool
. This is quite important for our unit tests so we know how we are going to assert
the results.
const addPersonResult = await addPerson.decode('bool');
AddressBook aepp unit tests
Now if we run forgae test
we should see our unit tests output:
forgae test
Conclusion
Writing smart contracts on Sophia is very intuitive if you have previous experience writing smart contracts or experience with programming languages like Python.
We will continue this series with some more sophisticated examples which will include deployment on testnet and mainnet and more soon. If you don’t want to miss the new content — join our newsletter to receive all our new content.
Useful links
Resources
About the author: Milen Radkov (@milen_radkov) has experience building and delivering successful complex software systems and projects for big enterprises and small startups. Software developed by him and his colleagues is being used by over 1000+ retail stores today. Milen has also extensive experience in blockchain development and is a well-known figure in Bulgaria’s blockchain ecosystem.
Originally published at hack — Blockchain Development and Consulting.