Build your first decentralized application (aepp) on Aeternity blockchain — Sophia ML smart contract — Address Book

Milen Radkov
hack
Published in
8 min readJan 10, 2019
Sophia aeternity

Æ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
Initialization of aeternity aepp with forgae
Initialization of aeternity aepp with forgae

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.

forgae init project directory
forgae init project directory

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

forgae node initialized successfully
forgae node initialized successfully
forgae node gives us preset test accounts
forgae node gives us preset test accounts

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 contractsfolder 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 initfunction.

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. The init function is pure and returns the initial state as its
    return value. At contract creation time, the init 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 variable state.
  • 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
Compiling sophia smart contract
Compiling sophia smart contract

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

aecli crypto decode aeternity address
aecli crypto decode aeternity address

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
forgae test output - addressbook aepp
forgae test output — addressbook aepp

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.

--

--