Skip to content

The plugin model

The DAO is deliberately minimal: treasury, executor, permission database. Everything else is a plugin. Voting, multisig approval, membership, token management, spending policies, all of it is functionality installed onto a DAO rather than baked into it. OSx keeps the core tiny and immutable in spirit, and makes capability modular, versioned, and upgradeable.

A plugin is a smart contract associated with one DAO. It gates its own functions by authorizing them against that DAO (which resolves through the permission system), and it acts on the world by having the DAO execute actions on its behalf. A governance plugin, for instance, is just a contract that (a) decides when a proposal has passed and (b) holds EXECUTE_PERMISSION_ID on the DAO so it can enact the result.

Why a whole framework, not just "deploy a contract"

You could deploy a plugin contract and grant it permissions by hand. The framework exists because doing that safely, cleanly and repeatably, across many DAOs, many plugin authors, and many versions over time, is the hard part. It answers three questions the protocol can't leave to trust:

  • How is a plugin published and versioned? → the PluginRepo: one per plugin, storing every version as an immutable release.build tag pointing at a setup contract.
  • How does a DAO install a specific version, wiring exactly the right permissions, without handing anyone unchecked control? → the plugin setup contract (which declares what to deploy and which permissions to grant) plus the PluginSetupProcessor (which applies it under a two-step, governance-reviewable flow).
  • How does the ecosystem discover and trust DAOs and plugin repos? → the registries and their ENS names.

The rest of this section is those pieces. If you're building a plugin, the reading order is: this page → choosing a baseplugin setupPluginRepoPSP. If you're just installing plugins onto a DAO, PSP and DAOFactory are the pages you want.

What every plugin shares

All plugins implement IPlugin, which declares one thing they must answer, pluginType(), telling the framework how the plugin is deployed (see choosing a base):

solidity
enum PluginType { UUPS, Cloneable, Constructable }

You never implement IPlugin directly; you extend one of the three plugin base contracts, which give you the DAO reference, the auth modifier, ERC-165 support, and the execution helpers below.

How a plugin makes the DAO act

A plugin doesn't call arbitrary contracts directly; it routes actions through an executor, by default its own DAO. This routing is configurable per plugin via a TargetConfig:

solidity
enum Operation { Call, DelegateCall }
struct TargetConfig { address target; Operation operation; }

(This Operation enum, Call / DelegateCall, is unrelated to PermissionLib.Operation with Grant / Revoke / GrantWithCondition; they only share a name.)

  • Call (the normal case) forwards to target.execute(callId, actions, allowFailureMap), the DAO's execute. Actions run as the DAO.
  • DelegateCall runs the executor's loop in the plugin's own context, so the actions run as the plugin (msg.sender is the plugin and value comes from the plugin's balance), not as the DAO. This is the advanced path: instead of shipping its own executor, the plugin borrows the shared GlobalExecutor's loop. It's also how a plugin performs nested execution in its own context, which re-entering DAO.execute would otherwise reject with ReentrantCall.

The target defaults to its own DAO. A target of address(0) is a sentinel: getTargetConfig() resolves it at read time to TargetConfig(dao(), Call), so a plugin that configures nothing acts through its DAO with a plain call. This is why install data commonly passes target: address(0), you get the sensible default without naming the DAO's address (which you may not know yet at install time).

Older OSx hard-wired plugins to always execute on their dao(). TargetConfig generalizes that so a plugin can target a different executor, for instance to run nested execution that DAO.execute's reentrancy guard would otherwise block. Because redirecting where a plugin's actions land is consequential, setting it is gated by SET_TARGET_CONFIG_PERMISSION_ID.

Safety guard: you cannot set a TargetConfig that both points at a contract implementing IDAO and uses DelegateCall, delegatecalling into a DAO would run DAO code in the plugin's storage and brick it. The base contract rejects that combination (InvalidTargetConfig).

Keep in mind

  • Never DelegateCall into a DAO. A TargetConfig pointing at an IDAO with DelegateCall is rejected (InvalidTargetConfig), it would run DAO code in the plugin's storage and brick it.
  • A plugin can only act once granted EXECUTE_PERMISSION_ID on its DAO. A governance plugin that can't enact its results is almost always missing that grant.

See also