Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
383dd79
contracts: base kill switch implementation
facuspagnuolo Apr 24, 2019
9d7a118
contracts: binary kill switch implementation
facuspagnuolo Apr 24, 2019
c3f78fd
contracts: severities kill switch implementation
facuspagnuolo Apr 24, 2019
4d9ce01
contracts: application level kill switch
facuspagnuolo Apr 24, 2019
51127c4
contracts: kernel level kill switch
facuspagnuolo Apr 24, 2019
b75fdca
tests: add kill switch unit tests
facuspagnuolo Apr 29, 2019
4f4421e
contracts: use ternary ignoration for kill-switch settings
facuspagnuolo Apr 29, 2019
00b76b9
contracts: fix linting issues
facuspagnuolo Apr 29, 2019
df3568f
kill-switch: cleanup models leaving only app level
facuspagnuolo Apr 30, 2019
b6c5531
kill-switch: use `highest` instead of `lowest` allowed severities
facuspagnuolo Apr 30, 2019
c20c608
kill-switch: Support one issues registry per contract
facuspagnuolo Apr 30, 2019
1f17dcb
kill-switch: drop custom programmatic handling
facuspagnuolo Apr 30, 2019
130c343
kill-switch: integrate with aragonOS components
facuspagnuolo May 1, 2019
3c9eb50
kill-switch: place mocks and test files in corresponding dirs
facuspagnuolo May 2, 2019
ffff170
kill-switch: minor tweaks based on @bingen's feedback
facuspagnuolo May 3, 2019
76f8521
kill-switch: skip gas costs test for coverage
facuspagnuolo May 3, 2019
2db3e46
kill-switch: rename 'ignore' action to 'allow'
facuspagnuolo May 3, 2019
d28cfee
kill-switch: rename issues registry `entry` by `implementation`
facuspagnuolo May 7, 2019
dba7e42
kill-switch: rename test files to follow naming convention
facuspagnuolo May 7, 2019
60268b2
kill-switch: improve authentication params
facuspagnuolo May 7, 2019
8f91fec
kill-switch: fix app id constant value
facuspagnuolo May 7, 2019
1ce842d
kill-switch: improve gas costs and support current kernel versions
facuspagnuolo May 7, 2019
a13d491
kill-switch: move kernel initialization logic to DAO factory
facuspagnuolo May 7, 2019
77b8b2e
kill-switch: rename issues registry `isSeverityFor` to `hasSeverity`
facuspagnuolo May 7, 2019
d499267
kill-switch: rename `killSwitched` modifier to `killSwitchProtected`
facuspagnuolo May 7, 2019
17922cc
kill-switch: improve settings to customize different scenarios
facuspagnuolo May 8, 2019
0f288ce
kill-switch: add `DAOFactory` tests
facuspagnuolo May 8, 2019
a5f6bfe
kill-switch: test non compliant kernel versions
facuspagnuolo May 8, 2019
7f73ac3
kill-switch: allow core instances by default
facuspagnuolo May 8, 2019
5230a86
kill-switch: optimize tests
facuspagnuolo May 8, 2019
fd6348c
kill-switch: parameterize instance being called in kernel
facuspagnuolo May 9, 2019
6ab5f69
Merge branch 'dev' of github.com:aragon/aragonOS into application_kil…
facuspagnuolo May 9, 2019
ae617f6
kill-switch: apply suggestions from @izqui
facuspagnuolo May 17, 2019
ce7a29b
kill-switch: extract `killSwitchProtected` modifier to function
facuspagnuolo May 17, 2019
08f1a8b
kill-switch: address feedback from @sohkai
facuspagnuolo May 20, 2019
5817661
kill-switch: small tweaks and optimizations
facuspagnuolo May 21, 2019
3113233
kill-switch: split unit and integration tests
facuspagnuolo May 21, 2019
c057f0d
kill-switch: rename mocks dir and improve inline doc
facuspagnuolo Jun 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions contracts/acl/ACLSyntaxSugar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,42 @@ contract ACLSyntaxSugar {
return arr(uint256(_a), uint256(_b));
}

function arr(bytes32 _a, address _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), uint256(_b));
}

function arr(bytes32 _a, address _b, address _c) internal pure returns (uint256[] r) {
return arr(uint256(_a), uint256(_b), uint256(_c));
}

