LogoLogo
These products have been deprecated and are no longer being maintained. For a better experience and support, please check out our new stack Aragon OSx.
Aragon Legacy Documentation
Aragon Legacy Documentation
  • 🔷Aragon
    • Aragon Legacy Documentation
    • Aragon Values, Finances, and Legal
      • Legal and technical infrastructure
      • Financial infrastructure
      • Meet your DAO support network
    • Learn about DAOs
      • What is a DAO?
      • Why do we need DAOs?
      • What is decentralized autonomous governance?
      • What is the AN DAO?
      • TAO Voting
      • Why use Aragon to build a DAO?
  • 🌐Products
    • Prerequisites
    • Quickstart
    • Setting up a Metamask Wallet
      • Getting started with Ethereum
      • Getting started with Goerli Testnet
      • Getting started with Polygon
      • Getting started with Mumbai Testnet
      • Getting started with Harmony
      • Getting started with Harmony testnet
      • Getting started with Metis Andromeda
      • Getting started with Stardust Testnet
      • Getting started with BSC Testnet
      • How to sign a transaction?
      • Import your seed wallet to Metamask
      • Gas Tracker
    • Setting up a Frame Wallet
    • Setting up a Gnosis Safe MultiSig Wallet
    • Aragon Client
      • What is Aragon Client
      • How to create a DAO
        • Templates
        • Using the Company Template
        • Using the Membership Template
        • Use the Reputation template
      • How to create a DAO on Polygon
      • How to create a DAO on Harmony
      • How to navigate your DAO
        • Home
        • The Apps
          • Tokens App
          • Voting App
          • Finance App
          • Agent App
            • How to install the Agent App in your DAO
            • Using Agent with Frame
        • System Setting
          • Permissions Setting
          • App Center
          • Organization Setting
      • After you've started a DAO
        • How to change the Quorum of your DAO
          • Change Quorum using Aragon Console
          • Change Quorum using EVMcrispr
        • How to create a Legal Wrapper for your DAO with Otoco
        • How to Operate your DAO from your Mobile Phone
      • How to Brick your DAO 🧱
    • Aragon Govern
      • What is Aragon Govern?
      • How to create a Govern DAO
      • Navigate into your Govern DAO
        • How to mint and assign DAO tokens to others
        • How to deposit funds
        • Challenging a transaction
      • Reasons for the delay period in the transaction
      • Collateral for scheduling or challenging a transaction. Why?
      • Acting as a guardian for an Aragon Govern dispute
      • Using the Client DAO with the Govern DAO
    • Aragon Voice
      • What is Aragon Voice?
      • Creating a voting proposal
      • Creating a voting proposal using your token
      • Voting on a proposal
    • Aragon Vocdoni
      • What is Aragon Vocdoni
      • Creating a Vodconi organization
      • Accessing your Vocdoni organization
      • Navigating your Vocdoni organization
        • Creating a voting proposal
        • Voting on a proposal (anonymous voting disabled)
        • Voting on a proposal (anonymous voting enabled)
    • Aragon Court
      • What is Aragon Court
      • Court Dashboard
      • Dispute lifecycle
      • Acting as guardian for a dispute
      • Glossary
  • 🛠️Developers
    • Legacy Developer Documentation
    • General Tools
      • The Basics
        • Before starting
        • Quick start
        • Tech Stack
        • App permissions
        • Forwarding
        • Upgradeability
        • Package management
        • Templates
        • Aragon client
        • Human readable transactions
      • Guides
        • How to create your first custom DAO using Aragon CLI!
        • How to use the Agent App
          • Installing Aragon Agent from aragonCLI
          • Setting and Checking permissions
          • Interacting with Aragon Agent
        • How to build your first Aragon App!
        • How to publish an Aragon App to aragonPM
        • How to migrate existing Aragon App to Buidler plugin
        • How to change the Quorum of your DAO
          • Change Quorum using Aragon Console
          • Change Quorum using EVMcrispr
        • Deploying Aragon Client in new Chains
          • Deployments information
            • Harmony testnet
            • BSC Tesnet
            • Harmony
            • Metis stardust
            • Metis Andromeda
        • How to Brick your DAO 🧱
        • How to sign with Web3 providers
          • Setting up a Metamask Wallet
            • Import your seed phrase into Metamask
            • Import your private key into Metamask
            • Sign a transaction with Metamask
          • Setting up a Frame Wallet
            • Sign a Transaction with Frame
        • Troubleshooting
      • aragonOS
        • Introduction
        • Motivations
        • Developing with aragonOS
        • Reference documentation
        • Migrating to aragonOS 4 from aragonOS 3
        • Reference (aragonOS 3)
        • Smart Contract References
          • ACL
            • ACL
            • ACLSyntaxSugar
            • ACLHelpers
            • IACL
            • IACLOracle
          • APM
            • APMNamehash
            • APMRegistry
            • APMInternalAppNames
            • Repo
          • APPS
            • AppProxyBase
            • AppProxyPinned
            • AppProxyUpgradeable
            • AppStorage
            • AragonApp
            • UnsafeAragonApp
          • COMMON
            • Autopetrified
            • ConversionHelpers
            • DelegateProxy
            • DepositableDelegateProxy
            • DepositableStorage
            • EtherTokenConstant
            • IForwarder
            • IForwarderFee
            • IVaultRecoverable
            • Initializable
            • IsContract
            • Petrifiable
            • ReentrancyGuard
            • SafeERC20
            • TimeHelpers
            • Uint256Helpers
            • UnstructuredStorage
            • VaultRecoverable
          • ENS
            • ENSConstants
            • ENSSubdomainRegistrar
          • EVMSCRIPT
            • EVMScriptRegistry
            • EVMScriptRunner
            • IEVMScriptExecutor
            • IEVMScriptRegistry
            • EVMScriptRegistryConstants
            • ScriptHelpers
          • EVMSCRIPT/EXECUTORS
            • BaseEVMScriptExecutor
            • CallsScript
          • FACTORY
            • APMRegistryFactory
            • AppProxyFactory
            • DAOFactory
            • ENSFactory
            • EVMScriptRegistryFactory
          • KERNEL
            • IKernel
            • IKernelEvents
            • Kernel
            • KernelAppIds
            • KernelNamespaceConstants
            • KernelProxy
            • KernelStorage
      • aragonCLI
        • Introduction
        • Main commands
        • DAO commands
        • APM commands
        • IPFS commands
        • Global configuration
      • aragonPM
        • Introduction
        • Architecture
        • Reference documentation
      • aragonAPI
        • Introduction
        • Javascript
          • Quick Start
          • App API
          • React API
          • Wrapper
          • Providers
          • Architecture of apps
          • Background Scripts
      • aragonUI
        • Getting started
        • How to upgrade
        • BASE
          • Spacing
          • Colors
          • Text styles
          • Icons
          • Main
        • ACTIONS
          • Button
          • ContextMenu
        • NAVIGATION
          • Tabs
          • Pagination
          • BackButton
          • Link
          • Header
        • STRUCTURE
          • Bar
          • Box
          • Card
          • Split
          • DataView
          • Table
          • EmptyStateCard
          • IdentityBadge
          • TransactionBadge
          • Tag
          • Accordion
          • Timer
          • TokenAmount
          • EthIdenticon
          • TransactionProgress
        • DATA ENTRY
          • AutoComplete
          • DateRangePicker
          • DropDown
          • Switch
          • Radio
          • CheckBox
          • Slider
          • TextInput
          • SearchInput
          • AddressField
          • RadioGroup
          • RadioList
          • TextCopy
          • Field
        • VISUALIZATION
          • CircleGraph
          • LineChart
          • Distribution
        • FEEDBACK
          • Info
          • ProgressBar
          • LoadingRing
          • Toast
          • SyncIndicator
          • FloatIndicator
        • OVERLAYS
          • Help
          • Popover
          • Modal
          • SidePanel
        • ADVANCES
          • ButtonBase
          • FocusVisible
          • PublicUrl
          • Redraw
          • RedrawFromDate
          • Root
          • RootPortal
          • Viewport
      • aragonDS
        • Guidelines
          • Layout
          • Color
          • Iconography
          • Typography
          • Illustrations
        • Components
          • Overview
      • Aragon Connect
        • Guides
          • Aragon Basics
          • Getting started
          • Usage with React
        • Advanced
          • Custom Subgraph queries
          • Writing an App Subgraph
          • Writing an App Connector
        • Connectors
          • Organizations
          • Tokens app
          • Voting app
          • Finance app
        • API reference
          • connect()
          • App
          • Connectors
          • Organization
          • Permission
          • Repo
          • Role
          • TransactionIntent
          • TransactionPath
          • TransactionRequest
          • Types
          • Errors
      • App Center
        • App Center
        • Preparing Assets
        • Submitting Your App to the App Center
    • Product Tools
      • Aragon Govern
        • README
        • Introduction
          • Concepts and background
            • Govern Core concepts
            • ERC3000
          • Developers
            • Getting started
            • Govern.js API
            • Historical deployments
            • GraphQL API
            • Smart contracts breakdown
        • Deployments
          • Mainnet
          • Rinkeby
        • Packages
          • ERC 3k
          • Govern Console
          • Govern contract utils
          • Types
          • govern-create
          • Govern Server
          • govern-subgraph
          • govern-token
          • govern.js
      • Aragon Vocdoni
    • Aragon Client Glossary
  • THE ANT TOKEN
    • Aragon Network Token
      • About ANT
      • Historical token sale
    • ANTv1
      • Non-standard behaviours and gotchas
      • About the MiniMe token
      • The initial token sale flow
    • ANTv2
      • Upgrade portal
        • Troubleshooting
      • Contract interaction
      • Migrating on-chain liquidity
    • Developers
      • Quick start
      • Integrating ANT
      • Historical deployments
      • Security policy
  • ‼️FAQ
    • Products
      • Aragon Client
        • Where is my DAO?
        • DAO creation taking a long time to confirm
        • DAO is taking a long time to load
        • Failed DAO creation transaction
        • Why do I see a Blue Screen?
        • An unexpected error has occurred
        • App does not appear in Firefox
        • Receiving funds directly to the Agent or Vault address
        • How to Recover Funds accidentally sent to an Aragon App address
        • Depositing EURS in the Finance app
        • Which templates are available on the Ethereum Network?
        • Which templates are available on the Polygon Network?
        • Which templates are available on the Harmony Network?
        • Which templates are available on the Metis Andromeda Network?
        • How to delete a DAO
      • Aragon Govern
        • Which was the wallet address used to create the Aragon Govern DAO?
        • Where are my DAO tokens?
        • How to delete a DAO
        • How can I transfer funds to the Aragon Govern DAO?
      • Aragon Vocdoni
        • Is Vocdoni easy to use?
        • Is Vocdoni anonymous?
        • Is Vocdoni free?
        • Is my data safe with Vocdoni?
        • As an Organization, what can I do with Vocdoni?
      • Aragon Court
        • What is the current duration of the different stages of a dispute?
        • Dispute - Which fees need to be paid to create a dispute?
        • Dispute - Do I need to put collateral to create a dispute?
        • Appeals - How much money is needed to appeal a dispute? And to confirm the appeal? What is it for?
        • Appeals - If I have tokens staked or activated, can I lose them if I appeal a dispute?
        • Appeals - What happens to the collateral put up
        • Voting - Is a majority needed to win a vote?
        • Voting - What happens if there is a tie?
        • Voting - What does "Refuse to vote" mean? What happens if it's the most voted option?
        • Voting - Another guardian tried to collude. Can I punish this guardian?
        • Voting - What's the penalty for leaked votes?
        • Governance - Which parameters of Court can be changed? How?
        • Governance - Do parameter changes affect ongoing disputes?
        • Technical - Where is the Court "hosted"?
        • Technical - Where can I find the source code and technical documentation for Aragon Court?
        • ANJ conversion - What date will the lock-up period end?
        • ANJ conversion - If I have not staked my $ANJ, do I still get the lockup period price?
        • ANJ conversion - Will I get the 0.044 conversion if I convert after September 5th 2021?
        • ANJ conversion - How much will it cost to be a Guardian in Aragon Court with $ANT?
        • I can't see my tokens in the Dashboard
        • I activated my tokens but I can't see my probability of being drafted
    • Miscellaneous
      • Metamask wallet transaction alert
      • Is Aragon open source?
      • Where can I browse through the DAOs created on Aragon?
      • How to migrate from "old" DAI to "new" DAI
      • Security notice for organizations created before Aragon 0.8
      • General troubleshooting tips
    • ANT Token
      • What can I do with ANT?
      • Who holds ANT?
      • Who are the biggest ANT holders?
      • Long-term holding ANT - What benefits?
      • Can I delegate my network votes to somebody else?
      • Can I do flash loans with ANT?
      • Is there an ANT options market?
      • Are you planning to launch new network tokens?
      • My wallet isn't available on the Upgrade Portal
      • How can connect my Ledger to the Upgrade Portal?
      • I accidentally sent my "old" ANT to an exchange
      • ANJ conversion - What is the minimum number of $ANJ I need to participate in the 0.044 conversion?
      • ANJ conversion - What is the conversion rate ANJ to ANT v2?
