Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Kill switch: Proof of concept #516

Closed
wants to merge 8 commits into from
61 changes: 42 additions & 19 deletions contracts/factory/DAOFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.4.24;
import "../kernel/IKernel.sol";
import "../kernel/Kernel.sol";
import "../kernel/KernelProxy.sol";
import "../kill_switch/kernel/KernelKillSwitch.sol";

import "../acl/IACL.sol";
import "../acl/ACL.sol";
Expand Down Expand Up @@ -46,32 +47,54 @@ contract DAOFactory {
dao.initialize(baseACL, _root);
} else {
dao.initialize(baseACL, this);
_setupNewDaoPermissions(_root, dao);
}

ACL acl = ACL(dao.acl());
bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE();
bytes32 appManagerRole = dao.APP_MANAGER_ROLE();
emit DeployDAO(address(dao));
return dao;
}

acl.grantPermission(regFactory, acl, permRole);
/**
* @notice Create a new DAO with `_root` set as the initial admin and `_issuesRegistry` as the source of truth for kill-switch purpose
* @param _root Address that will be granted control to setup DAO permissions
* @param _issuesRegistry Address of the registry of issues that will be used in case of critical situations by the kernel kill switch
* @return Newly created DAO
*/
function newDAOWithKillSwitch(address _root, IssuesRegistry _issuesRegistry) public returns (KernelKillSwitch) {
KernelKillSwitch dao = KernelKillSwitch(new KernelProxy(baseKernel));

acl.createPermission(regFactory, dao, appManagerRole, this);
if (address(regFactory) == address(0)) {
dao.initialize(_issuesRegistry, baseACL, _root);
} else {
dao.initialize(_issuesRegistry, baseACL, address(this));
_setupNewDaoPermissions(_root, Kernel(dao));
}

EVMScriptRegistry reg = regFactory.newEVMScriptRegistry(dao);
emit DeployEVMScriptRegistry(address(reg));
emit DeployDAO(address(dao));
return dao;
}

// Clean up permissions
// First, completely reset the APP_MANAGER_ROLE
acl.revokePermission(regFactory, dao, appManagerRole);
acl.removePermissionManager(dao, appManagerRole);
function _setupNewDaoPermissions(address _root, Kernel _dao) internal {
ACL acl = ACL(_dao.acl());
bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE();
bytes32 appManagerRole = _dao.APP_MANAGER_ROLE();

// Then, make root the only holder and manager of CREATE_PERMISSIONS_ROLE
acl.revokePermission(regFactory, acl, permRole);
acl.revokePermission(this, acl, permRole);
acl.grantPermission(_root, acl, permRole);
acl.setPermissionManager(_root, acl, permRole);
}
acl.grantPermission(regFactory, acl, permRole);

emit DeployDAO(address(dao));
acl.createPermission(regFactory, _dao, appManagerRole, this);

return dao;
EVMScriptRegistry reg = regFactory.newEVMScriptRegistry(_dao);
emit DeployEVMScriptRegistry(address(reg));

// Clean up permissions
// First, completely reset the APP_MANAGER_ROLE
acl.revokePermission(regFactory, _dao, appManagerRole);
acl.removePermissionManager(_dao, appManagerRole);

// Then, make root the only holder and manager of CREATE_PERMISSIONS_ROLE
acl.revokePermission(regFactory, acl, permRole);
acl.revokePermission(this, acl, permRole);
acl.grantPermission(_root, acl, permRole);
acl.setPermissionManager(_root, acl, permRole);
}
}
14 changes: 14 additions & 0 deletions contracts/kill_switch/app/AppBinaryKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pragma solidity 0.4.24;

import "./AppKillSwitch.sol";
import "../base/BinaryKillSwitch.sol";


contract AppBinaryKillSwitch is AppKillSwitch, BinaryKillSwitch {
function setContractAction(address _contract, ContractAction _action)
external
authP(SET_CONTRACT_ACTION_ROLE, arr(_baseApp(), msg.sender))
{
_setContractAction(_contract, _action);
}
}
15 changes: 15 additions & 0 deletions contracts/kill_switch/app/AppKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity 0.4.24;

import "../base/KillSwitch.sol";


contract AppKillSwitch is AragonApp, KillSwitch {
function initialize(IssuesRegistry _issuesRegistry) public onlyInit {
initialized();
_setIssuesRegistry(_issuesRegistry);
}

function _baseApp() internal view returns (address) {
return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, appId());
}
}
21 changes: 21 additions & 0 deletions contracts/kill_switch/app/AppSeveritiesKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pragma solidity 0.4.24;