function arr(bytes32 _a, uint256 _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), _b);
}

function arr(bytes32 _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) {
return arr(uint256(_a), _b, _c);
}

function arr(address _a) internal pure returns (uint256[] r) {
return arr(uint256(_a));
}

function arr(address _a, bool _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), _toUint(_b));
}

function arr(address _a, bool _b, bool _c) internal pure returns (uint256[] r) {
return arr(uint256(_a), _toUint(_b), _toUint(_c));
}

function arr(address _a, address _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), uint256(_b));
}

function arr(address _a, uint256 _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), _b);
}

function arr(address _a, uint256 _b, uint256 _c) internal pure returns (uint256[] r) {
return arr(uint256(_a), _b, _c);
}
Expand All @@ -34,10 +62,6 @@ contract ACLSyntaxSugar {
return arr(uint256(_a), _b, _c, _d);
}

function arr(address _a, uint256 _b) internal pure returns (uint256[] r) {
return arr(uint256(_a), uint256(_b));
}

function arr(address _a, address _b, uint256 _c, uint256 _d, uint256 _e) internal pure returns (uint256[] r) {
return arr(uint256(_a), uint256(_b), _c, _d, _e);
}
Expand Down Expand Up @@ -84,6 +108,10 @@ contract ACLSyntaxSugar {
r[3] = _d;
r[4] = _e;
}

function _toUint(bool _a) private pure returns (uint256) {
return _a ? uint256(1) : uint256(0);
}
}