Powered by GitBook
On this page
  • Design philosophy
  • Components:
  • Kernel
  • The app mapping
  • Namespaces
  • App installation
  • App permissioning
  • Upgradeability
  • Permissions
  • Events
  • Interface
  • API documentation
  • ACL
  • Managing permissions
  • Basic ACL example
  • Permission managers
  • Parameter interpretation
  • Permissions
  • Events
  • API documentation
  • AragonApp
  • Security recommendations and sane defaults
  • Authentication
  • Application lifecycle guarantees
  • Application capabilities
  • API documentation
  • Forwarders and EVMScripts
  • Forwarding and transaction pathing
  • EVMScripts
  • Making an app a Forwarder
  • API documentation

Was this helpful?

  1. Developers
  2. General Tools
  3. aragonOS

Reference documentation

PreviousDeveloping with aragonOSNextMigrating to aragonOS 4 from aragonOS 3

Last updated 2 years ago

Was this helpful?

Documentation for aragonOS v4.0.1.

This section provides a technical overview of the framework's architecture and provides insight into its capabilities. It assumes the reader understands . For a less technical introduction, visit the.

Looking for aragonOS 3 documentation? Go .

Design philosophy

Using aragonOS allows you to write simpler code by decoupling the specific business logic of a protocol or application from its authentication logic.

