Aragon OS のチュートリアルをやってみた

Aragon OS

自律型非中央集権組織 (DAO, Decentralized Autonomous Organization)のためのツールとして、Aragon OSを紹介したいと思います。

ちなみに、DAOは自律型分散組織とか訳されていますが、Decentralized(非中央集権)Distributed(分散)は訳を分けたいです。両者は技術的に異なるものを指します。DAOのDはDistributed(分散)ではなく、あくまでもDecentralized(非中央集権)であり、Blockchainの思想そていは後者のDecentralized(非中央集権)だと考えています。

さて、Aragon OSのGetting-Startedページを見ると、Aragon OSについて以下のように紹介されています。


Aragon is a project to empower freedom by creating tools for decentralized governance.

These tools help people freely organize across borders and without intermediaries. Instead of bureaucracy, subjectivity, and trust, smart contracts have opened the door to experiment with governance at the speed of software.

The Aragon stack helps you develop software for human organization. From the smart contracts to the user interface, Aragon takes care of the most important pieces of infrastructure to deliver censorship-resistant, decentralized and upgradeable apps.

(訳:Aragonは非中央集権統治のためのツールを作成することで、自由を促進するプロジェクトです。

これらのツールは、人々が仲介者を減ること無く自由に境界を超えてつながることの助けとなります。官僚性、主観主義、信用の裏付けに変わり、スマートコントラクトは、ソフトウェアのスピードによってもたらされる統治の形を実験する門戸を開きます

Aragonが提供するスタックを通じ、人々のつながりを実現するソフトウェを開発するのを支援します。スマートコントラクトからユーザインタフェースまで、Aragonは検閲されない、非中央集権的、かつ更新が可能なアプリケーションの提供を可能とする、最重要なインフラ構成要素について機能提供を行います。)


文言が無政府主義的なのはブロックチェーンの特徴?というか気質なのでそこはともかく、機能的にはよくありませんか?ユーザーインタフェースも提供してくれるし、スマートコントラクトの更新可能性も提供してくれるのですから。どんなものか感じをつかむために、よくできたチュートリアルが提供されているので、以下でチュートリアルに沿ってアプリケーションを構築してみます。

チュートリアル

セットアップ

まずはAragon OSの雛形環境を作ります。

$ npx create-aragon-app aragonosplayground.eth tutorial
  ✔ Preparing initialization
  ✔ Cloning app template
  ✔ Installing package dependencies
  ✔ Check IPFS
✔ Created new application aragonosplayground.eth in aragonosplayground

Start your Aragon app by typing:

  cd aragonosplayground
  npm start

Visit https://hack.aragon.org/docs/cli-main-commands for more information.
(注:githubに上げるときに、生成ファイルを一階層上のフォルダにうつしました)

以下のファイルが雛形として作成されます。truffleのプロジェクトになっていました。


npm startでアプリを使い始められるようなことが書いてありますが、この段階ではエラーになります。package.jsonのscriptの中身が空です。

スマートコントラクト

チュートリアルの通りに、contracts/CounterApp.solの中身を記述します

// contracts/CounterApp.sol
pragma solidity 0.4.24;

contract CounterApp {
    // Events
    event Increment(address entity);
    event Decrement(address entity);

    // State
    int public value;

    function increment() external {
        value += 1;
        emit Increment(msg.sender);
    }

    function decrement() external {
        value -= 1;
        emit Decrement(msg.sender);
    }
}

Aragon OSのアプリとするためには、以下の継承の記述を上記のスマートコントラクトに追加するそうです。

import "@aragon/os/contracts/apps/AragonApp.sol";