import "./AppKillSwitch.sol";
import "../base/SeveritiesKillSwitch.sol";


contract AppSeveritiesKillSwitch is AppKillSwitch, SeveritiesKillSwitch {
function setContractAction(address _contract, ContractAction _action)
external
authP(SET_CONTRACT_ACTION_ROLE, arr(_baseApp(), msg.sender))
{
_setContractAction(_contract, _action);
}

function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity)
external
authP(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_baseApp(), msg.sender))
{
_setLowestAllowedSeverity(_contract, _severity);
}
}
26 changes: 26 additions & 0 deletions contracts/kill_switch/app/KillSwitchedApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
pragma solidity 0.4.24;

import "./AppKillSwitch.sol";
import "../../apps/AragonApp.sol";


contract KillSwitchedApp is AragonApp {
string private constant ERROR_CONTRACT_CALL_NOT_ALLOWED = "APP_CONTRACT_CALL_NOT_ALLOWED";

AppKillSwitch internal appKillSwitch;

modifier killSwitched {
bool _isCallAllowed = !appKillSwitch.shouldDenyCallingContract(_baseApp(), address(this), msg.sender, msg.data, msg.value);
require(_isCallAllowed, ERROR_CONTRACT_CALL_NOT_ALLOWED);
_;
}

function initialize(AppKillSwitch _appKillSwitch) public onlyInit {
initialized();
appKillSwitch = _appKillSwitch;
}

function _baseApp() internal view returns (address) {
return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, appId());
}
}
11 changes: 11 additions & 0 deletions contracts/kill_switch/base/BinaryKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity 0.4.24;

import "./KillSwitch.sol";
import "./IssuesRegistry.sol";


contract BinaryKillSwitch is KillSwitch {
function isSeverityIgnored(address /*_contract*/, IssuesRegistry.Severity _severity) public view returns (bool) {
return _severity == IssuesRegistry.Severity.None;
}
}
31 changes: 31 additions & 0 deletions contracts/kill_switch/base/IssuesRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
pragma solidity 0.4.24;

import "../../apps/AragonApp.sol";


contract IssuesRegistry is AragonApp {
bytes32 constant public SET_ENTRY_SEVERITY_ROLE = keccak256("SET_ENTRY_SEVERITY_ROLE");

enum Severity { None, Low, Mid, High, Critical }

mapping (address => Severity) internal issuesSeverity;

event SeveritySet(address indexed entry, Severity severity, address sender);

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

function isSeverityFor(address entry) public view isInitialized returns (bool) {
return issuesSeverity[entry] != Severity.None;
}

function getSeverityFor(address entry) public view isInitialized returns (Severity) {
return issuesSeverity[entry];
}

function setSeverityFor(address entry, Severity severity) public authP(SET_ENTRY_SEVERITY_ROLE, arr(entry, msg.sender)) {
issuesSeverity[entry] = severity;
emit SeveritySet(entry, severity, msg.sender);
}
}
84 changes: 84 additions & 0 deletions contracts/kill_switch/base/KillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
pragma solidity 0.4.24;

import "./IssuesRegistry.sol";


contract KillSwitch {
bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE");

enum ContractAction { Check, Ignore, Deny }

IssuesRegistry public issuesRegistry;
mapping (address => ContractAction) internal contractActions;

event IssuesRegistrySet(address issuesRegistry, address sender);
event ContractActionSet(address contractAddress, ContractAction action);

function setContractAction(address _contract, ContractAction _action) external;

function getContractAction(address _contract) public view returns (ContractAction) {
return contractActions[_contract];
}

function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool);

function isContractIgnored(address _contract) public view returns (bool) {
return getContractAction(_contract) == ContractAction.Ignore;
}

function isContractDenied(address _contract) public view returns (bool) {
return getContractAction(_contract) == ContractAction.Deny;
}

function shouldDenyCallingContract(address _base, address _instance, address _sender, bytes _data, uint256 _value) public returns (bool) {
// if the call should not be evaluated, then allow given call
if (!_shouldEvaluateCall(_base, _instance, _sender, _data, _value)) {
return false;
}

// if the call should be denied, then deny given call
if (isContractDenied(_base)) {
return true;
}

// if the contract issues are ignored, then allow given call
if (isContractIgnored(_base)) {
return false;
}

// if the issues registry has not been set, then allow given call
if (issuesRegistry == address(0)) {
return false;
}

// if the contract severity found is ignored, then allow given call
IssuesRegistry.Severity _severityFound = issuesRegistry.getSeverityFor(_base);
if (isSeverityIgnored(_base, _severityFound)) {
return false;
}

// if none of the conditions above were met, then deny given call
return true;
}