With aragonOS, you don't have to think about how to implement authentication or governance at all. Simply inherit from the AragonApp base class and use a special modifier to mark actions that require authentication.

Additionally, upgradeability capabilities are provided and are used by default. aragonOS implements the pattern with a special implementation called . This pattern essentially splits a contract into two instances: a base logic contract, which is then depended upon by a simple, slim proxy contract. The proxy delegates all its logic to the linked base contract and can modify its pointer to the base contract in order to upgrade its logic.

Components:

Kernel

The app mapping

At the core of the Kernel lives a mapping called the app mapping. You can set and get apps using the following interfaces:

function setApp(bytes32 namespace, bytes appId, address app) public;
function getApp(bytes32 namespace, bytes32 appId) public view returns (address);
  • namespace: specifies what type of app record is being set.

Warning

Modifying this mapping can have completely destructive consequences and can result in loss of funds. The permission to execute this action, APP_MANAGER_ROLE, is critical and has to be well protected behind the ACL.

Namespaces

The Kernel implements three namespaces in which installed apps can be registered:

  • Core namespace (keccak256('core')): the core components of the Kernel. The only contract set in the core mapping should be the reference to the Kernel's base contract.

  • Base namespace (keccak256('base')): the base contract addresses for proxy instances.

  • App namespace (keccak256('app')): the "default" app address for an installed app. This is used when an app might need to reference another app in the organization, for example, the default ACL instance or the EVMScriptsRegistry.

