Part 2 - Aragon app primer

If you haven’t seen “Part 1 - Introduction” of the tutorial, go check that out first, so that you understand what we’re building here.

In this article, we will bootstrap an Aragon app. We will create it from scratch using create-aragon-app, run its frontend inside the Aragon client, write unit tests for its contracts, and start modifying it towards our end goal of building a complete HCVoting Aragon app.

Pre-requisites and set up

As mentioned in part 1, I’m assuming that you know your way around in NodeJS, and are familiar with smart contract development in Solidity. If you are new to all this, consider reading Facu Spagnuolo’s Gentle Introduction to Ethereum Programming first.

I’m using node v11.14.0, but you should be ok with other versions. I’m also using NPM v6.10.1, and all other dependencies are local to the project that we will build, so you shouldn’t have to worry about installing any other global dependencies in your system.

Source code

Each part of the tutorial will be completely standalone, having an associated start and end branch. You should check out the start branch as soon as you begin a part to make sure that you are 100% in sync with the instructions that you are about to follow. If you run into trouble, you can always compare your work with the end branch of the section. Each part starts off from the end branch of the previous part.

This part’s branches:

Checking out the repo

Alright, let’s get started! First fork the repo, then check out your fork:

$ git clone [email protected]:<your github user>/hc.git

Switch to the starting branch for this part:

$ git checkout branch tutorial/part2/app-primer-start

Now, let’s see what you just checked out:

$ ls -la

Yup, that’s right, there’s nothing here! There will be no magic in this tutorial—you will be building the whole thing yourself—so, naturally, we start from an empty folder.

Before we start putting stuff in there, I recommend that you create your personal branch for this part (and that you do this each time you start a new part), so that you can always compare it with the start and end branches of the part:

$ git checkout -b tutorial/part2/app-primer-<your name>

That’s it! We’re ready to get started with some actual Aragon hackering here.

Curious side note: Did you know that when hacking started in the 1950’s in MIT, it was first called “hackering”? More info here.

Bootstrapping: an intro to create-aragon-app, aragonCLI, and the Aragon client

The first thing you do when creating any Aragon app, is use the create-aragon-app tool. It’s an NPM package located within the aragonCLI repo.

In your local branch, run:

$ npx create-aragon-app hc

This command takes a while to run, because it’s doing some serious bootstrapping behind the scenes. It creates a folder with the same name as your project, in this case “hc”, with a bunch of files in it, which we will look at next. But before doing that, let’s move all the contents of “hc” up one level, because we already had a project folder called “hc” and we don’t want our files to live in “hc/hc”.

$ mv hc/{.,}* .
$ rmdir hc

If you look at the content now, you will see the following files:

app/
contracts/
migrations/
node_modules/
test/
arapp.json
manifest.json
package-lock.json
package.json
README.md
truffle.js

Take your time to look around.

The create-aragon-app command basically spawned a Truffle 4.1.14 project in the root directory of the project, plus a React sub-project for the frontend in the “app” directory. The Truffle project provides the “backend” of the app, via the CounterApp.sol contract. We will be using this placeholder app, and replacing it with our own HCVoting app, both for the backend and the frontend.

The placeholder Counter app contract that comes with the boilerplate code is as simple as an app gets in the Aragon universe. It defines a value that can be incremented or decremented by two functions, which are guarded by two roles. We will see this app running in the Aragon client shortly. Then, we will continue to analyze how it works by looking at the code’s test suite.

If you look at the create-aragon-app documentation, you will see that you should now be able to run `npm start`. Let’s see what that does.

$ npm start

Note: If you run into any problems while running this command, try running npx aragon ipfs install first.

You now see the aragonCLI program doing a bunch of *magical* things. This takes a while, so while it’s running, let's take a moment to analyze what’s happening.

Our Truffle project is of course also an NPM project, as all Truffle projects are, and this NPM project comes with the aragonCLI dependency installed in it:

“devDependencies”: {
"@aragon/cli": "^6.0.0"
…
},
“scripts”: {
"start": "npm run start:ipfs",
…
}

package.json (simplified)

You can also see that the configuration file defines a “start” script, which runs `aragon run --files dist`. This `run` subcommand of the aragonCLI starts the app locally, and the way this is done is quite impressive: it starts a local Ethereum network, deploys a bunch of Aragon contracts to it (including the placeholder CounterApp.sol), and finally opens the application’s frontend from a local IPFS daemon. Whoa!

Note: If you haven’t heard about IPFS (InterPlanetary File System), it’s absolutely insane, and apart from having the coolest possible name a project could have, it’s a decentralized peer-to-peer filesystem, which allows us to serve the frontend of the app also in a decentralized manner.

When the aragonCLI is finished running the command, you should see a browser window pop up with a URL that looks like this:

http://localhost:3000/#/0x60886A4022bf5ccfCB864F459a0BcBbfdA509FE7

Notice that the last element of that URL is an Ethereum address. What is it? Believe it or not, this is the address of an actual, real-life organization that the aragonCLI just built and deployed for us on the local Ethereum network. What you see in the browser is the Aragon client itself, used to interact with any Aragon organization.

If you click on the “Counter” button in the left menu, you’ll see your project’s Counter app running in the organization, with its own GUI displayed in the main app view:

If you click on the “Increment” button, you should see a panel moving in from the right, prompting you to create a transaction. The Aragon client mediates all interactions between you and the active Ethereum network via this panel. If you don’t have a Web3 provider installed in your browser, the client will notice, and suggest you to install one. If you haven’t done so already, install the MetaMask extension in your browser and point it to the local Ethereum network that the CLI started at localhost:8545.

Next, to be able to interact with the organization, you will need to use an Ethereum address that owns ether (actually, test ether). If you look at the terminal where you ran npm start you should see that the aragonCLI lists two private keys for Ethereum addresses that have funds in the locally deployed network. Go ahead and import one of these accounts into MetaMask. If you did that correctly, you should see your balance in the localhost:8545 Ethereum network be around ~96 ether. Yey, you’re rich!

Now, enable MetaMask to interact with the Aragon client by clicking on “Enable account” at the lower left of the Aragon client. This is a security feature of all Ethereum providers, which simply asks you if you want to allow a dapp, in this case the Aragon client, to interact with your address.

If you click on the “Increment” button one more time, you should see the right panel swoosh in again, but this time it displays a “Create transaction” button. When you click it, a MetaMask prompt will pop up asking you to confirm the transaction, and when you do, you should be absolutely dazzled to see that the app’s 0 just turned into a 1! Holy cow, that’s a lot of complexity for just incrementing a counter, isn’t it? Well, remember that we are dealing with an Aragon organization here. You might as well have just gone through this exact same process to submit your presidential election vote. But, we’re not quite there yet.

Go ahead and play around with the Aragon client; we won’t be using it for a while. When you’re done, kill the npm start process and close the browser window with the Aragon client.

A look from another angle - tests

We’ve just seen the Counter app from the point of view of the Aragon client. Let’s now look at it from the point of view of tests. Tests give us key insights into how an Aragon organization works, and are the gateway into the Aragon universe. Understand them, and you shall be enlightened!

If you look at the test file "test/app.js", you should find a small test suite that validates the implementation of the CounterApp.sol contract.

Go ahead and run the tests:

$ npm test

We can divide the structure of this file into two main phases: A setup phase in the beforeEach block, in which the organization and the app are deployed, and the actual tests in the following it blocks.

When setting up the organization, the imported deployDAO helper is used. A quick look at the file test/helpers/deployDAO.js reveals that the returned dao and acl objects are in fact TruffleContract instances of a Kernel contract representing the organization, and an ACL contract instance representing... Well, we don’t know what this is, but we will soon enough!

