ERC-2767: Contract Ownership Governance
ERC-2767 standardises DAO-style ownership transfer on top of ERC-173. Where ERC-173 lets the current owner unilaterally hand off control with transferOwnership, ERC-2767 routes ownership changes through a governance contract whose vote tallies replace single-key authority. Used by:
- DAO-controlled token contracts — governance votes can rotate the treasury / minter / upgrader address.
- Multi-stakeholder protocols — investor + team + community multisigs collectively own contracts.
- Time-locked transfers — proposed ownership changes wait through a governance delay before execution.
The standard is interface-only: it defines what a governance contract exposes so any ERC-173-aware tool can recognise governance-mediated ownership without per-DAO integration.
Required Interface
interface IERC2767 {
/// Returns the address that has authority over the governed contract.
/// Tools call this instead of `owner()` when the governance contract
/// holds the ERC-173 owner role.
function executor() external view returns (address);
/// Returns the contract that hosts the governance logic.
function governance() external view returns (address);
/// Returns true if `account` is allowed to act as the executor for
/// the current proposal/state. Tools use this for UI gating.
function isAuthorisedExecutor(address account) external view returns (bool);
}The pattern: a single governance contract holds the ERC-173 owner role across all governed contracts; the governance contract internally runs proposals, vote tallies, time-locks, etc., and exposes executor() as the address allowed to execute the latest passed proposal.
Neo Equivalent: Governance Contract as Owner + Manifest Permission
The Neo port maps cleanly: a governance contract holds the Owner/Admin role of governed contracts; calls from the governance contract pass Runtime.CheckWitness(governance) because the governance contract is the calling script. NEP-22 contract updates (redeploys) gate on the same governance check.
[DisplayName("GovernedContract")]
[ContractPermission("*", "*")]
public class GovernedContract : SmartContract
{
private const byte Prefix_Governance = 0x01;
public static UInt160 GetOwner()
=> (UInt160)Storage.Get(Storage.CurrentContext, new byte[] { Prefix_Governance });
public static UInt160 Executor() // ERC-2767 canonical entry
=> (UInt160)Contract.Call(GetOwner(), "currentExecutor", CallFlags.ReadOnly);
[Safe]
public static UInt160 Governance() => GetOwner();
public static void AdminOp(/* args */)
{
// Permits direct call from the governance contract OR the executor
// address it currently delegates to.
var executor = Executor();
if (!Runtime.CheckWitness(GetOwner()) && !Runtime.CheckWitness(executor))
throw new Exception("ERC2767:NotAuthorised");
// ... admin operation ...
}
}| ERC-2767 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
executor() | Executor() returning the current allowed-executor account | May be the governance contract itself or a time-locked delegate |
governance() | Governance() returning the governance contract hash | The address that holds the owner role |
isAuthorisedExecutor(account) | IsAuthorisedExecutor(account) view | Bool predicate for UI gating |
| Governance contract holds ERC-173 owner | Governance contract is the stored Owner | NEP-22 update-permission ties to it |
| Time-lock + proposal flows | Application-specific governance impl | Same shape as Compound Governor / OZ Governor on Ethereum |
Composition
- ERC-173 — base ownership. ERC-2767 strictly extends.
- ERC-5313 — light ownership. A governance-owned contract typically exposes
getOwner(5313) so off-chain tools see the governance address; ERC-2767 addsexecutor/governance/isAuthorisedExecutorfor richer UI. - ERC-7656 — generalised contract-linked services. Governance services for a contract fit cleanly under the contract-linked-services umbrella.
- ERC-2535 — diamond proxy. Diamond cuts (adding/removing facets) typically require governance approval; ERC-2767 names the gate.
Migration Notes
For Solidity DAOs porting governance:
- Replace per-contract ad-hoc
onlyGovernancemodifiers with a singleRuntime.CheckWitness(GetOwner())against the stored governance address. - Implement
Executor()/Governance()/IsAuthorisedExecutor(...)on each governed contract for ERC-2767 compatibility. - Set the governance contract as the manifest's allowed
updatecaller via NEP-22. - The governance contract itself follows whatever proposal / voting logic you choose (see Compound Governor port, OZ Governor port).
The Neo idiom is delegate the witness, not the call: governance contract signs the witness as one of the transaction's signers, and the governed contract's CheckWitness(governance) succeeds. No need for an "execute on behalf of" indirection — governance signing IS the execution.