App installation

The notion of "installing" an application in aragonOS is somewhat tricky. Although the Kernel keeps information about apps and their bases, it does not actually keep every app instance stored within its apps mapping.

While aragonOS is unopinionated about using base or proxy contracts as app instances, by default it prefers using proxies to allow for upgradeability.

function newAppInstance(bytes32 appId, address appBase);
function newPinnedAppInstance(bytes32 appId, address appBase);
  • app: Address of the base contract for the app instance. If this app has already been installed previously, this address must be the same as the one currently set (use getApp(kernel.APP_BASES_NAMESPACE(), appId) to check).

Overloaded versions of the two functions with more options are available:

function newAppInstance(bytes32 appId, address appBase, bytes initializePayload, bool setDefault);
function newPinnedAppInstance(bytes32 appId, address appBase, bytes initializePayload, bool setDefault);
  • initializePayload: calldata to be used to immediately initialize the app proxy, useful for atomically initializing the new app proxy in one transaction.

  • setDefault: set the new app as the default instance of the app in the Kernel (i.e. also set it in the App namespace).

App permissioning

For convenience, the Kernel exposes the following interfaces for getting the default ACL as well as whether an entity has permission to invoke a particular action on an app:

function acl() public view returns (IACL);
function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool);

Upgradeability

Upgradeability of apps and the Kernel itself is done by setting a new address for a specific key in the apps mapping with either the Core or Base namespace.

Kernel upgradeability

Kernel instances for different organizations can share the same implementation. Every Kernel instance is a KernelProxy, allowing them to be upgradeable.

Warning

Be extremely careful when upgrading the Kernel! The logic for upgrading to a new implementation is in the implementation itself, and so an upgrade to the Kernel could render it un-upgradeable or even unusable!

Upgrading the Kernel of an organization is done by changing the Kernel appId in the Core namespace:

kernel.setApp(kernel.CORE_NAMESPACE(), kernel.KERNEL_APP_ID(), newKernelBaseAddr);

AppProxies and upgradeability

In a similar fashion to the Kernel, apps can share implementation code to save gas on deployment. AppProxies rely on the Kernel for their upgradeability. Note that separate app instances in an organization are all linked to the same base contract in the Kernel, and so upgrading the base contract would effectively upgrade all of that app's instances.

Upgrading an app is done by setting a new base address for that app's appId in the Base namespace:

kernel.setApp(kernel.APP_BASES_NAMESPACE(), votingAppId, newVotingAppBaseAddr);

aragonOS provides two different types of proxies for apps:

  • AppProxyUpgradeable: an upgradeable proxy. In every call to the proxy it retrieves the current base contract address from the Kernel and forwards the call.

  • AppProxyPinned: a non-upgradeable proxy. On creation, it checks and saves the base contract address in the Kernel. This cannot be upgraded unless the base contract has explicit logic to change that storage slot.

Permissions

APP_MANAGER_ROLE is required any time the apps mapping is modified.

Warning

APP_MANAGER_ROLE can be used in malicious and dangerous ways. Protect this permission.

Events

SetApp is fired any time the app mapping changes:

SetPermission(address indexed from, address indexed to, bytes32 indexed role, bool allowed);

Interface

The Kernel implements the following interface:

interface IVaultRecoverable {
    function transferToVault(address token) external;

    function allowRecoverability(address token) external view returns (bool);
    function getRecoveryVault() external view returns (address);
}

contract IKernel is IVaultRecoverable {
    event SetApp(bytes32 indexed namespace, bytes32 indexed appId, address app);

    function acl() public view returns (IACL);
    function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool);

    function setApp(bytes32 namespace, bytes32 appId, address app) public;
    function getApp(bytes32 namespace, bytes32 appId) public view returns (address);
}

API documentation

ACL