contract CounterApp is AragonApp {

このアプリでは、スマートコントラクトのIncrement,Decrement機能を呼び出すことのできるRoleを定義し、各increment、decrement関数呼び出し時のmodifierとしてauthを追加することで、Roleに基づいた関数呼び出しのコントロールを行うようです。最終的なスマートコントラクトの実装は以下のようになります。

pragma solidity 0.4.24;

import "@aragon/os/contracts/apps/AragonApp.sol";

// inherit from the Aragon app smart contract
contract CounterApp is AragonApp {
    // Events
    event Increment(address entity);
    event Decrement(address entity);

    // define the roles that you want your app to have.
    // A role can be assigned to other apps or people
    // and those entities will have access to methods guarded by that role.
    bytes32 constant public INCREMENT_ROLE = keccak256("INCREMENT_ROLE");
    bytes32 constant public DECREMENT_ROLE = keccak256("DECREMENT_ROLE");

    // State
    int public value;

    function initialize() onlyInit public {
        initialized();
    }

/**
     * @notice Increment the counter by 1
     */
    function increment() auth(INCREMENT_ROLE) external {
        value += 1;
        emit Increment(msg.sender);
    }

/**
     * @notice Decrement the counter by 1
     */
    function decrement() auth(DECREMENT_ROLE) external {
        value -= 1;
        emit Decrement(msg.sender);
    }
}

チュートリアルの中の記述に従い、関数のコメントに@noticeという記述を加えています。スマートコントラクトの関数の説明を記述しやすくするAragon OSの機能だそうです。

フロントエンド

Aragonのクライアントは直接的には、Web3とやりとりしないそうです。

Aragonのappは上記のクライアントとaragonAPIというAPIを呼び出すことで、独自のRPCのプロトコルでやりとりします。このappを解することで、間接的にWeb3機能を呼び出し、トランザクションの署名、通知の表示と行った機能をエンドユーザーに提供するそうです。

バックグラウンド処理の記述

スマートコントラクトのイベントを受信するバックグラウンドの処理を記述します。aragonAPIを使うことで、以前TruffleのDrizzleでイベント処理をしたときよりもSimpleな仕組みになっている気がします。storeのところはreducerの機能を使っているので、ReactのフレームワークだったDrizzleと共通の仕組みを使っているようです。

// app/script.js
import '@babel/polyfill'
import Aragon from '@aragon/api'

const app = new Aragon()

const initialState = {
    count: 0,
}
app.store(async (state, event) => {
    if (state === null) state = initialState

    switch (event.event) {
        case 'Increment':
            return { count: await getValue() }
        case 'Decrement':
            return { count: await getValue() }
        default:
            return state
    }
})

async function getValue() {
    // Get current value from the contract by calling the public getter
    // app.call() returns a single-emission observable that we can immediately turn into a promise
    const value = await app.call('value').toPromise()
    return parseInt(value, 10)
}

Stateの表示

フロントエンド部分として、app/index.htmlとapp/app.jsを記述します。
app/app.js内の、const app = new Aragon(new providers.WindowMessage(window.parent))により、aragonAPIへの入り口を用意しているようです。

app/index.html

<!-- app/index.html !-->
<!DOCTYPE html>
<html>
  <head>
    <title>Counter App</title>
  </head>
  <body>
    <button id="decrement">-</button>
    <div id="view">...</div>
    <button id="increment">+</button>
    <script src="app.js"></script>
  </body>
</html>

app/app.js

// app/app.js
import Aragon, { providers } from '@aragon/api'

const initializeApp = () => {
    const app = new Aragon(new providers.WindowMessage(window.parent))

    const view = document.getElementById('view')
    const increment = document.getElementById('increment')
    const decrement = document.getElementById('decrement')

    increment.onclick = () => {
        app.increment()
    }
    decrement.onclick = () => {
        app.decrement()
    }

    app.state().subscribe(
        state => {
            // the state is null in the beginning, when there are no event emitted from the contract
            view.innerHTML = `The counter is ${state ? state.count : 0}`
        },
        err => {
            view.innerHTML = 'An error occured, check the console'
            console.log(err)
        }
    )
}

const sendMessageToWrapper = (name, value) => {
    window.parent.postMessage({ from: 'app', name, value }, '*')
}

// handshake between Aragon Core and the iframe,
// since iframes can lose messages that were sent before they were ready
window.addEventListener('message', ({ data }) => {
    if (data.from !== 'wrapper') {
        return
    }
    if (data.name === 'ready') {
        sendMessageToWrapper('ready', true)
        initializeApp()
    }
})

ビルド

フレームワーク作成段階で空だったpackage.jsonのscriptsの中身を変更します。ここでは、チュートリアルの先の方で追加する記述内容も合わせて記述しています。

  "scripts": {
    "build": "parcel build app/script.js -d dist/ && parcel build app/index.html -d dist/ --public-url \".\"",
    "start:app": "npm run build -- --no-minify && parcel serve app/index.html -p 8001 --out-dir dist/ --no-cache",
    "start:aragon:http": "npx aragon run --http localhost:8001 --http-served-from ./dist"
  }

また、arrapp.jsonの記述を追加します。

{
    "roles": [
        {
            "name": "Increment the counter",
            "id": "INCREMENT_ROLE",
            "params": []
        },
        {
            "name": "Decrement the counter",
            "id": "DECREMENT_ROLE",
            "params": []
        }
    ],
    "environments": {
        "default": {
            "network": "development",
            "appName": "aragonosplaygraound.aragonpm.eth"
        }
    },
    "path": "contracts/CounterApp.sol"
}

manifest.jsonの中身も書き換えます。

{
  "name": "Counter",
  "description": "My first Aragon app",
  "script": "/dist/script.js",
  "start_url": "/dist/index.html"
}

 アプリを走らせる

まずアプリを起動します。もろもろのBuild処理が自動で行われるようです。

$ npm run start:app

> app-name@1.0.0 start:app /home/hajime/git/aragonosplayground
> npm run build -- --no-minify && parcel serve app/index.html -p 8001 --out-dir dist/ --no-cache

> app-name@1.0.0 build /home/hajime/git/aragonosplayground
> parcel build app/script.js -d dist/ && parcel build app/index.html -d dist/ --public-url "." "--no-minify"

✨  Built in 5.49s.

dist/script.js.map    ⚠️  1.21 MB     73ms
dist/script.js         391.89 KB    5.19s
✨  Built in 3.12s.

dist/app.ec81a464.js.map    950.98 KB     66ms
dist/app.ec81a464.js        507.22 KB    2.84s
dist/index.html                 273 B    159ms
Server running at http://localhost:8001 
✨  Built in 2.91s.

エンドユーザーのクライアント側を起動します。でもCannot find the ipfs dependency. Has this module installed correctly?となり、エラーとなります。

 npm run start:aragon:http

> app-name@1.0.0 start:aragon:http /home/hajime/git/aragonosplayground
> npx aragon run --http localhost:8001 --http-served-from ./dist

Cannot find the ipfs dependency. Has this module installed correctly?
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! app-name@1.0.0 start:aragon:http: `npx aragon run --http localhost:8001 --http-served-from ./dist`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the app-name@1.0.0 start:aragon:http script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/hajime/.npm/_logs/2019-11-20T21_25_39_470Z-debug.log

どうやらipfsが走っていることが、aragonOSを実行するうえでの実質的な前提条件のようです。ipfsそのものはインストールしていたのですが、node.js用にはインストールしていなかっので、OSグローバルにインストールします。シンプルにsudo npm install -g ipfsと実行しても、権限不足エラーでインストールできなかったので、いろいろオプションをつけてインストールしました。最終的にインストールが成功したコマンドを以下に記載します。

sudo npm install -g ipfs --unsafe-perm=true --allow-root

無事にインストールできたので、あらためてアプリ、クライアントを実行します。

app起動

$ npm run start:app
(コマンド実行結果は省略)

IPFSデーモン起動

$ ipfs daemon
Initializing daemon...
go-ipfs version: 0.4.21-8ca278f45
Repo version: 7
System version: amd64/linux
Golang version: go1.12.6
Swarm listening on /ip4/10.0.1.40/tcp/4001
Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/172.17.0.1/tcp/4001
Swarm listening on /ip4/172.18.0.1/tcp/4001
Swarm listening on /ip6/::1/tcp/4001
Swarm listening on /p2p-circuit
Swarm announcing /ip4/10.0.1.40/tcp/4001
Swarm announcing /ip4/127.0.0.1/tcp/4001
Swarm announcing /ip4/172.17.0.1/tcp/4001
Swarm announcing /ip4/172.18.0.1/tcp/4001
Swarm announcing /ip6/::1/tcp/4001
API server listening on /ip4/127.0.0.1/tcp/5001
WebUI: http://127.0.0.1:5001/webui
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready

クライアント起動(デフォル度ブラウザが起動します)。ちなみにBuilt時間はかなりうそです。初回は結構な時間がかかりました。

$ npm run start:app

> app-name@1.0.0 start:app /home/hajime/git/aragonosplayground
> npm run build -- --no-minify && parcel serve app/index.html -p 8001 --out-dir dist/ --no-cache

> app-name@1.0.0 build /home/hajime/git/aragonosplayground
> parcel build app/script.js -d dist/ && parcel build app/index.html -d dist/ --public-url "." "--no-minify"

✨  Built in 7.23s.

dist/script.js.map    ⚠️  1.21 MB    110ms
dist/script.js         391.89 KB    6.89s
✨  Built in 4.03s.

dist/app.ec81a464.js.map    950.98 KB     76ms
dist/app.ec81a464.js        507.22 KB    3.76s
dist/index.html                 273 B     11ms
Server running at http://localhost:8001 
✨  Built in 3.52s.

無事クライアントの画面が起動しました。

ん?カウンターが動かない

ここまで来て、あとはボタンクリックして終わりと思ったのですが、なんとカウンターが動かない。どうやらスマートコントラクトのmodifierとしてauthという権限チェックが「うまく」きいてくれていて、権限無しで実行すると引っかかるようです。メッセージにもそのような内容が出てきています。

フロントのオペレーションの際に、MetaMaskと連動して、権限チェックしてくれるのはいいですね。いろいろきめ細やかにできているようだし、本来の目的のDAOには至っていないので、しばらく楽しめそうです。このTutorialの段階では、アプリ開発フレームワークとして、Drizzleよりちょっと使い勝手がいいよね、くらいなので。

よく見ると、 npm run start:aragon:httpの出力に、ローカルチェーンの情報がありました。MetaMaskでローカルアカウントを設定して(Address #1を使いました)、再度実行します。

Address #1:  0xb4124cEB3451635DAcedd11767f004d8a28c6eE7 (this account is used to deploy DAOs, it has more permissions)
Private key: a8a54b2d8197bc0b19bb8a084031be71835580a01e70a45a13babd16c9bc1563
Address #2:  0x8401Eb5ff34cc943f096A32EF3d5113FEbE8D4Eb 
Private key: ce8e3bda3b44269c147747a373646393b1504bfcbb73fc9564f5d753d8116608
 ℹ The accounts were generated from the following mnemonic phrase:
explain tackle mirror kit van hammer degree position ginger unfair soup bonus

 ℹ This is the configuration for your development deployment:
    Ethereum Node: ws://localhost:8545
    ENS registry: 0x5f6f7e8cc7346a11ca2def8f827b7a0b612c56a1
    APM registry: aragonpm.eth
    DAO address: 0xF98b8EB330B81b6EAF0231adBE356B6d78E1336F

スマートコントラクトが発行したイベントを引き金に、メッセージが表示されます。

Metamaskのトランザクションの承認(Confirm)画面が出てきます。

無事にカウントアップしました。

Tutorial実施後のファイルはGitHubにアップロードしました。

参照

https://github.com/aragon/aragonOS
https://hack.aragon.org/docs/tutorial

コメント

  1. […] AragonOSのチュートリアルをやってみたの続きです。先のチュートリアルでは、起動時にローカルチェーンを立ち上げてくれていたので、スマートコントラクトのデバッグなどを考えて、Ganacheを別途起動した上で、実行してみようかと思います。ただこの投稿では、うまくいっていないです。 […]

  2. […] AragonOSのチュートリアルを試した際の、プロジェクトフォルダ内のnode_moduleを覗くと、node_modules/@aragon/os/contracts/lib/ens配下に、 […]

タイトルとURLをコピーしました