Going back to app.js, an instance of CounterApp is then deployed, and immediately after that a call to the organization’s newAppInstance(...) is made. This function takes the deployed CounterApp as a parameter and from it, produces an instance of the app. If this sounds confusing right now, it’s alright, you will understand everything in just a minute when we discuss proxies.

In the next 3 lines, we finally get our hands on the actual app instance. For this, we look into the instanceReceipt generated by the call to the organization’s newAppInstance(...) function, find a “NewAppProxy” event in it, and retrieve its “proxy” value, which is the address of the instantiated app. This address is passed to CounterApp’s at() function, which is a factory method that creates a new TruffleContract instance with CounterApp's interface for the given address.

You may be wondering why can’t we just use the instance of CounterApp that we deployed before? And more importantly, why are we even doing all this instance wrapping nonsense?

Because “proxies”. Aragon uses proxies for all of its user-facing contracts. A proxy, simply put, is a facade contract that uses another contract as its logic contract, allowing its logic to be upgraded in the future. In this case, the CounterApp contract that we previously deployed will be the reference logic contract, or the “base” contract, but not the instance that we install into the organization and use directly. If, in the future, we wanted to upgrade our app, we would simply tell the proxy to use a new version of the logic contract. The concept of proxies may seem rather confusing to you until you get used to it. However, for practical purposes, all you need to know for now is that a proxy can be treated as a normal instance of a contract.

Let’s move on. We’re almost done here!

Aragon uses a very sophisticated permissions system which groups addresses into roles, and only the addresses associated with those roles can perform certain actions in an app. ACL stands for “Access Control List” and is a central component of an Aragon organization. Don’t be scared of it though, it’s basically onlyOwner on steroids.

You can see that we are granting ANY_ADDRESS, the INCREMENT_ROLE permission in the CounterApp. Additionally, we’re also specifying that the appManager address should be the manager for this role. Whenever dealing with ACL permissions, it’s helpful to think in terms of who, what, and where. Who can perform what action where (and who can manage further permissions).

In this particular case, since the CounterApp’s increment(...) function is guarded by the auth(INCREMENT_ROLE), only addresses who have been granted INCREMENT_ROLE will be allowed to call this function.
Finally, the setup calls the app’s initialize() function. Put this on ice, we will come back to it later. Spoiler: it also has to do with proxies!

If you’re feeling adventurous, go ahead and take a deeper look at how deployDAO(...) works in the file test/helpers/deployDao.js. You don’t need to understand all of it, but nobody’s stopping you from doing so :)

Removing the Counter app and replacing it with your own

Let’s get rid of that placeholder Counter app and bootstrap our own HCVoting app, shall we?

We won’t be deleting any CounterApp files just yet, since they may come in handy as a reference later on. We’ll simply tell the aragonCLI that our Aragon app lives in another file. The backend of an Aragon app is always just a smart contract, which we’ll be swapping out here.

First, create the file:

$ touch contracts/HCVoting.sol

And then modify “arapp.json” to point the aragonCLI to another file. As far as we’re concerned right now, this file is simply something that ties together the app’s back and front ends, and tells the aragonCLI where to look for files.

"path": "contracts/HCVoting.sol"

arapp.json, line 32 (modified)

Now, as for the Solidity contents of the new file, enter this:

import "@aragon/os/contracts/apps/AragonApp.sol";
contract HCVoting is AragonApp {
function initialize() public onlyInit {
initialized();
}
}

HCVoting.sol

Our first goal will be to run HCVoting.sol through the same tests, instead of CounterApp.sol. To do this, go ahead and replace all occurrences of “CounterApp” in the app.js test file with “HCVoting”.

Then, replace the two ‘it’ blocks with a new “dummy” block:

it('dummy', async () => {
// Test me!
})

test/app.js, line 91

If you run npm test now, you will see that the tests fail because appBase.INCREMENT_ROLE is missing. Look at CounterApp.sol, you will see where this role is declared:

bytes32 constant public INCREMENT_ROLE = keccak256("INCREMENT_ROLE");

contracts/CounterApp.sol, line 18

This failure makes sense, because the tests call the public getter “INCREMENT_ROLE()”, which existed in CounterApp.sol but doesn’t exist anymore in HCVoting.sol. Let’s comment out the lines that set permissions for the app from app.js for now (lines 34 to 47). We’re just commenting out these lines because they’ll come in handy later, when we define roles in HCVoting.sol.

Now, if you run npm test again, you should see that the tests pass again, but this time with our very own HCVoting app.

Inheriting from AragonApp

Before we wrap things up for this part of the tutorial, let’s take a closer look at how HCVoting’s code extends AragonApp and implements an initialize() function.

aragonOS is the main Solidity module of the Aragon stack. It contains all the on-chain goodies that make the organization’s machinery work, as well as everything you need to build Aragon apps. When you extend AragonApp, you gain access to a myriad of tools and features as well as the ability to have your app interact with its parent organization, as well as with other apps installed in it. Let’s have a quick look at AragonApp’s inheritance tree:

└─ AragonApp
├─ AppStorage
├─ Autopetrified
│  └─ Petrifiable
│     └─ Initializable
│        └─ TimeHelpers
├─ VaultRecoverable
│  ├─ IVaultRecoverable
│  ├─ EtherTokenConstant
│  └─ IsContract
├─ ReentrancyGuard
├─ EVMScriptRunner
│  ├─ AppStorage
│  ├─ Initializable
│  │  └─ TimeHelpers
│  ├─ EVMScriptRegistryConstants
│  └─ KernelNamespaceConstants
└─ ACLSyntaxSugar

AragonApp’s inheritance tree, obtained with npx pocketh inheritance build/contracts/AragonApp.json. [link]

Here, we’re using Initializable’s onlyInit modifier, which ensures that a function can be called only until initialized() is called. Our initialize() function will act as our HCVoting contract’s constructor.

Proxy time, round two! When you put code inside a constructor function in Solidity, or initialize a variable in the global scope of a contract, all these operations are intended to be executed during the deployment of a contract, to set up its initial state. Such code never actually makes it to the deployed bytecode of a contract.

To illustrate, consider a simple contract that sets the value of a state variable myValue to 1 in its constructor, and exposes a simple function to increment such value, incrementMyValue(). When the contract is deployed, the constructor code is run, which results in myValue being set to 1. Right after that, the constructor and all the code within it is discarded. Then, the code for the incrementMyValue() function is uploaded to the contract so that it now contains two things: the state variable myValue set to 1, and the deployed bytecode. In this deployed bytecode, you won’t find any logic for myValue being set to 1.

Now, recall how we deployed HCVoting in our tests. We deployed an instance of it, but instead of using this instance directly, we passed it to a proxy, to be used as its base contract. Whatever state this instance has, state that could have been set up using a constructor, is completely ignored by the proxy since all the proxy cares about is the base contract’s deployed bytecode.

Thus, if we want any code to be run during the beginning of the lifetime of our proxy, we’re going to have to put it in a function that does make it to the contract’s deployed bytecode, and that function needs to be guarded so that it can only be called once. That’s exactly what we just did with the initialize() function.

That’s all folks!

We’ve successfully bootstrapped an Aragon app using create-aragon-app. We’ve interacted with it in the browser, and we’ve completely dissected its tests to further understand how all this works together.

In the next part of the tutorial, we will start building voting functionality into the HCVoting contract.

Stay tuned


Other parts of this series:


This post was a collaboration between

Alejandro Santander, Aragon One

  • Alejandro Santander

    Alejandro Santander

    Read more posts by this author.

    Alejandro Santander
  • Aragon One

    Aragon One

    Aragon One is a for-profit company that encompasses the foundational team working on the Aragon project. The company is currently established in Switzerland, although we want it to function as a DAO

    More posts by Aragon One.

    Aragon One