A Permission is defined as the ability to perform actions (grouped by Roles) in a certain app instance (identified by its address).

We refer to a permission instance as an entity holding a certain permission. If it helps, you can think of a permission as a manifestation of an app's role that is held by an entity.

The ACL is built entirely as an AragonApp and can be upgraded in the same way as any other application installed into a Kernel. Unlike other apps, however, the ACL is installed and initialized upon the Kernel's own initialization due to its importance.

Managing permissions

First of all, you can obtain the default ACL instance for a Kernel with:

ACL acl = ACL(kernel.acl());

Then you can execute the following actions:

Create Permission

acl.createPermission(address entity, address app, bytes32 role, address manager);
  • entity: entity to hold the permission.

  • app: app whose role will be allowed through the permission

  • role: role to allow

  • manager: manager of the role's permission instances

Warning

createPermission() will fail if that role has pre-existing permission instances or a permission manager set.

Grants role in app for an entity and set manager as the manager of the role's permission instances.

This action is identical to grantPermission() except it allows the creation of the first permission instance of a role.

Note

Creating permissions is mandatory for apps to work. Any permission checks on non-existent permissions are failed automatically.

Grant Permission

acl.grantPermission(address entity, address app, bytes32 role);

Warning

Only callable by the manager of the role's permission instances.

Grants role in app for an entity. This entity would then be allowed to call all actions that their role can perform on that particular app until the permission manager revokes their role with revokePermission().

This action is identical to createPermission() except it can only be used by the permission manager of the role and does not set a new manager.

Note

The grantPermission() action doesn’t require protection with the ACL because only the permission manager of the role can make changes.

Revoke Permission

acl.revokePermission(address entity, address app, bytes32 role);

Warning

Only callable by the manager of the role's permission instances.

Revokes role in app for an entity.

Note

The revokePermission() action doesn’t require protection with the ACL because only the permission manager of the role can make changes.

Basic ACL example

  1. Deploy the Kernel and the ACL

  2. Executing kernel.initialize(acl, rootAddress), which in turns calls acl.initialize(rootAddress), creates the "permissions creator" permission under the hood createPermission(rootAddress, aclAddress, CREATE_PERMISSIONS_ROLE, rootAddress)

  3. Deploy the Voting app

  4. Grant the Voting app the ability to call createPermission(): grantPermission(votingAppAddress, aclAddress, CREATE_PERMISSIONS_ROLE) (must be executed by rootAddress)

  5. Deploy the Vault app, which has an action called transfer()

  6. Create a new vote via the Voting app to create the TRANSFER_ROLE permission: createPermission(votingAppAddress, vaultAppAddress, TRANSFER_ROLE, votingAppAddress)

  7. If the vote passes, the Voting app now has access to all actions in the Vault protected by TRANSFER_ROLE, which in this case is just transfer()

  8. Fund transfers from the Vault can now be controlled via votes from the Voting app. Each time a user wishes to transfer funds, they can create a new vote via the Voting app to propose an execution of the Vault's transfer() action. The transfer() action will be executed if and only if the vote passes.

Note that the Voting app is also able to revoke or regrant the TRANSFER_ROLE permission as it is that permission's manager of TRANSFER_ROLE on vaultAppAddress.

Permission managers

Getting a role's permission manager

acl.getPermissionManager(address app, bytes32 role)

Change a permission manager

acl.setPermissionManager(address newManager, address app, bytes32 role);

Only callable by the manager of the role's permission instances.

Changes the permission manager to newManager.

The new permission manager replaces the old permission manager resulting in the old manager losing any management power over that permission.

createPermission() executes a special case of this action to set the initial manager for the newly created permission. From that point forward, the manager can only be changed with setPermissionManager().

Parameter interpretation

When a permission is granted to an entity by the permission manager it can be assigned an array of parameters that will be evaluated every time the ACL is checked to see if the entity can perform the action.

Parameters allow the ACL to perform certain computations with the arguments of a permission in order to decide whether to allow the action or not. This moves the ACL from being a purely binary access list to a more sophisticated system that allows for fine-grained control.