Expand Down
29 changes: 16 additions & 13 deletions contracts/apps/AragonApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import "../evmscript/EVMScriptRunner.sol";
// are included so that they are automatically usable by subclassing contracts
contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGuard, EVMScriptRunner, ACLSyntaxSugar {
string private constant ERROR_AUTH_FAILED = "APP_AUTH_FAILED";
string private constant ERROR_CONTRACT_CALL_NOT_ALLOWED = "APP_CONTRACT_CALL_NOT_ALLOWED";
string private constant ERROR_UNEXPECTED_KERNEL_RESPONSE = "APP_UNEXPECTED_KERNEL_RESPONSE";

modifier auth(bytes32 _role) {
Expand All @@ -33,11 +32,6 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua
_;
}

modifier killSwitchProtected {
require(canExecuteCall(), ERROR_CONTRACT_CALL_NOT_ALLOWED);
_;
}

/**
* @dev Check whether an action can be performed by a sender for a particular role on this app
* @param _sender Sender of the call
Expand All @@ -47,7 +41,7 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua
* Always returns false if the app hasn't been initialized yet.
*/
function canPerform(address _sender, bytes32 _role, uint256[] _params) public view returns (bool) {
if (!hasInitialized()) {
if (!isCallEnabled()) {
return false;
}

Expand All @@ -68,15 +62,24 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua
* @dev Check whether a call to the current app can be executed or not based on the kill-switch settings
* @return Boolean indicating whether the call could be executed or not
*/
function canExecuteCall() public view returns (bool) {
function isCallEnabled() public view returns (bool) {
if (!hasInitialized()) {
return false;
}

IKernel _kernel = kernel();
bytes4 selector = _kernel.shouldDenyCallingContract.selector;
bytes memory callData = abi.encodeWithSelector(selector, appId(), address(this));
bool success = address(_kernel).call(callData);
bytes4 selector = _kernel.isAppDisabled.selector;
bytes memory isAppDisabledCalldata = abi.encodeWithSelector(selector, appId(), address(this));
bool success;
assembly {
success := staticcall(gas, _kernel, add(isAppDisabledCalldata, 0x20), mload(isAppDisabledCalldata), 0, 0)
}

// if the call to `kernel.shouldDenyCallingApp` reverts (using an old Kernel) we consider that
// If the call to `kernel.isAppDisabled()` reverts (using an old or non-existent Kernel) we consider that
// there is no kill switch and the call can be executed be allowed to continue
if (!success) return true;
if (!success) {
return true;
}

// if not, first ensure the returned value is 32 bytes length
uint256 _outputLength;
Expand Down
146 changes: 70 additions & 76 deletions contracts/factory/DAOFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import "../acl/ACL.sol";
import "../kernel/IKernel.sol";
import "../kernel/Kernel.sol";
import "../kernel/KernelProxy.sol";
import "../kill_switch/KillSwitch.sol";
import "../kill_switch/IssuesRegistry.sol";
import "../kill-switch/KillSwitch.sol";
import "../kill-switch/IssuesRegistry.sol";
import "./EVMScriptRegistryFactory.sol";


Expand All @@ -16,15 +16,17 @@ contract DAOFactory {
IKernel public baseKernel;
IACL public baseACL;
KillSwitch public baseKillSwitch;
EVMScriptRegistryFactory public scriptsRegistryFactory;
EVMScriptRegistryFactory public regFactory;

event DeployDAO(address dao);
event DeployKillSwitch(address killSwitch);
event DeployEVMScriptRegistry(address registry);

/**
* @notice Create a new DAOFactory, creating DAOs with Kernels proxied to `_baseKernel`, ACLs proxied to `_baseACL`, and new EVMScriptRegistries created from `_scriptsRegistryFactory`.
* @param _baseKernel Base Kernel
* @param _baseACL Base ACL
* @param _baseKillSwitch Base KillSwitch
* @param _scriptsRegistryFactory EVMScriptRegistry factory
*/
constructor(
Expand All @@ -37,7 +39,7 @@ contract DAOFactory {
{
// No need to init as it cannot be killed by devops199
if (address(_scriptsRegistryFactory) != address(0)) {
scriptsRegistryFactory = _scriptsRegistryFactory;
regFactory = _scriptsRegistryFactory;
}
if (address(_baseKillSwitch) != address(0)) {
baseKillSwitch = _baseKillSwitch;
Expand All @@ -53,35 +55,69 @@ contract DAOFactory {
* @return Newly created DAO
*/
function newDAO(address _root) public returns (Kernel) {
if (address(scriptsRegistryFactory) == address(0)) {
if (address(regFactory) == address(0)) {
return _createDAO(_root);
}

Kernel dao = _createDAO(address(this));
ACL acl = ACL(dao.acl());
_setupEVMScriptRegistry(dao, acl, _root);

// load roles
bytes32 appManagerRole = dao.APP_MANAGER_ROLE();
bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE();

// grant app manager permissions to factory and deploy EVM scripts registry
acl.createPermission(regFactory, dao, appManagerRole, address(this));
_createEVMScriptRegistry(dao, acl, createPermissionsRole);

// roll back app manager permissions
acl.revokePermission(regFactory, dao, appManagerRole);
acl.removePermissionManager(dao, appManagerRole);

// transfer create permissions roles to root address
acl.revokePermission(address(this), acl, createPermissionsRole);
acl.grantPermission(_root, acl, createPermissionsRole);
acl.setPermissionManager(_root, acl, createPermissionsRole);

return dao;
}

/**
* @notice Create a new DAO with `_root` set as the initial admin and `_issuesRegistry` as the source of truth for kill-switch purpose
* @notice Create a new DAO with `_root` set as the initial admin and `_issuesRegistry` as the source of truth for kill-switch purposes
* @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 kill switch
* @param _issuesRegistry Address of the registry of issues that will be used to detect critical situations by the kill switch
* @return Newly created DAO
*/
function newDAOWithKillSwitch(address _root, IssuesRegistry _issuesRegistry) public returns (Kernel) {
require(address(baseKillSwitch) != address(0), ERROR_MISSING_BASE_KILL_SWITCH);

Kernel dao = _createDAO(address(this));
ACL acl = ACL(dao.acl());
_createKillSwitch(dao, acl, _issuesRegistry);

if (address(scriptsRegistryFactory) == address(0)) {
_transferCreatePermissionsRole(dao, acl, address(this), _root);
} else {
_setupEVMScriptRegistry(dao, acl, _root);
// load roles
bytes32 appManagerRole = dao.APP_MANAGER_ROLE();
bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE();

// grant app manager permissions to this and deploy kill switch
acl.createPermission(address(this), dao, appManagerRole, address(this));
_createKillSwitch(dao, acl, _issuesRegistry, appManagerRole);

// deploy EVM scripts registry if required
if (address(regFactory) != address(0)) {
acl.grantPermission(regFactory, dao, appManagerRole);
_createEVMScriptRegistry(dao, acl, createPermissionsRole);
acl.revokePermission(regFactory, dao, appManagerRole);
}

// roll back app manager permissions
acl.revokePermission(address(this), dao, appManagerRole);
acl.removePermissionManager(dao, appManagerRole);

// transfer create permissions roles to root address
acl.revokePermission(address(this), acl, createPermissionsRole);
acl.grantPermission(_root, acl, createPermissionsRole);
acl.setPermissionManager(_root, acl, createPermissionsRole);

return dao;
}

Expand All @@ -92,75 +128,33 @@ contract DAOFactory {
return dao;
}

function _createKillSwitch(Kernel _dao, ACL _acl, IssuesRegistry _issuesRegistry) internal {
// create app manager role for this
_createAppManagerRole(_dao, _acl, address(this));

// create kill switch instance and set it as default
bytes32 killSwitchAppID = _dao.DEFAULT_KILL_SWITCH_APP_ID();
bytes memory initializeData = abi.encodeWithSelector(baseKillSwitch.initialize.selector, _issuesRegistry);
_dao.newAppInstance(killSwitchAppID, baseKillSwitch, initializeData, true);
_allowKillSwitchCoreInstances(_dao, _acl);

// remove app manager role permissions from this
_removeAppManagerRole(_dao, _acl, address(this));
}

function _allowKillSwitchCoreInstances(Kernel _dao, ACL _acl) internal {
KillSwitch killSwitch = KillSwitch(_dao.killSwitch());

// create allow instances role for this
bytes32 setAllowedInstancesRole = killSwitch.SET_ALLOWED_INSTANCES_ROLE();
_acl.createPermission(address(this), killSwitch, setAllowedInstancesRole, address(this));

// allow calls to core instances: kill switch, acl and kernel
killSwitch.setAllowedInstance(address(_dao), true);
killSwitch.setAllowedInstance(address(_acl), true);
killSwitch.setAllowedInstance(address(killSwitch), true);

// remove allow instances role from this
_acl.revokePermission(address(this), killSwitch, setAllowedInstancesRole);
_acl.removePermissionManager(killSwitch, setAllowedInstancesRole);
}

function _setupEVMScriptRegistry(Kernel _dao, ACL _acl, address _root) internal {
// grant permissions to script registry factory
_grantCreatePermissionsRole(_dao, _acl, scriptsRegistryFactory);
_createAppManagerRole(_dao, _acl, scriptsRegistryFactory);

// create evm scripts registry
EVMScriptRegistry scriptsRegistry = scriptsRegistryFactory.newEVMScriptRegistry(_dao);
function _createEVMScriptRegistry(Kernel _dao, ACL _acl, bytes32 _createPermissionsRole) internal {
_acl.grantPermission(regFactory, _acl, _createPermissionsRole);
EVMScriptRegistry scriptsRegistry = regFactory.newEVMScriptRegistry(_dao);
emit DeployEVMScriptRegistry(address(scriptsRegistry));

// remove permissions from scripts registry factory and transfer to root address
_removeAppManagerRole(_dao, _acl, scriptsRegistryFactory);
_transferCreatePermissionsRole(_dao, _acl, scriptsRegistryFactory, _root);
}

function _grantCreatePermissionsRole(Kernel _dao, ACL _acl, address _to) internal {
bytes32 createPermissionsRole = _acl.CREATE_PERMISSIONS_ROLE();
_acl.grantPermission(_to, _acl, createPermissionsRole);
_acl.revokePermission(regFactory, _acl, _createPermissionsRole);
}

function _createAppManagerRole(Kernel _dao, ACL _acl, address _to) internal {
bytes32 appManagerRole = _dao.APP_MANAGER_ROLE();
_acl.createPermission(_to, _dao, appManagerRole, address(this));
function _createKillSwitch(Kernel _dao, ACL _acl, IssuesRegistry _issuesRegistry, bytes32 _appManagerRole) internal {
bytes32 killSwitchAppID = _dao.DEFAULT_KILL_SWITCH_APP_ID();
bytes memory initializeData = abi.encodeWithSelector(baseKillSwitch.initialize.selector, _issuesRegistry);
KillSwitch killSwitch = KillSwitch(_dao.newAppInstance(killSwitchAppID, baseKillSwitch, initializeData, true));
_allowKillSwitchCoreInstances(_dao, _acl, killSwitch);
emit DeployKillSwitch(address(killSwitch));
}

function _removeAppManagerRole(Kernel _dao, ACL _acl, address _from) internal {
bytes32 appManagerRole = _dao.APP_MANAGER_ROLE();
_acl.revokePermission(_from, _dao, appManagerRole);
_acl.removePermissionManager(_dao, appManagerRole);
}
function _allowKillSwitchCoreInstances(Kernel _dao, ACL _acl, KillSwitch _killSwitch) internal {
// create change whitelisted instances role for this
bytes32 changeWhitelistedInstancesRole = _killSwitch.CHANGE_WHITELISTED_INSTANCES_ROLE();
_acl.createPermission(address(this), _killSwitch, changeWhitelistedInstancesRole, address(this));

function _transferCreatePermissionsRole(Kernel _dao, ACL _acl, address _from, address _to) internal {
bytes32 createPermissionsRole = _acl.CREATE_PERMISSIONS_ROLE();
_acl.revokePermission(_from, _acl, createPermissionsRole);
if (_from != address(this)) {
_acl.revokePermission(address(this), _acl, createPermissionsRole);
}
// whitelist core instances: kill switch, acl and kernel
_killSwitch.setWhitelistedInstance(address(_dao), true);
_killSwitch.setWhitelistedInstance(address(_acl), true);
_killSwitch.setWhitelistedInstance(address(_killSwitch), true);

_acl.grantPermission(_to, _acl, createPermissionsRole);
_acl.setPermissionManager(_to, _acl, createPermissionsRole);
// revoke and remove change whitelisted instances role from this
_acl.revokePermission(address(this), _killSwitch, changeWhitelistedInstancesRole);
_acl.removePermissionManager(_killSwitch, changeWhitelistedInstancesRole);
}
}
2 changes: 1 addition & 1 deletion contracts/kernel/IKernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ contract IKernel is IKernelEvents, IVaultRecoverable {

function setApp(bytes32 namespace, bytes32 appId, address app) public;
function getApp(bytes32 namespace, bytes32 appId) public view returns (address);
function shouldDenyCallingContract(bytes32 appId, address _instance) public returns (bool);
function isAppDisabled(bytes32 appId, address _instance) public view returns (bool);
}
14 changes: 8 additions & 6 deletions contracts/kernel/Kernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "../common/IsContract.sol";
import "../common/Petrifiable.sol";
import "../common/VaultRecoverable.sol";
import "../factory/AppProxyFactory.sol";
import "../kill_switch/IKillSwitch.sol";
import "../kill-switch/IKillSwitch.sol";
import "../lib/misc/ERCProxy.sol";


Expand Down Expand Up @@ -164,11 +164,13 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant
}

/**
* @dev Tells whether a call to an instance of an app should be denied or not based on the kill-switch settings
* @dev Tells whether a call to an instance of an app should be denied or not based on the kill-switch settings.
* Note that we don't need to perform an initialization check since having or not a kill-switch installed
* implicitly means that.
* @param _appId Identifier for app to be checked
* @return True is the given call should be denied, false otherwise
* @return True if the given call should be denied, false otherwise
*/
function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) {
function isAppDisabled(bytes32 _appId, address _instance) public view returns (bool) {
IKillSwitch _killSwitch = killSwitch();
if (address(_killSwitch) == address(0)) {
return false;
Expand Down Expand Up @@ -207,15 +209,15 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant
}

/**
* @dev Get the installed ACL app
* @dev Get the default ACL app
* @return ACL app
*/
function acl() public view returns (IACL) {
return IACL(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID));
}

/**
* @dev Get the installed KillSwitch app
* @dev Get the default KillSwitch app
* @return KillSwitch app
*/
function killSwitch() public view returns (IKillSwitch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.4.24;
contract IIssuesRegistry {
enum Severity { None, Low, Mid, High, Critical }

event SeveritySet(address indexed implementation, Severity severity, address indexed sender);
event ChangeSeverity(address indexed implementation, Severity severity, address indexed sender);

function setSeverityFor(address implementation, Severity severity) external;

Expand Down
Loading