/**
* @dev This function allows different kill-switch implementations to provide a custom logic to tell whether a
* certain call should be denied or not. This is important to ensure recoverability. For example, custom
* implementations could override this function to provide a decision based on the msg.sender, timestamp,
* block information, among many other options.
* @return Always true by default.
*/
function _shouldEvaluateCall(address, address, address, bytes, uint256) internal returns (bool) {
return true;
}

function _setIssuesRegistry(IssuesRegistry _issuesRegistry) internal {
issuesRegistry = _issuesRegistry;
emit IssuesRegistrySet(_issuesRegistry, msg.sender);
}

function _setContractAction(address _contract, ContractAction _action) internal {
contractActions[_contract] = _action;
emit ContractActionSet(_contract, _action);
}
}
25 changes: 25 additions & 0 deletions contracts/kill_switch/base/SeveritiesKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pragma solidity 0.4.24;

import "./KillSwitch.sol";
import "./IssuesRegistry.sol";


contract SeveritiesKillSwitch is KillSwitch {
bytes32 constant public SET_LOWEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_LOWEST_ALLOWED_SEVERITY_ROLE");

mapping (address => IssuesRegistry.Severity) internal lowestAllowedSeverityByContract;

event LowestAllowedSeveritySet(address indexed _contract, IssuesRegistry.Severity severity);

function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) external;

function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool) {
IssuesRegistry.Severity lowestAllowedSeverity = lowestAllowedSeverityByContract[_contract];
return lowestAllowedSeverity >= _severity;
}

function _setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) internal {
lowestAllowedSeverityByContract[_contract] = _severity;
emit LowestAllowedSeveritySet(_contract, _severity);
}
}
18 changes: 18 additions & 0 deletions contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity 0.4.24;

import "./KernelKillSwitch.sol";
import "../base/BinaryKillSwitch.sol";


contract KernelBinaryKillSwitch is KernelKillSwitch, BinaryKillSwitch {
constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {
// solium-disable-previous-line no-empty-blocks
}

function setContractAction(address _contract, ContractAction _action)
external
auth(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender))
{
_setContractAction(_contract, _action);
}
}
40 changes: 40 additions & 0 deletions contracts/kill_switch/kernel/KernelKillSwitch.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pragma solidity 0.4.24;

import "../base/KillSwitch.sol";
import "../../kernel/Kernel.sol";


contract KernelKillSwitch is Kernel, KillSwitch {
string private constant ERROR_CONTRACT_CALL_NOT_ALLOWED = "KERNEL_CONTRACT_CALL_NOT_ALLOWED";

function initialize(IssuesRegistry _issuesRegistry, IACL _baseAcl, address _permissionsCreator) public onlyInit {
_setIssuesRegistry(_issuesRegistry);
Kernel.initialize(_baseAcl, _permissionsCreator);
}

function getApp(bytes32 _namespace, bytes32 _appId) public view returns (address) {
// TODO: The tx information that the kill switch should eval cannot be accessed from here.
// Note that `msg.sender` is the proxy requesting the base app address, and `msg.data`
// refers to this call (`Kernel#getApp(bytes32,bytes32)`)

address _app = super.getApp(_namespace, _appId);
bool _isCallAllowed = !shouldDenyCallingContract(_app, msg.sender, address(0), new bytes(0), uint256(0));
require(_isCallAllowed, ERROR_CONTRACT_CALL_NOT_ALLOWED);
return _app;
}

function _shouldEvaluateCall(address _base, address _instance, address _sender, bytes _data, uint256 _value) internal returns (bool) {
/****************************************** IMPORTANT *********************************************/
/* Due to how proxies work, every time we call a proxied app, we will ask the kernel what's the */
/* address of the base implementation where it should delegate the call to. But since the kernel */
/* is also a proxy, it will basically delegate that query to the base kernel implementation, and */
/* that's when this context is evaluated. Thus, we don't have full context of the call that its */
/* about to be delegated to the base app implementation, the msg.data corresponds to the Kernel */
/* getApp(bytes32,bytes32) method for example. Therefore, handling specific scenarios here it's */
/* really cumbersome. We could rely easily on timestamps or block information, but tx data does */
/* not correspond to the application call in this context. */
/**************************************************************************************************/

return super._shouldEvaluateCall(_base, _instance, _sender, _data, _value);
}
}
Loading