An ACL parameter is comprised of a data structure with 3 values:

  • Argument Value (uint240): the value to compare against, depending on the argument. It is a regular Ethereum memory word that loses its two most significant bytes of precision. The reason for this was to allow parameters to be saved in just one storage slot, saving significant gas. Even though uint240s are used, it can be used to store any integer up to 2^30 - 1, addresses, and bytes32. In the case of comparing hashes, losing 2 bytes of precision shouldn't be a dealbreaker if the hash algorithm is secure.

  • Argument ID (uint8): Determines how the comparison value is fetched. From 0 to 200 it refers to the argument index number passed to the role. After 200, there are some special Argument IDs:

    • BLOCK_NUMBER_PARAM_ID (id = 200): sets comparison value to the block number at the time of execution. This allows for setting up timelocks depending on blocks.

    • TIMESTAMP_PARAM_ID (id = 201): sets comparison value to the timestamp of the current block at the time of execution. This allows for setting up timelocks on time.

    • id = 202: not currently in use.

    • ORACLE_PARAM_ID (id = 203): checks with an oracle at the address in the argument value and returns whether it returned true or false (no comparison with the argument value).

    • LOGIC_OP_PARAM_ID (id = 204): evaluates a logical operation and returns true or false depending on its result (no comparison with the argument value).

    • PARAM_VALUE_PARAM_ID (id = 205): return argument value. Commonly used with the RET operation to just return a value. If the value in the param is greater than 0 it will evaluate to true, otherwise it will return false.

  • Operation type (uint8): what operation should be done to compare the value fetched using the argument ID or the argument value. For all comparisons, both values are compared in the following order args[param.id] <param.op> param.value. Therefore, for a greater than operation, with param = {id: 0, op: Op.GT, value: 10}, it will interpret whether the argument 0 is greater than 10. The implemented operation types are:

    • None (Op.NONE): always evaluates to false regardless of parameter or arguments.

    • Equals (Op.EQ): evaluates to true if every byte matches between args[param.id] and param.value.

    • Not equals (Op.NEQ): evaluates to true if any byte doesn't match.

    • Greater than (Op.GT): evaluates to true if args[param.id] > param.value.

    • Less than (Op.LT): evaluates to true if args[param.id] < param.value.

    • Greater than or equal (Op.GTE): evaluates to true if args[param.id] >= param.value.

    • Less than or equal (Op.LTE): evaluates to true if args[param.id] <= param.value.

    • Return (Op.RET): evaluates to true if args[param.id] is greater than one. Used with PARAM_VALUE_PARAM_ID, it makes args[param.id] = param.value, so it returns the associated value of the parameter.

While also representing an operation, when the argument ID is LOGIC_OP_PARAM_ID only the Ops below are valid. These operations use the parameter's value to point to other parameter indices in the parameter array. Any values are encoded as uint32 numbers, each left-shifted 32 bits (for example, an Op that takes two inputs with a value of 0x00....0000000200000001 would have input 1, 1, and input 2, 2, referring to params at index 1 and 2). Here are the available logic Ops:

  • Not (Op.NOT): takes 1 parameter index and evaluates to the opposite of what the linked parameter evaluates to.

  • And (Op.AND): takes 2 parameter indices and evaluates to true if both evaluate to true.

  • Or (Op.OR): takes 2 parameter indices and evaluates to true if either evaluate to true.

  • Exclusive or (Op.XOR): takes 2 parameter indices and evaluates to true if only one of the parameters evaluate to true.

  • If else (Op.IF_ELSE): takes 3 parameters, evaluates the first parameter and if true, evaluates as the second parameter's evaluation, or as the third parameter's evaluation if false.

Parameter execution

When evaluating a rule the ACL will always evaluate the result of the first parameter. This first parameter can be an operation that links to other parameters and its evaluation depends on those parameters' evaluation. Execution is recursive and the result evaluated is always the result of the evaluation of the first parameter.

Examples of rules

function testComplexCombination() {
    // if (oracle and block number > block number - 1) then arg 0 < 10 or oracle else false
    Param[] memory params = new Param[](7);
    params[0] = Param(LOGIC_OP_PARAM_ID, uint8(Op.IF_ELSE), encodeIfElse(1, 4, 6));
    params[1] = Param(LOGIC_OP_PARAM_ID, uint8(Op.AND), encodeOperator(2, 3));
    params[2] = Param(ORACLE_PARAM_ID, uint8(Op.EQ), uint240(new AcceptOracle()));
    params[3] = Param(BLOCK_NUMBER_PARAM_ID, uint8(Op.GT), uint240(block.number - 1));
    params[4] = Param(LOGIC_OP_PARAM_ID, uint8(Op.OR), encodeOperator(5, 2));
    params[5] = Param(0, uint8(Op.LT), uint240(10));
    params[6] = Param(PARAM_VALUE_PARAM_ID, uint8(Op.RET), 0);

    assertEval(params, arr(uint256(10)), true);

    params[4] = Param(LOGIC_OP_PARAM_ID, uint8(Op.AND), encodeOperator(5, 2));
    assertEval(params, arr(uint256(10)), false);
}

When assigned to a permission, this rule will evaluate to true (and therefore allow the action) only on the following conditions:

  • If an oracle accepts it, and

  • The block number is greater than the previous block number, and

  • Either the oracle allows it (again! testing redundancy too) or the first parameter of the rule is lower than 10.

The possibilities for customizing an organization or protocol's governance model are truly endless and there is no need to write any actual Solidity.

Permissions

CREATE_PERMISSION_ROLE protects createPermission().

Warning

CREATE_PERMISSION_ROLE could be used in malicious and dangerous ways. This is initially assigned when the Kernel is first initialized. Protect this permission.

Events

createPermission(), grantPermission(), and revokePermission() all fire the same SetPermission event that Aragon clients are expected to cache and process into a locally stored version of the ACL:

SetPermission(address indexed from, address indexed to, bytes32 indexed role, bool allowed);
ChangePermissionManager(address indexed app, bytes32 indexed role, address indexed manager)

API documentation

AragonApp

AragonApp is the base class for all aragonOS applications. It exposes a light layer of functionality to supplement an application's business logic and sets up the required storage to connect to a Kernel.

Note

Security recommendations and sane defaults

While it is ultimately up to you to understand the concepts and sufficiently protect your business logic from flaws, AragonApp attempts to provide sane and secure defaults out of the box so you don't have to worry about potential security breaches from misconfiguration.

Authentication

Adding roles

Declaring roles is simple and usually done as public bytes32 declarations at the start of the contract file. By convention, the standard name for a role identifier is the keccak256 hash of its name as other tooling in the stack expects this to be the case:

bytes32 public CUSTOM_ACTION_ROLE = keccak256("CUSTOM_ACTION_ROLE");

Protecting functionality

Protecting an action behind the ACL is done in the smart contract by simply adding the authentication modifiers auth() or authP() to the action. On executing the action, the auth() or authP() modifier checks with the Kernel whether the entity performing the call holds the required role or not.

auth(bytes32 role) is capable of defining a binary permission—either yes or no:

function customAction() auth(CUSTOM_ACTION_ROLE) {
}
bytes32 public TRANSFER_ROLE = keccak256("TRANSFER_ROLE");
function transfer(uint256 amount) authP(TRANSFER_ROLE, arr(amount)) {
}

authP()'s second argument is a uint256[], but aragonOS exposes a number of arr() syntactical sugar helpers by default from AragonApp to help construct this array when using different argument types.

Note

Both auth() and authP() check that the application instance is initialized before allowing the action. Trying to access a protected action in an uninitialized application will result in a revert.

Finally, AragonApp also exposes a public getter for checking if an entity can perform a certain action:

function canPerform(address sender, bytes32 role, uint256[] params) public view returns (bool);

Note

Apps have the choice of which actions to protect behind the ACL as some actions may make sense to be completely public. Any publicly exposed state-changing function should most likely be protected, however.

Lifecycle of an AragonApp call requiring the ACL

Application lifecycle guarantees

AragonApps can be in the lifecycle stages of uninitialized, initialized, or petrified. As an application contract is deployed it begins in the uninitialized state and can go to either the initialized or petrified state.

AragonApp base logic contracts are petrified upon their deployment. They can never be initialized and are considered frozen in an uninitialized state forever. This also means that, if properly developed, there is no way for these contracts to be selfdestructed.

The AppProxy contracts users deploy and link to the base logic contracts are expected to be initialized by their users and only made usable once this initialization is complete.

You can check for an application instance's lifecycle state using the following:

function hasInitialized() public view returns (bool);
function isPetrified() public view returns (bool);

Application capabilities

Fund recovery

By default, all AragonApps have a fund recovery mechanism enabled for all tokens and ETH to protect against the event of an accidental transfer of funds. This is partly motivated by a flaw in the ERC20 specification that does not allow contracts to prevent themselves from receiving tokens like they can with ETH.

All AragonApps expose an externally-accessible fund recovery mechanism:

function transferToVault(address token) external;

This capability is configurable through the overloadable hook:

function allowRecoverability(address token) public view returns (bool);

The default implementation of allowRecoverability() is just to return true for all tokens but your overload could choose to not allow certain tokens or even ETH.

Depositable proxies

AppProxies start off not being able to receive ETH through the native, gas-limited .send() and .transfer() methods. This can be explicitly enabled through the setDepositable() function when an app wants to allow itself (as the proxy instance) to receive ETH from other contracts:

function setDepositable(bool depositable) internal;

An example use case would be a fundraising application which would only enable its proxy instances to be capable of receiving ETH for the duration of a fundraiser.

EVMScripts

AragonApp exposes the following interface for running EVMScripts:

function runScript(bytes script, bytes input, address[] blacklist) internal isInitialized protectState returns (bytes);

And some getters for information about EVMScripts:

function getEVMScriptExecutor(bytes script) public view returns (IEVMScriptExecutor);
function getEVMScriptRegistry() public view returns (IEVMScriptRegistry);

Re-entrancy protection

AragonApp comes with a built-in re-entrancy guard, easily usable through the nonReentrant modifier:

function nonReentrantFunction() external nonReentrant {
}

It's use is recommended as a last resort, for cases where there are no better options for protecting against re-entrancy.

Most commonly, you may want to apply this modifier to functions that fulfil these requirements:

  • Externally available and is state changing

  • Invokable by non-trusted contracts or accounts

  • Not already protected by a role

  • There exist more than one of these functions

A contrived example of this is if your app allows creating a recurring token payment to another account (protected via a role), but only the recipient account can modify certain parameters (e.g. when to withdraw payments, what token to withdraw). If the withdraw and token selection functions are separately available, they may benefit from being nonReentrant

API documentation

Forwarders and EVMScripts

Forwarders are one of the most important concepts of aragonOS. Rather than hardcoding the notion of a vote into each separate app’s functionality and ACL one can instead use a generic Voting App, which implements the forwarding interface, to pass actions forward to other apps after successful votes. If the Voting App is set up to only allow a token’s holders to vote, that means any actions/calls being passed from it must have also been approved by the token’s holders.

Forwarding and transaction pathing

The forwarding interface also allows a frontend interface, like the Aragon client, to calculate "forwarding paths". If you wanted to perform an action but you don't have the required permissions, a client can think of alternative paths for execution. For example, you might be in the Vault app's interface wishing to perform a token transfer. If you only had the permission to create votes, the client would directly prompt you to create a vote rather than let you complete the transfer. The flow is illustrated in the following animation:

Vote forwarding scenario. (Please note that the governance model and characters are fictional.) 👇

EVMScripts

We have designed our own scripting format, known as EVMScripts, to encode complex actions into a bytes representation that can be stored and later executed by another entity. EVMScripts can be installed on a per-organization basis through a EVMScriptRegistry and aragonOS comes complete with the ability to install multiple script executors in an organization.

EVMScript executors are contracts that take a script and an input and return an output after execution.

EVMScript executors must implement the following interface:

interface IEVMScriptExecutor {
    function execScript(bytes script, bytes input, address[] blacklist) external returns (bytes);
    function executorType() external pure returns (bytes32);
}

Warning

EVMScript executors are called with a delegatecall and operate in the context of the calling app. This must be taken into consideration when developing your own executor as it could cause a security breach.

aragonOS provides the CallsScript executor as a simple way to concatenate multiple calls. It cancels the operation if any of the calls fail.

  • Input: None

  • Output: None.

  • Blacklist: Entire script reverts if a call to one of the addresses in the blacklist is performed.

Making an app a Forwarder

Apps can become Forwarders by simply implementing the following interface:

interface IForwarder {
    function isForwarder() external pure returns (bool);
    function canForward(address sender, bytes evmCallScript) public view returns (bool);
    function forward(bytes evmCallScript) public;
}

Warning

EVMScripts are very powerful and risk causing security breaches! For example, the Token Manager, which allows any token holder to forward actions, needs to have the token address in its blacklist as otherwise any token holder would effectively have control over the token in the same way that the Token Manager does!

API documentation

appId: used to identify what app is being set. It is the of the aragonPM repo (e.g. namehash('voting.aragonpm.eth')).

app: Address of a contract that can have a different meaning depending on the .

As such, we attribute the "installation" of an app instance to the creation of its first permission in the . If an app has no permissions set, it is technically impossible to use, if developed correctly, and is not considered installed.

You can create new instances through the following interfaces:

appId: used to identify what app to link the proxy to. It is the of the aragonPM repo (e.g. namehash('voting.aragonpm.eth')).

See

As an example, the following steps show a complete flow for user "Root" to create a new DAO with the basic permissions set so that a can manage the funds stored in a :

As we have seen in the when a permission is created a Permission Manager is set for that specific role. The permission manager is able to grant or revoke permission instances for that role.

The interpreter supports encoding complex rules in what would look almost like a programming language. For example, let’s look at the following :

fires the following event:

See .

We have outlined a number of recommended conventions to follow in the .

Applications inheriting from AragonApp are required to be initialized, connected to a Kernel, and used with an . By default, they are not meant to receive or hold funds and allow all tokens to be recovered through the fund recovery mechanism in case of an accidental token transfer.

To secure an application, it is critical to ensure that all externally-accessible, state-changing functionality is protected by . If the application is meant to receive, hold, or transfer funds, you will also have to carefully reason about the and how they affect your application (alongside the of course!) If the app is a you should also carefully understand the implications of allowing another application or entity to execute an action from your application's address.

authP(bytes32 role, uint256[] params) allows you to pass a number of parameters that can then be used in the for each permission. This allows you to define powerful permissions with highly granular controls based on the inputs of an action:

The pattern suffers from a particular weakness of the proxy contracts depending upon the survival of the base logic contracts. It is important to understand the lifecycles of these base and proxy contracts to ensure users' safety and to avoid incidents like the unfortunate .

For more information on the use cases for EVMScripts, see the following section.

See .

Script body: See for spec of the payload.

Examples of forwarders can be found in the . Both the and apps are forwarders.

See and .

🛠️
ENS namehash
ENS namehash
Kernel.
Voting app
Vault app
test case
ACL
aragonOS development guide
DelegateProxy
second Parity multisig wallet vulnerability
AragonApp
CallsScript source code
aragon-apps repo
Voting
Token Manager
IForwarder
EVMScriptRunner
Solidity
introduction
here
DelegateProxy
unstructured storage
Kernel
ACL
AragonApp
Forwarders and EVMScripts
namespace
ACL
app proxy
Basic ACL example,
setPermissionManager()
AppProxy
standard Ethereum security recommendations
authentication
fund recovery and depositable capabilities
forwarder or uses EVMScripts
ACL's parameterization
Forwarders and EVMScripts