From 383dd794da9a3ea8468bf7e252deeed5d68b6c61 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 24 Apr 2019 11:10:37 -0300 Subject: [PATCH 01/37] contracts: base kill switch implementation --- contracts/kill_switch/base/IssuesRegistry.sol | 31 ++++++++++++ contracts/kill_switch/base/KillSwitch.sol | 48 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 contracts/kill_switch/base/IssuesRegistry.sol create mode 100644 contracts/kill_switch/base/KillSwitch.sol diff --git a/contracts/kill_switch/base/IssuesRegistry.sol b/contracts/kill_switch/base/IssuesRegistry.sol new file mode 100644 index 000000000..1cad627dd --- /dev/null +++ b/contracts/kill_switch/base/IssuesRegistry.sol @@ -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) authP(SET_ENTRY_SEVERITY_ROLE, arr(entry, msg.sender)) public { + issuesSeverity[entry] = severity; + emit SeveritySet(entry, severity, msg.sender); + } +} diff --git a/contracts/kill_switch/base/KillSwitch.sol b/contracts/kill_switch/base/KillSwitch.sol new file mode 100644 index 000000000..3aea7d9ec --- /dev/null +++ b/contracts/kill_switch/base/KillSwitch.sol @@ -0,0 +1,48 @@ +pragma solidity 0.4.24; + +import "./IssuesRegistry.sol"; + + +contract KillSwitch { + IssuesRegistry public issuesRegistry; + + event IssuesRegistrySet(address issuesRegistry, address sender); + + function isContractIgnored(address _contract) public view returns (bool); + + function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool); + + 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 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 /*_base*/, address /*_instance*/, address /*_sender*/, bytes /*_data*/, uint256 /*_value*/) internal returns (bool) { + return true; + } + + function _setIssuesRegistry(IssuesRegistry _issuesRegistry) internal { + issuesRegistry = _issuesRegistry; + emit IssuesRegistrySet(_issuesRegistry, msg.sender); + } +} From 9d7a1186de6936dab785674848477c16823cb6d4 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 24 Apr 2019 11:10:55 -0300 Subject: [PATCH 02/37] contracts: binary kill switch implementation --- .../kill_switch/base/BinaryKillSwitch.sol | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 contracts/kill_switch/base/BinaryKillSwitch.sol diff --git a/contracts/kill_switch/base/BinaryKillSwitch.sol b/contracts/kill_switch/base/BinaryKillSwitch.sol new file mode 100644 index 000000000..1f5623c94 --- /dev/null +++ b/contracts/kill_switch/base/BinaryKillSwitch.sol @@ -0,0 +1,28 @@ +pragma solidity 0.4.24; + +import "./KillSwitch.sol"; +import "./IssuesRegistry.sol"; + + +contract BinaryKillSwitch is KillSwitch { + bytes32 constant public SET_IGNORED_CONTRACTS_ROLE = keccak256("SET_IGNORED_CONTRACTS_ROLE"); + + mapping (address => bool) internal ignoredContracts; + + event ContractIgnored(address _contract, bool ignored); + + function setContractIgnore(address _contract, bool _ignored) external; + + function isContractIgnored(address _contract) public view returns (bool) { + return ignoredContracts[_contract]; + } + + function isSeverityIgnored(address /*_contract*/, IssuesRegistry.Severity _severity) public view returns (bool) { + return _severity == IssuesRegistry.Severity.None; + } + + function _setContractIgnore(address _contract, bool _ignored) internal { + ignoredContracts[_contract] = _ignored; + emit ContractIgnored(_contract, _ignored); + } +} From c3f78fd987d266b9e0b1cddaebfcdb9a9ac2e761 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 24 Apr 2019 11:11:19 -0300 Subject: [PATCH 03/37] contracts: severities kill switch implementation --- .../kill_switch/base/SeveritiesKillSwitch.sol | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 contracts/kill_switch/base/SeveritiesKillSwitch.sol diff --git a/contracts/kill_switch/base/SeveritiesKillSwitch.sol b/contracts/kill_switch/base/SeveritiesKillSwitch.sol new file mode 100644 index 000000000..b2912e5d1 --- /dev/null +++ b/contracts/kill_switch/base/SeveritiesKillSwitch.sol @@ -0,0 +1,29 @@ +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 isContractIgnored(address _contract) public view returns (bool) { + return lowestAllowedSeverityByContract[_contract] == IssuesRegistry.Severity.None; + } + + 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); + } +} From 4d9ce01adeefcd665a74c0b903b00cc99aa640cc Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 24 Apr 2019 11:11:36 -0300 Subject: [PATCH 04/37] contracts: application level kill switch --- .../kill_switch/app/AppBinaryKillSwitch.sol | 14 + contracts/kill_switch/app/AppKillSwitch.sol | 15 + .../app/AppSeveritiesKillSwitch.sol | 14 + contracts/kill_switch/app/KillSwitchedApp.sol | 26 ++ .../app/AppKillSwitchedAppMock.sol | 27 ++ .../app/BinaryKillSwitchedAppMock.sol | 21 + .../app/SeveritiesKillSwitchedAppMock.sol | 21 + .../app/AppBinaryKillSwitch.test.js | 283 ++++++++++++ .../app/AppSeveritiesKillSwitch.test.js | 421 ++++++++++++++++++ 9 files changed, 842 insertions(+) create mode 100644 contracts/kill_switch/app/AppBinaryKillSwitch.sol create mode 100644 contracts/kill_switch/app/AppKillSwitch.sol create mode 100644 contracts/kill_switch/app/AppSeveritiesKillSwitch.sol create mode 100644 contracts/kill_switch/app/KillSwitchedApp.sol create mode 100644 contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol create mode 100644 contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol create mode 100644 contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol create mode 100644 test/kill_switch/app/AppBinaryKillSwitch.test.js create mode 100644 test/kill_switch/app/AppSeveritiesKillSwitch.test.js diff --git a/contracts/kill_switch/app/AppBinaryKillSwitch.sol b/contracts/kill_switch/app/AppBinaryKillSwitch.sol new file mode 100644 index 000000000..fce46e780 --- /dev/null +++ b/contracts/kill_switch/app/AppBinaryKillSwitch.sol @@ -0,0 +1,14 @@ +pragma solidity 0.4.24; + +import "./AppKillSwitch.sol"; +import "../base/BinaryKillSwitch.sol"; + + +contract AppBinaryKillSwitch is AppKillSwitch, BinaryKillSwitch { + function setContractIgnore(address _contract, bool _ignored) + external + authP(SET_IGNORED_CONTRACTS_ROLE, arr(_baseApp(), msg.sender)) + { + _setContractIgnore(_contract, _ignored); + } +} diff --git a/contracts/kill_switch/app/AppKillSwitch.sol b/contracts/kill_switch/app/AppKillSwitch.sol new file mode 100644 index 000000000..a3e5569ff --- /dev/null +++ b/contracts/kill_switch/app/AppKillSwitch.sol @@ -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()); + } +} diff --git a/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol b/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol new file mode 100644 index 000000000..bc9d15d60 --- /dev/null +++ b/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol @@ -0,0 +1,14 @@ +pragma solidity 0.4.24; + +import "./AppKillSwitch.sol"; +import "../base/SeveritiesKillSwitch.sol"; + + +contract AppSeveritiesKillSwitch is AppKillSwitch, SeveritiesKillSwitch { + function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) + external + authP(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_baseApp(), msg.sender)) + { + _setLowestAllowedSeverity(_contract, _severity); + } +} diff --git a/contracts/kill_switch/app/KillSwitchedApp.sol b/contracts/kill_switch/app/KillSwitchedApp.sol new file mode 100644 index 000000000..d091433a9 --- /dev/null +++ b/contracts/kill_switch/app/KillSwitchedApp.sol @@ -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()); + } +} diff --git a/contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol new file mode 100644 index 000000000..2bce791c1 --- /dev/null +++ b/contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol @@ -0,0 +1,27 @@ +pragma solidity 0.4.24; + +import "../../../../kill_switch/app/KillSwitchedApp.sol"; + + +contract AppKillSwitchedAppMock is KillSwitchedApp { + address public owner; + uint256 internal data; + + function initialize(AppKillSwitch _appKillSwitch, address _owner) public onlyInit { + super.initialize(_appKillSwitch); + data = 42; + owner = _owner; + } + + function read() public view returns (uint256) { + return data; + } + + function write(uint256 _data) public killSwitched { + data = _data; + } + + function reset() public killSwitched { + data = 0; + } +} diff --git a/contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol new file mode 100644 index 000000000..d746aad91 --- /dev/null +++ b/contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol @@ -0,0 +1,21 @@ +pragma solidity 0.4.24; + +import "./AppKillSwitchedAppMock.sol"; +import "../../../../kill_switch/app/AppBinaryKillSwitch.sol"; + + +contract AppBinaryKillSwitchMock is AppBinaryKillSwitch { + function _shouldEvaluateCall(address /*_base*/, address _instance, address _sender, bytes _data, uint256 /*_value*/) internal returns (bool) { + bytes4 methodID; + assembly { methodID := mload(add(_data, 0x20)) } + + // since this will act for every tx of the app, we provide a whitelist of functions + AppKillSwitchedAppMock app = AppKillSwitchedAppMock(_instance); + + // if called method is #reset, and the sender is the owner, do not evaluate + if (methodID == app.reset.selector && _sender == app.owner()) return false; + + // evaluate otherwise + return true; + } +} diff --git a/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol new file mode 100644 index 000000000..bbe682a1e --- /dev/null +++ b/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol @@ -0,0 +1,21 @@ +pragma solidity 0.4.24; + +import "./AppKillSwitchedAppMock.sol"; +import "../../../../kill_switch/app/AppSeveritiesKillSwitch.sol"; + + +contract AppSeveritiesKillSwitchMock is AppSeveritiesKillSwitch { + function _shouldEvaluateCall(address /*_base*/, address _instance, address _sender, bytes _data, uint256 /*_value*/) internal returns (bool) { + bytes4 methodID; + assembly { methodID := mload(add(_data, 0x20)) } + + // since this will act for every tx of the app, we provide a whitelist of functions + AppKillSwitchedAppMock app = AppKillSwitchedAppMock(_instance); + + // if called method is #reset, and the sender is the owner, do not evaluate + if (methodID == app.reset.selector && _sender == app.owner()) return false; + + // evaluate otherwise + return true; + } +} diff --git a/test/kill_switch/app/AppBinaryKillSwitch.test.js b/test/kill_switch/app/AppBinaryKillSwitch.test.js new file mode 100644 index 000000000..5043e3379 --- /dev/null +++ b/test/kill_switch/app/AppBinaryKillSwitch.test.js @@ -0,0 +1,283 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') +const AppBinaryKillSwitch = artifacts.require('AppBinaryKillSwitchMock') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { + let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase + let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + appKillSwitchBase = await AppBinaryKillSwitch.new() + issuesRegistryBase = await IssuesRegistry.new() + appBase = await KillSwitchedApp.new() + }) + + before('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const kernelReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry', async () => { + const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create app kill switch', async () => { + const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) + appKillSwitch = AppBinaryKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) + await appKillSwitch.initialize(issuesRegistry.address) + const SET_IGNORED_CONTRACTS_ROLE = await appKillSwitchBase.SET_IGNORED_CONTRACTS_ROLE() + await acl.createPermission(owner, appKillSwitch.address, SET_IGNORED_CONTRACTS_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) + await app.initialize(appKillSwitch.address, owner) + }) + + context('when the function being called is not tagged', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) + + context('when the function being called is tagged', () => { + context('when the function being called is always evaluated', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.write(10, { from }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itDoesNotExecuteTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + }) + + context('when the function being called is evaluated only when the sender is not the owner', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.reset({ from }) + assert.equal(await app.read(), 0) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + }) + }) +}) diff --git a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js new file mode 100644 index 000000000..117b676c1 --- /dev/null +++ b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js @@ -0,0 +1,421 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') +const AppSeveritiesKillSwitch = artifacts.require('AppSeveritiesKillSwitchMock') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { + let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase + let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + appKillSwitchBase = await AppSeveritiesKillSwitch.new() + issuesRegistryBase = await IssuesRegistry.new() + appBase = await KillSwitchedApp.new() + }) + + before('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const kernelReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry', async () => { + const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create app kill switch', async () => { + const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) + appKillSwitch = AppSeveritiesKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) + await appKillSwitch.initialize(issuesRegistry.address) + const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await appKillSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() + await acl.createPermission(owner, appKillSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) + await app.initialize(appKillSwitch.address, owner) + }) + + describe('when the function being called is not tagged', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) + }) + + describe('when the function being called is tagged', () => { + describe('when the function being called is always evaluated', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.write(10, { from }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itDoesNotExecuteTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + context('when the sender is the owner', () => { + itDoesNotExecuteTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + }) + }) + + describe('when the function being called is evaluated only when the sender is not the owner', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.reset({ from }) + assert.equal(await app.read(), 0) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) + }) + }) + }) +}) From 51127c42a001d99ac845a6ef699fddf04b9d2f54 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 24 Apr 2019 11:12:10 -0300 Subject: [PATCH 05/37] contracts: kernel level kill switch --- contracts/factory/DAOFactory.sol | 61 +++++-- .../kernel/KernelBinaryKillSwitch.sol | 16 ++ .../kill_switch/kernel/KernelKillSwitch.sol | 40 +++++ .../kernel/KernelSeveritiesKillSwitch.sol | 16 ++ .../kernel/KernelKillSwitchAppMock.sol | 27 +++ .../kernel/KernelBinaryKillSwitch.test.js | 128 ++++++++++++++ .../kernel/KernelSeveritiesKillSwitch.test.js | 164 ++++++++++++++++++ 7 files changed, 433 insertions(+), 19 deletions(-) create mode 100644 contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol create mode 100644 contracts/kill_switch/kernel/KernelKillSwitch.sol create mode 100644 contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol create mode 100644 contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol create mode 100644 test/kill_switch/kernel/KernelBinaryKillSwitch.test.js create mode 100644 test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index a2eec709a..c88ac9771 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -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"; @@ -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); } } diff --git a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol new file mode 100644 index 000000000..95ce4c4fc --- /dev/null +++ b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol @@ -0,0 +1,16 @@ +pragma solidity 0.4.24; + +import "./KernelKillSwitch.sol"; +import "../base/BinaryKillSwitch.sol"; + + +contract KernelBinaryKillSwitch is KernelKillSwitch, BinaryKillSwitch { + constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} + + function setContractIgnore(address _contract, bool _ignored) + external + auth(SET_IGNORED_CONTRACTS_ROLE, arr(_contract, msg.sender)) + { + _setContractIgnore(_contract, _ignored); + } +} diff --git a/contracts/kill_switch/kernel/KernelKillSwitch.sol b/contracts/kill_switch/kernel/KernelKillSwitch.sol new file mode 100644 index 000000000..16c96a691 --- /dev/null +++ b/contracts/kill_switch/kernel/KernelKillSwitch.sol @@ -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); + } +} diff --git a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol new file mode 100644 index 000000000..6fb4519b4 --- /dev/null +++ b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol @@ -0,0 +1,16 @@ +pragma solidity 0.4.24; + +import "./KernelKillSwitch.sol"; +import "../base/SeveritiesKillSwitch.sol"; + + +contract KernelSeveritiesKillSwitch is KernelKillSwitch, SeveritiesKillSwitch { + constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} + + function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) + external + auth(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) + { + _setLowestAllowedSeverity(_contract, _severity); + } +} diff --git a/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol b/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol new file mode 100644 index 000000000..9bfec0be7 --- /dev/null +++ b/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol @@ -0,0 +1,27 @@ +pragma solidity 0.4.24; + +import "../../../../apps/AragonApp.sol"; + + +contract KernelKillSwitchAppMock is AragonApp { + address public owner; + uint256 internal data; + + function initialize(address _owner) public onlyInit { + initialized(); + data = 42; + owner = _owner; + } + + function read() public view returns (uint256) { + return data; + } + + function write(uint256 _data) public { + data = _data; + } + + function reset() public { + data = 0; + } +} diff --git a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js new file mode 100644 index 000000000..adc32f301 --- /dev/null +++ b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js @@ -0,0 +1,128 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') + +const ACL = artifacts.require('ACL') +const RegularKernel = artifacts.require('Kernel') +const KernelKillSwitch = artifacts.require('KernelBinaryKillSwitch') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { + let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory + let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app + + before('deploy base implementations', async () => { + regularKernelBase = await RegularKernel.new(true) // petrify immediately + killSwitchedKernelBase = await KernelKillSwitch.new(true) // petrify immediately + aclBase = await ACL.new() + appBase = await KernelKillSwitchAppMock.new() + issuesRegistryBase = await IssuesRegistry.new() + registryFactory = await EVMScriptRegistryFactory.new() + }) + + beforeEach('deploy DAO with regular kernel', async () => { + const regularDaoFactory = await DAOFactory.new(regularKernelBase.address, aclBase.address, registryFactory.address) + const regularKernelReceipt = await regularDaoFactory.newDAO(root) + regularDao = RegularKernel.at(getEventArgument(regularKernelReceipt, 'DeployDAO', 'dao')) + regularAcl = ACL.at(await regularDao.acl()) + + const APP_MANAGER_ROLE = await regularKernelBase.APP_MANAGER_ROLE() + await regularAcl.createPermission(root, regularDao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry app from DAO with regular kernel', async () => { + const issuesRegistryReceipt = await regularDao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await regularAcl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with kernel binary kill switch', async () => { + const killSwitchedDaoFactory = await DAOFactory.new(killSwitchedKernelBase.address, aclBase.address, registryFactory.address) + const killSwitchedKernelReceipt = await killSwitchedDaoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + killSwitchedDao = KernelKillSwitch.at(getEventArgument(killSwitchedKernelReceipt, 'DeployDAO', 'dao')) + killSwitchedAcl = ACL.at(await killSwitchedDao.acl()) + + const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() + await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) + const SET_IGNORED_CONTRACTS_ROLE = await killSwitchedDao.SET_IGNORED_CONTRACTS_ROLE() + await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_IGNORED_CONTRACTS_ROLE, root, { from: root }) + }) + + beforeEach('create sample app from DAO with kernel binary kill switch', async () => { + const appReceipt = await killSwitchedDao.newAppInstance('0x1235', appBase.address, '0x', false, { from: root }) + app = KernelKillSwitchAppMock.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) + }) + + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + itDoesNotExecuteTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) +}) diff --git a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js new file mode 100644 index 000000000..6a5d60109 --- /dev/null +++ b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js @@ -0,0 +1,164 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') + +const ACL = artifacts.require('ACL') +const RegularKernel = artifacts.require('Kernel') +const KernelKillSwitch = artifacts.require('KernelSeveritiesKillSwitch') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner]) => { + let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory + let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app + + before('deploy base implementations', async () => { + regularKernelBase = await RegularKernel.new(true) // petrify immediately + killSwitchedKernelBase = await KernelKillSwitch.new(true) // petrify immediately + aclBase = await ACL.new() + appBase = await KernelKillSwitchAppMock.new() + issuesRegistryBase = await IssuesRegistry.new() + registryFactory = await EVMScriptRegistryFactory.new() + }) + + before('deploy DAO with regular kernel', async () => { + const regularDaoFactory = await DAOFactory.new(regularKernelBase.address, aclBase.address, registryFactory.address) + const regularKernelReceipt = await regularDaoFactory.newDAO(root) + regularDao = RegularKernel.at(getEventArgument(regularKernelReceipt, 'DeployDAO', 'dao')) + regularAcl = ACL.at(await regularDao.acl()) + + const APP_MANAGER_ROLE = await regularKernelBase.APP_MANAGER_ROLE() + await regularAcl.createPermission(root, regularDao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry app from DAO with regular kernel', async () => { + const issuesRegistryReceipt = await regularDao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await regularAcl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with kernel severities kill switch', async () => { + const killSwitchedDaoFactory = await DAOFactory.new(killSwitchedKernelBase.address, aclBase.address, registryFactory.address) + const killSwitchedKernelReceipt = await killSwitchedDaoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + killSwitchedDao = KernelKillSwitch.at(getEventArgument(killSwitchedKernelReceipt, 'DeployDAO', 'dao')) + killSwitchedAcl = ACL.at(await killSwitchedDao.acl()) + + const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() + await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) + const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchedDao.SET_LOWEST_ALLOWED_SEVERITY_ROLE() + await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create sample app from DAO with kernel severities kill switch', async () => { + const appReceipt = await killSwitchedDao.newAppInstance('0x1235', appBase.address, '0x', false, { from: root }) + app = KernelKillSwitchAppMock.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) + }) + + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when there lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when there lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when there lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) + }) +}) From b75fdca7e7739d22d87ff1a2561615e4b2c6c5bf Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Sun, 28 Apr 2019 21:45:14 -0300 Subject: [PATCH 06/37] tests: add kill switch unit tests --- .../kill_switch/base/SeveritiesKillSwitch.sol | 2 +- .../app/AppBinaryKillSwitch.test.js | 287 +++++------ .../app/AppSeveritiesKillSwitch.test.js | 469 +++++++++--------- test/kill_switch/base/IssuesRegistry.test.js | 131 +++++ .../base/itBehavesLikeBinaryKillSwitch.js | 50 ++ .../base/itBehavesLikeSeveritiesKillSwitch.js | 73 +++ .../kernel/KernelBinaryKillSwitch.test.js | 83 ++-- .../kernel/KernelSeveritiesKillSwitch.test.js | 141 +++--- 8 files changed, 767 insertions(+), 469 deletions(-) create mode 100644 test/kill_switch/base/IssuesRegistry.test.js create mode 100644 test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js create mode 100644 test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js diff --git a/contracts/kill_switch/base/SeveritiesKillSwitch.sol b/contracts/kill_switch/base/SeveritiesKillSwitch.sol index b2912e5d1..1c7106121 100644 --- a/contracts/kill_switch/base/SeveritiesKillSwitch.sol +++ b/contracts/kill_switch/base/SeveritiesKillSwitch.sol @@ -19,7 +19,7 @@ contract SeveritiesKillSwitch is KillSwitch { function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool) { IssuesRegistry.Severity lowestAllowedSeverity = lowestAllowedSeverityByContract[_contract]; - return lowestAllowedSeverity > _severity; + return lowestAllowedSeverity >= _severity; } function _setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) internal { diff --git a/test/kill_switch/app/AppBinaryKillSwitch.test.js b/test/kill_switch/app/AppBinaryKillSwitch.test.js index 5043e3379..52ae92517 100644 --- a/test/kill_switch/app/AppBinaryKillSwitch.test.js +++ b/test/kill_switch/app/AppBinaryKillSwitch.test.js @@ -1,4 +1,5 @@ const { assertRevert } = require('../../helpers/assertThrow') +const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') @@ -57,58 +58,19 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await app.initialize(appKillSwitch.address, owner) }) - context('when the function being called is not tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) + describe('binary kill switch', function () { + beforeEach('bind kill switch', function () { + this.killSwitch = appKillSwitch }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - context('when the contract being called is not ignored', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) - }) + itBehavesLikeBinaryKillSwitch(owner, anyone) }) - context('when the function being called is tagged', () => { - context('when the function being called is always evaluated', () => { - const itExecutesTheCall = (from = owner) => { + describe('integration', () => { + context('when the function being called is not tagged', () => { + const itExecutesTheCall = () => { it('executes the call', async () => { - await app.write(10, { from }) - assert.equal(await app.read(), 10) - }) - } - - const itDoesNotExecuteTheCall = (from = owner) => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + assert.equal(await app.read(), 42) }) } @@ -131,15 +93,38 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - context('when the bug was not fixed yet', () => { - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itDoesNotExecuteTheCall(owner) - }) + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) - }) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + }) + + context('when the function being called is tagged', () => { + context('when the function being called is always evaluated', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.write(10, { from }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() }) context('when the contract being called is ignored', () => { @@ -147,90 +132,90 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCall() }) }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itDoesNotExecuteTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - }) - }) - }) - }) - context('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from = owner) => { - it('executes the call', async () => { - await app.reset({ from }) - assert.equal(await app.read(), 0) - }) - } + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) - const itDoesNotExecuteTheCall = (from = owner) => { - it('does not execute the call', async () => { - await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) }) - - itExecutesTheCall() }) }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) + context('when the function being called is evaluated only when the sender is not the owner', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.reset({ from }) + assert.equal(await app.read(), 0) + }) + } - context('when the bug was not fixed yet', () => { - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) - }) + context('when there is no bug registered', () => { + context('when the contract being called is not ignored', () => { + itExecutesTheCall() }) context('when the contract being called is ignored', () => { @@ -238,42 +223,68 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCall() }) }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the contract being called is not ignored', () => { + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) }) }) diff --git a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js index 117b676c1..bfe9455a1 100644 --- a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js +++ b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js @@ -1,4 +1,5 @@ const { assertRevert } = require('../../helpers/assertThrow') +const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') @@ -57,76 +58,19 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await app.initialize(appKillSwitch.address, owner) }) - describe('when the function being called is not tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCall() - }) + describe('binary kill switch', function () { + beforeEach('bind kill switch', function () { + this.killSwitch = appKillSwitch }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCall() - }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - }) + itBehavesLikeSeveritiesKillSwitch(owner, anyone) }) - describe('when the function being called is tagged', () => { - describe('when the function being called is always evaluated', () => { - const itExecutesTheCall = (from = owner) => { + describe('integration', () => { + context('when the function being called is not tagged', () => { + const itExecutesTheCall = () => { it('executes the call', async () => { - await app.write(10, { from }) - assert.equal(await app.read(), 10) - }) - } - - const itDoesNotExecuteTheCall = (from = owner) => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + assert.equal(await app.read(), 42) }) } @@ -149,83 +93,74 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - context('when the sender is the owner', () => { - itDoesNotExecuteTheCall(owner) - }) + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - context('when the sender is the owner', () => { - itDoesNotExecuteTheCall(owner) - }) + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) - }) + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) + itExecutesTheCall() }) }) + }) + }) + + context('when the function being called is tagged', () => { + describe('when the function being called is always evaluated', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.write(10, { from }) + assert.equal(await app.read(), 10) + }) + } - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') }) + } + context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) + + itExecutesTheCall() }) + }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { context('when the sender is the owner', () => { itExecutesTheCall(owner) }) @@ -235,25 +170,57 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itDoesNotExecuteTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) }) + }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + context('when there is no lowest allowed severity set for the contract being called', () => { context('when the sender is the owner', () => { itExecutesTheCall(owner) }) @@ -262,121 +229,149 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) itExecutesTheCall(anyone) }) }) - }) - }) - }) - }) - describe('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from = owner) => { - it('executes the call', async () => { - await app.reset({ from }) - assert.equal(await app.read(), 0) - }) - } + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - const itDoesNotExecuteTheCall = (from = owner) => { - it('does not execute the call', async () => { - await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - itExecutesTheCall() + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) + }) + }) }) }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + describe('when the function being called is evaluated only when the sender is not the owner', () => { + const itExecutesTheCall = (from = owner) => { + it('executes the call', async () => { + await app.reset({ from }) + assert.equal(await app.read(), 0) + }) + } + + const itDoesNotExecuteTheCall = (from = owner) => { + it('does not execute the call', async () => { + await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } - context('when the bug was not fixed yet', () => { + context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) + + itExecutesTheCall() }) + }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { context('when the sender is the owner', () => { itExecutesTheCall(owner) }) context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) + itExecutesTheCall(anyone) }) }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) + context('when the sender is not the owner', () => { + itDoesNotExecuteTheCall(anyone) + }) }) - }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - }) - }) - }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) - context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) + }) }) }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + context('when there is no lowest allowed severity set for the contract being called', () => { context('when the sender is the owner', () => { itExecutesTheCall(owner) }) @@ -386,31 +381,47 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCall(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) }) }) diff --git a/test/kill_switch/base/IssuesRegistry.test.js b/test/kill_switch/base/IssuesRegistry.test.js new file mode 100644 index 000000000..5859c88e6 --- /dev/null +++ b/test/kill_switch/base/IssuesRegistry.test.js @@ -0,0 +1,131 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +const IssuesRegistry = artifacts.require('IssuesRegistry') +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { + let kernelBase, aclBase, issuesRegistryBase, registryFactory, dao, acl, issuesRegistry + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + issuesRegistryBase = await IssuesRegistry.new() + }) + + before('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const kernelReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry', async () => { + const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(owner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + describe('isSeverityFor', () => { + context('when there was no severity set before', () => { + it('returns false', async () => { + assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + }) + }) + + context('when there was a severity already set', () => { + beforeEach('set medium severity', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from: owner }) + }) + + context('when the issues was not fixed yet', () => { + it('returns true', async () => { + assert.isTrue(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + }) + }) + + context('when the issues was already fixed', () => { + beforeEach('set medium severity', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.NONE, { from: owner }) + }) + + it('returns false', async () => { + assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + }) + }) + }) + }) + + describe('getSeverityFor', () => { + context('when there was no severity set before', () => { + it('returns none', async () => { + assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.NONE, 'severity does not match') + }) + }) + + context('when there was a severity already set', () => { + beforeEach('set medium severity', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.MID, { from: owner }) + }) + + it('returns the severity already set', async () => { + assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.MID, 'severity does not match') + }) + }) + }) + + describe('setSeverityFor', () => { + context('when the sender is the owner', () => { + const from = owner + + it('emits an event', async () => { + const { logs } = await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from }) + + const events = logs.filter(l => l.event === 'SeveritySet') + assert.equal(events.length, 1, 'number of SeveritySet events does not match') + assert.equal(events[0].args.entry, implementation, 'entry address does not match') + assert.equal(events[0].args.severity, SEVERITY.LOW, 'severity does not match') + assert.equal(events[0].args.sender, owner, 'sender does not match') + }) + + context('when there was no severity set before', () => { + it('sets the severity for the given entry', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.MID, { from }) + + assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.MID, 'severity does not match') + }) + }) + + context('when there was a severity already set', () => { + beforeEach('set medium severity', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.MID, { from }) + }) + + it('changes the severity for the given entry', async () => { + await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from }) + + assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.LOW, 'severity does not match') + }) + }) + }) + + context('when the sender is not the owner', () => { + const from = anyone + + it('reverts', async () => { + await assertRevert(issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from })) + }) + }) + }) +}) diff --git a/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js b/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js new file mode 100644 index 000000000..831ce6003 --- /dev/null +++ b/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js @@ -0,0 +1,50 @@ +const { assertRevert } = require('../../helpers/assertThrow') + +module.exports = function (owner, address) { + describe('isContractIgnored', function () { + context('when the contract is not ignored', function () { + it('returns false', async function () { + assert.isFalse(await this.killSwitch.isContractIgnored(address)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await this.killSwitch.setContractIgnore(address, true, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isContractIgnored(address)) + }) + }) + }) + + describe('setContractIgnore', function () { + context('when the sender is the owner', function () { + const from = owner + + context('ignoring a contract', function () { + it('ignores the contract', async function () { + await this.killSwitch.setContractIgnore(address, true, { from }) + + assert.isTrue(await this.killSwitch.isContractIgnored(address)) + }) + }) + + context('reverting a contract ignore', function () { + it('reverts the contract ignore', async function () { + await this.killSwitch.setContractIgnore(address, true, { from }) + await this.killSwitch.setContractIgnore(address, false, { from }) + + assert.isFalse(await this.killSwitch.isContractIgnored(address)) + }) + }) + }) + + context('when the sender is not the owner', function () { + it('reverts', async function () { + await assertRevert(this.killSwitch.setContractIgnore(address, true)) + }) + }) + }) +} diff --git a/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js b/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js new file mode 100644 index 000000000..821b53643 --- /dev/null +++ b/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js @@ -0,0 +1,73 @@ +const { assertRevert } = require('../../helpers/assertThrow') +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +module.exports = function (owner, anAddress) { + describe('isSeverityIgnored', function () { + context('when no lowest allowed severity was set yet', function () { + it('returns false for all the severities', async function () { + for (const key of Object.keys(SEVERITY).slice(1)) { + assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY[key])) + } + }) + }) + + context('when a lowest allowed severity was set', function () { + beforeEach('set a lowest allowed severity', async function () { + await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID, { from: owner }) + }) + + context('when the given severity is lower than the one set', function () { + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.LOW)) + }) + }) + + context('when the given severity is equal to the one set', function () { + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.MID)) + }) + }) + + context('when the given severity is greater than the one set', function () { + it('returns false', async function () { + assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) + }) + }) + }) + }) + + describe('setLowestAllowedSeverity', function () { + context('when the contract is the owner', function () { + const from = owner + + context('when there was no severity set', function () { + it('sets the lowest allowed severity', async function () { + await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.HIGH, { from }) + + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) + assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.CRITICAL)) + }) + }) + + context('when there was a previous severity set', function () { + beforeEach('set lowest allowed severity', async function () { + await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.LOW, { from }) + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.LOW)) + }) + + it('changes the lowest allowed severity', async function () { + await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID, { from }) + + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.MID)) + assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) + }) + }) + }) + + context('when the sender is not the owner', function () { + it('reverts', async function () { + await assertRevert(this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID)) + }) + }) + }) +} diff --git a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js index adc32f301..e9dc5e572 100644 --- a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js +++ b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js @@ -1,4 +1,5 @@ const { assertRevert } = require('../../helpers/assertThrow') +const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') @@ -62,40 +63,30 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = await app.initialize(owner) }) - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) + describe('binary kill switch', function () { + beforeEach('bind kill switch', function () { + this.killSwitch = killSwitchedDao }) - } - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { - itExecutesTheCall() - }) + itBehavesLikeBinaryKillSwitch(owner, anyone) + }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + describe('integration', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) }) + } - itExecutesTheCall() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') + }) + } - context('when the bug was not fixed yet', () => { + context('when there is no bug registered', () => { context('when the contract being called is not ignored', () => { - itDoesNotExecuteTheCall() + itExecutesTheCall() }) context('when the contract being called is ignored', () => { @@ -107,21 +98,41 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = }) }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - itExecutesTheCall() + context('when the bug was not fixed yet', () => { + context('when the contract being called is not ignored', () => { + itDoesNotExecuteTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - itExecutesTheCall() + context('when the contract being called is not ignored', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) }) }) }) diff --git a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js index 6a5d60109..de4f48dc1 100644 --- a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js +++ b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js @@ -1,4 +1,5 @@ const { assertRevert } = require('../../helpers/assertThrow') +const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') @@ -13,7 +14,7 @@ const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] -contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner]) => { +contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app @@ -62,101 +63,111 @@ contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner]) => { await app.initialize(owner) }) - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) + describe('severities kill switch', function () { + beforeEach('bind kill switch', function () { + this.killSwitch = killSwitchedDao }) - } - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) + itBehavesLikeSeveritiesKillSwitch(owner, anyone) + }) - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + describe('integration', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) }) + } - itExecutesTheCall() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') + }) + } - context('when the bug was not fixed yet', () => { + context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { itExecutesTheCall() }) context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itDoesNotExecuteTheCall() + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + itExecutesTheCall() + }) + }) - itDoesNotExecuteTheCall() + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCall() }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itDoesNotExecuteTheCall() }) - itExecutesTheCall() - }) - }) - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) + itExecutesTheCall() + }) - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when there lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + itExecutesTheCall() }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + context('when there is no lowest allowed severity set for the contract being called', () => { itExecutesTheCall() }) - context('when there lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCall() }) - itExecutesTheCall() - }) + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when there lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + itExecutesTheCall() }) - itExecutesTheCall() + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCall() + }) }) }) }) From 4f4421ebe94a20fe701b3048566d7ad411ef48fb Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Mon, 29 Apr 2019 10:21:57 -0300 Subject: [PATCH 07/37] contracts: use ternary ignoration for kill-switch settings --- .../kill_switch/app/AppBinaryKillSwitch.sol | 6 +- .../app/AppSeveritiesKillSwitch.sol | 7 + .../kill_switch/base/BinaryKillSwitch.sol | 17 - contracts/kill_switch/base/KillSwitch.sol | 28 +- .../kill_switch/base/SeveritiesKillSwitch.sol | 4 - .../kernel/KernelBinaryKillSwitch.sol | 6 +- .../kernel/KernelSeveritiesKillSwitch.sol | 7 + .../app/AppBinaryKillSwitch.test.js | 222 ++++++------ .../app/AppSeveritiesKillSwitch.test.js | 321 +++++++++++------- test/kill_switch/base/IssuesRegistry.test.js | 6 +- .../base/itBehavesLikeBinaryKillSwitch.js | 68 +++- .../base/itBehavesLikeSeveritiesKillSwitch.js | 72 +++- test/kill_switch/helpers/enums.js | 7 + test/kill_switch/helpers/events.js | 5 + .../kernel/KernelBinaryKillSwitch.test.js | 52 +-- .../kernel/KernelSeveritiesKillSwitch.test.js | 10 +- 16 files changed, 541 insertions(+), 297 deletions(-) create mode 100644 test/kill_switch/helpers/enums.js create mode 100644 test/kill_switch/helpers/events.js diff --git a/contracts/kill_switch/app/AppBinaryKillSwitch.sol b/contracts/kill_switch/app/AppBinaryKillSwitch.sol index fce46e780..b813755bd 100644 --- a/contracts/kill_switch/app/AppBinaryKillSwitch.sol +++ b/contracts/kill_switch/app/AppBinaryKillSwitch.sol @@ -5,10 +5,10 @@ import "../base/BinaryKillSwitch.sol"; contract AppBinaryKillSwitch is AppKillSwitch, BinaryKillSwitch { - function setContractIgnore(address _contract, bool _ignored) + function setContractAction(address _contract, ContractAction _action) external - authP(SET_IGNORED_CONTRACTS_ROLE, arr(_baseApp(), msg.sender)) + authP(SET_CONTRACT_ACTION_ROLE, arr(_baseApp(), msg.sender)) { - _setContractIgnore(_contract, _ignored); + _setContractAction(_contract, _action); } } diff --git a/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol b/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol index bc9d15d60..005732f9b 100644 --- a/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol +++ b/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol @@ -5,6 +5,13 @@ 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)) diff --git a/contracts/kill_switch/base/BinaryKillSwitch.sol b/contracts/kill_switch/base/BinaryKillSwitch.sol index 1f5623c94..e5f507edd 100644 --- a/contracts/kill_switch/base/BinaryKillSwitch.sol +++ b/contracts/kill_switch/base/BinaryKillSwitch.sol @@ -5,24 +5,7 @@ import "./IssuesRegistry.sol"; contract BinaryKillSwitch is KillSwitch { - bytes32 constant public SET_IGNORED_CONTRACTS_ROLE = keccak256("SET_IGNORED_CONTRACTS_ROLE"); - - mapping (address => bool) internal ignoredContracts; - - event ContractIgnored(address _contract, bool ignored); - - function setContractIgnore(address _contract, bool _ignored) external; - - function isContractIgnored(address _contract) public view returns (bool) { - return ignoredContracts[_contract]; - } - function isSeverityIgnored(address /*_contract*/, IssuesRegistry.Severity _severity) public view returns (bool) { return _severity == IssuesRegistry.Severity.None; } - - function _setContractIgnore(address _contract, bool _ignored) internal { - ignoredContracts[_contract] = _ignored; - emit ContractIgnored(_contract, _ignored); - } } diff --git a/contracts/kill_switch/base/KillSwitch.sol b/contracts/kill_switch/base/KillSwitch.sol index 3aea7d9ec..3305cc938 100644 --- a/contracts/kill_switch/base/KillSwitch.sol +++ b/contracts/kill_switch/base/KillSwitch.sol @@ -4,18 +4,39 @@ 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 getContractAction(address _contract) public view returns (ContractAction) { + return contractActions[_contract]; + } - function isContractIgnored(address _contract) public view returns (bool); + function setContractAction(address _contract, ContractAction _action) external; 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; @@ -45,4 +66,9 @@ contract KillSwitch { issuesRegistry = _issuesRegistry; emit IssuesRegistrySet(_issuesRegistry, msg.sender); } + + function _setContractAction(address _contract, ContractAction _action) internal { + contractActions[_contract] = _action; + emit ContractActionSet(_contract, _action); + } } diff --git a/contracts/kill_switch/base/SeveritiesKillSwitch.sol b/contracts/kill_switch/base/SeveritiesKillSwitch.sol index 1c7106121..e38867944 100644 --- a/contracts/kill_switch/base/SeveritiesKillSwitch.sol +++ b/contracts/kill_switch/base/SeveritiesKillSwitch.sol @@ -13,10 +13,6 @@ contract SeveritiesKillSwitch is KillSwitch { function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) external; - function isContractIgnored(address _contract) public view returns (bool) { - return lowestAllowedSeverityByContract[_contract] == IssuesRegistry.Severity.None; - } - function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool) { IssuesRegistry.Severity lowestAllowedSeverity = lowestAllowedSeverityByContract[_contract]; return lowestAllowedSeverity >= _severity; diff --git a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol index 95ce4c4fc..8c8d3eeb7 100644 --- a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol +++ b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol @@ -7,10 +7,10 @@ import "../base/BinaryKillSwitch.sol"; contract KernelBinaryKillSwitch is KernelKillSwitch, BinaryKillSwitch { constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} - function setContractIgnore(address _contract, bool _ignored) + function setContractAction(address _contract, ContractAction _action) external - auth(SET_IGNORED_CONTRACTS_ROLE, arr(_contract, msg.sender)) + auth(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender)) { - _setContractIgnore(_contract, _ignored); + _setContractAction(_contract, _action); } } diff --git a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol index 6fb4519b4..00503324b 100644 --- a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol +++ b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol @@ -7,6 +7,13 @@ import "../base/SeveritiesKillSwitch.sol"; contract KernelSeveritiesKillSwitch is KernelKillSwitch, SeveritiesKillSwitch { constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} + function setContractAction(address _contract, ContractAction _action) + external + auth(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender)) + { + _setContractAction(_contract, _action); + } + function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) external auth(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) diff --git a/test/kill_switch/app/AppBinaryKillSwitch.test.js b/test/kill_switch/app/AppBinaryKillSwitch.test.js index 52ae92517..7ae743735 100644 --- a/test/kill_switch/app/AppBinaryKillSwitch.test.js +++ b/test/kill_switch/app/AppBinaryKillSwitch.test.js @@ -1,4 +1,6 @@ +const { ACTION, SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -10,10 +12,6 @@ const Kernel = artifacts.require('Kernel') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } - -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] - contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch @@ -48,8 +46,8 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) appKillSwitch = AppBinaryKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) await appKillSwitch.initialize(issuesRegistry.address) - const SET_IGNORED_CONTRACTS_ROLE = await appKillSwitchBase.SET_IGNORED_CONTRACTS_ROLE() - await acl.createPermission(owner, appKillSwitch.address, SET_IGNORED_CONTRACTS_ROLE, root, { from: root }) + const SET_CONTRACT_ACTION_ROLE = await appKillSwitchBase.SET_CONTRACT_ACTION_ROLE() + await acl.createPermission(owner, appKillSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { @@ -75,13 +73,21 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { } context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { + context('when the contract being called is checked', () => { itExecutesTheCall() }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) }) itExecutesTheCall() @@ -93,13 +99,21 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { + context('when the contract being called is checked', () => { itExecutesTheCall() }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) }) itExecutesTheCall() @@ -109,31 +123,43 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the function being called is tagged', () => { context('when the function being called is always evaluated', () => { - const itExecutesTheCall = (from = owner) => { + const itExecutesTheCall = () => { it('executes the call', async () => { - await app.write(10, { from }) + await app.write(10, { from: owner }) assert.equal(await app.read(), 10) }) } - const itDoesNotExecuteTheCall = (from = owner) => { + const itDoesNotExecuteTheCall = () => { it('does not execute the call', async () => { - await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') }) } - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { + const itExecutesTheCallWhenNotDenied = () => { + context('when the contract being called is checked', () => { itExecutesTheCall() }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) }) itExecutesTheCall() }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + } + + context('when there is no bug registered', () => { + itExecutesTheCallWhenNotDenied() }) context('when there is a bug registered', () => { @@ -142,28 +168,24 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when the bug was not fixed yet', () => { - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itDoesNotExecuteTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) - }) + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall(owner) }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + itExecutesTheCall() + }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) }) + + itDoesNotExecuteTheCall() }) }) @@ -172,59 +194,81 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) - }) - - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) + itExecutesTheCallWhenNotDenied() }) }) }) context('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from = owner) => { + const itExecutesTheCall = (from) => { it('executes the call', async () => { await app.reset({ from }) assert.equal(await app.read(), 0) }) } - const itDoesNotExecuteTheCall = (from = owner) => { + const itDoesNotExecuteTheCall = (from) => { it('does not execute the call', async () => { await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') }) } - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { - itExecutesTheCall() + const itExecutesTheCallEvenIfDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCall(from) }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itExecutesTheCall(from) + }) + } + + const itExecutesTheCallWhenNotDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(from) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(from) + }) + } + + const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCallWhenNotDenied(anyone) + }) + } + + context('when there is no bug registered', () => { + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when there is a bug registered', () => { @@ -233,27 +277,31 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when the bug was not fixed yet', () => { - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) - context('when the sender is not the owner', () => { + context('when the sender is not the owner', () => { + context('when the contract being called is checked', () => { itDoesNotExecuteTheCall(anyone) }) - }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) - }) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the sender is not the owner', () => { + itExecutesTheCall(anyone) + }) }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(anyone) }) }) }) @@ -263,29 +311,7 @@ contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractIgnore(appBase.address, true, { from: owner }) - }) - - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) }) }) diff --git a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js index bfe9455a1..160080212 100644 --- a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js +++ b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js @@ -1,4 +1,6 @@ +const { ACTION, SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -10,10 +12,6 @@ const Kernel = artifacts.require('Kernel') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } - -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] - contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch @@ -48,6 +46,8 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) appKillSwitch = AppSeveritiesKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) await appKillSwitch.initialize(issuesRegistry.address) + const SET_CONTRACT_ACTION_ROLE = await appKillSwitchBase.SET_CONTRACT_ACTION_ROLE() + await acl.createPermission(owner, appKillSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await appKillSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() await acl.createPermission(owner, appKillSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) @@ -68,15 +68,30 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) describe('integration', () => { context('when the function being called is not tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) + + const itExecutesTheCallEvenIfDenied = () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + context('when the contract being called is not denied', () => { + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itExecutesTheCall() }) } context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) context('when there is a lowest allowed severity set for the contract being called', () => { @@ -84,7 +99,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) }) @@ -94,16 +109,12 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) context('when there is a lowest allowed severity set for the contract being called', () => { context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) context('when the lowest allowed severity is equal to the reported bug severity', () => { @@ -111,7 +122,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) context('when the lowest allowed severity is greater than the reported bug severity', () => { @@ -119,7 +130,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCallEvenIfDenied() }) }) }) @@ -127,22 +138,44 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) context('when the function being called is tagged', () => { describe('when the function being called is always evaluated', () => { - const itExecutesTheCall = (from = owner) => { + const itExecutesTheCall = () => { it('executes the call', async () => { - await app.write(10, { from }) + await app.write(10, { from: owner }) assert.equal(await app.read(), 10) }) } - const itDoesNotExecuteTheCall = (from = owner) => { + const itDoesNotExecuteTheCall = () => { it('does not execute the call', async () => { - await assertRevert(app.write(10, { from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + const itExecutesTheCallWhenNotDenied = () => { + context('when the contract being called is checked', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() }) } context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() + itExecutesTheCallWhenNotDenied() }) context('when there is a lowest allowed severity set for the contract being called', () => { @@ -150,7 +183,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCallWhenNotDenied() }) }) @@ -161,12 +194,24 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) context('when the bug was not fixed yet', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall() }) - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() }) }) @@ -176,12 +221,24 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when the sender is the owner', () => { - itDoesNotExecuteTheCall(owner) + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall() }) - context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() }) }) @@ -190,13 +247,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) context('when the lowest allowed severity is greater than the reported bug severity', () => { @@ -204,13 +255,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) }) }) @@ -221,13 +266,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) context('when there is a lowest allowed severity set for the contract being called', () => { @@ -236,13 +275,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) context('when the lowest allowed severity is equal to the reported bug severity', () => { @@ -250,13 +283,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) context('when the lowest allowed severity is greater than the reported bug severity', () => { @@ -264,13 +291,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallWhenNotDenied() }) }) }) @@ -278,22 +299,76 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) describe('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from = owner) => { + const itExecutesTheCall = (from) => { it('executes the call', async () => { await app.reset({ from }) assert.equal(await app.read(), 0) }) } - const itDoesNotExecuteTheCall = (from = owner) => { + const itDoesNotExecuteTheCall = (from) => { it('does not execute the call', async () => { await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') }) } + const itExecutesTheCallEvenIfDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(from) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itExecutesTheCall(from) + }) + } + + const itExecutesTheCallWhenNotDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(from) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(from) + }) + } + + const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCallWhenNotDenied(anyone) + }) + } + context('when there is no bug registered', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when there is a lowest allowed severity set for the contract being called', () => { @@ -301,7 +376,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCall() + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) }) @@ -313,11 +388,29 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) context('when the bug was not fixed yet', () => { context('when there is no lowest allowed severity set for the contract being called', () => { context('when the sender is the owner', () => { - itExecutesTheCall(owner) + itExecutesTheCallEvenIfDenied(owner) }) context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall(anyone) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(anyone) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(anyone) + }) }) }) @@ -328,11 +421,29 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) context('when the sender is the owner', () => { - itExecutesTheCall(owner) + itExecutesTheCallEvenIfDenied(owner) }) context('when the sender is not the owner', () => { - itDoesNotExecuteTheCall(anyone) + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall(anyone) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(anyone) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(anyone) + }) }) }) @@ -341,13 +452,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when the lowest allowed severity is greater than the reported bug severity', () => { @@ -355,13 +460,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) }) }) @@ -372,13 +471,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) }) context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when there is a lowest allowed severity set for the contract being called', () => { @@ -387,13 +480,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when the lowest allowed severity is equal to the reported bug severity', () => { @@ -401,13 +488,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) context('when the lowest allowed severity is greater than the reported bug severity', () => { @@ -415,13 +496,7 @@ contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) - context('when the sender is the owner', () => { - itExecutesTheCall(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() }) }) }) diff --git a/test/kill_switch/base/IssuesRegistry.test.js b/test/kill_switch/base/IssuesRegistry.test.js index 5859c88e6..bdc78d618 100644 --- a/test/kill_switch/base/IssuesRegistry.test.js +++ b/test/kill_switch/base/IssuesRegistry.test.js @@ -1,4 +1,6 @@ +const { SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') const IssuesRegistry = artifacts.require('IssuesRegistry') const ACL = artifacts.require('ACL') @@ -6,10 +8,6 @@ const Kernel = artifacts.require('Kernel') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } - -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] - contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { let kernelBase, aclBase, issuesRegistryBase, registryFactory, dao, acl, issuesRegistry diff --git a/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js b/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js index 831ce6003..043845b39 100644 --- a/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js +++ b/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js @@ -1,50 +1,86 @@ +const { ACTION, SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') -module.exports = function (owner, address) { +module.exports = function (owner, anAddress) { describe('isContractIgnored', function () { - context('when the contract is not ignored', function () { + context('when the contract is checked', function () { it('returns false', async function () { - assert.isFalse(await this.killSwitch.isContractIgnored(address)) + assert.isFalse(await this.killSwitch.isContractIgnored(anAddress)) }) }) context('when the contract is ignored', function () { beforeEach('ignore contract', async function () { - await this.killSwitch.setContractIgnore(address, true, { from: owner }) + await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from: owner }) }) it('returns true', async function () { - assert.isTrue(await this.killSwitch.isContractIgnored(address)) + assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) }) }) }) - describe('setContractIgnore', function () { + describe('isContractDenied', function () { + context('when the contract is not denied', function () { + it('returns false', async function () { + assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + }) + + describe('setContractAction', function () { context('when the sender is the owner', function () { const from = owner - context('ignoring a contract', function () { - it('ignores the contract', async function () { - await this.killSwitch.setContractIgnore(address, true, { from }) + context('when there was no action set yet', function () { + it('sets a new action', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) - assert.isTrue(await this.killSwitch.isContractIgnored(address)) + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) }) }) - context('reverting a contract ignore', function () { - it('reverts the contract ignore', async function () { - await this.killSwitch.setContractIgnore(address, true, { from }) - await this.killSwitch.setContractIgnore(address, false, { from }) + context('when there was an action already set', function () { + beforeEach('deny contract', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) + }) + + it('changes the contract action', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from }) - assert.isFalse(await this.killSwitch.isContractIgnored(address)) + assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) + assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) }) }) }) context('when the sender is not the owner', function () { it('reverts', async function () { - await assertRevert(this.killSwitch.setContractIgnore(address, true)) + await assertRevert(this.killSwitch.setContractAction(anAddress, ACTION.DENY)) }) }) }) + + describe('isSeverityIgnored', function () { + it('returns true for none severities', async function () { + assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.NONE)) + }) + + it('returns false for all the severities', async function () { + for (const key of Object.keys(SEVERITY).slice(1)) { + assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY[key])) + } + }) + }) } diff --git a/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js b/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js index 821b53643..d6e8f0730 100644 --- a/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js +++ b/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js @@ -1,7 +1,77 @@ +const { ACTION, SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } module.exports = function (owner, anAddress) { + describe('isContractIgnored', function () { + context('when the contract is checked', function () { + it('returns false', async function () { + assert.isFalse(await this.killSwitch.isContractIgnored(anAddress)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) + }) + }) + }) + + describe('isContractDenied', function () { + context('when the contract is not denied', function () { + it('returns false', async function () { + assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + }) + + describe('setContractAction', function () { + context('when the sender is the owner', function () { + const from = owner + + context('when there was no action set yet', function () { + it('sets a new action', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) + + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + + context('when there was an action already set', function () { + beforeEach('deny contract', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) + assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) + }) + + it('changes the contract action', async function () { + await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from }) + + assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) + assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) + }) + }) + }) + + context('when the sender is not the owner', function () { + it('reverts', async function () { + await assertRevert(this.killSwitch.setContractAction(anAddress, ACTION.DENY)) + }) + }) + }) + describe('isSeverityIgnored', function () { context('when no lowest allowed severity was set yet', function () { it('returns false for all the severities', async function () { diff --git a/test/kill_switch/helpers/enums.js b/test/kill_switch/helpers/enums.js new file mode 100644 index 000000000..01d118b73 --- /dev/null +++ b/test/kill_switch/helpers/enums.js @@ -0,0 +1,7 @@ +const ACTION = { CHECK: 0, IGNORE: 1, DENY: 2 } +const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } + +module.exports = { + ACTION, + SEVERITY +} diff --git a/test/kill_switch/helpers/events.js b/test/kill_switch/helpers/events.js new file mode 100644 index 000000000..de6e1b63a --- /dev/null +++ b/test/kill_switch/helpers/events.js @@ -0,0 +1,5 @@ +const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] + +module.exports = { + getEventArgument +} diff --git a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js index e9dc5e572..91be1ff6b 100644 --- a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js +++ b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js @@ -1,4 +1,6 @@ const { assertRevert } = require('../../helpers/assertThrow') +const { ACTION, SEVERITY } = require('../helpers/enums') +const { getEventArgument } = require('../helpers/events') const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -10,10 +12,6 @@ const KernelKillSwitch = artifacts.require('KernelBinaryKillSwitch') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } - -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] - contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app @@ -53,8 +51,8 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) - const SET_IGNORED_CONTRACTS_ROLE = await killSwitchedDao.SET_IGNORED_CONTRACTS_ROLE() - await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_IGNORED_CONTRACTS_ROLE, root, { from: root }) + const SET_CONTRACT_ACTION_ROLE = await killSwitchedDao.SET_CONTRACT_ACTION_ROLE() + await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) }) beforeEach('create sample app from DAO with kernel binary kill switch', async () => { @@ -84,18 +82,30 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = }) } - context('when there is no bug registered', () => { - context('when the contract being called is not ignored', () => { + const itExecutesTheCallWhenNotDenied = () => { + context('when the contract being called is checked', () => { itExecutesTheCall() }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + await killSwitchedDao.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) }) itExecutesTheCall() }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitchedDao.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + } + + context('when there is no bug registered', () => { + itExecutesTheCallWhenNotDenied() }) context('when there is a bug registered', () => { @@ -104,17 +114,25 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = }) context('when the bug was not fixed yet', () => { - context('when the contract being called is not ignored', () => { + context('when the contract being called is checked', () => { itDoesNotExecuteTheCall() }) context('when the contract being called is ignored', () => { beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) + await killSwitchedDao.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) }) itExecutesTheCall() }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitchedDao.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) }) context('when the bug was already fixed', () => { @@ -122,17 +140,7 @@ contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) = await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when the contract being called is not ignored', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractIgnore(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) + itExecutesTheCallWhenNotDenied() }) }) }) diff --git a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js index de4f48dc1..6165b5dff 100644 --- a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js +++ b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js @@ -1,4 +1,6 @@ +const { ACTION, SEVERITY } = require('../helpers/enums') const { assertRevert } = require('../../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -10,10 +12,6 @@ const KernelKillSwitch = artifacts.require('KernelSeveritiesKillSwitch') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } - -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] - contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app @@ -53,6 +51,8 @@ contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) + const SET_CONTRACT_ACTION_ROLE = await killSwitchedDao.SET_CONTRACT_ACTION_ROLE() + await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchedDao.SET_LOWEST_ALLOWED_SEVERITY_ROLE() await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) @@ -105,7 +105,7 @@ contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone context('when the bug was not fixed yet', () => { context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() + itDoesNotExecuteTheCall() }) context('when there is a lowest allowed severity set for the contract being called', () => { From 00b76b945bb208090027d3135f5acb632fe9afb7 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Mon, 29 Apr 2019 11:28:48 -0300 Subject: [PATCH 08/37] contracts: fix linting issues --- contracts/kill_switch/base/IssuesRegistry.sol | 2 +- contracts/kill_switch/base/KillSwitch.sol | 26 +++++++++++++------ .../kernel/KernelBinaryKillSwitch.sol | 4 ++- .../kill_switch/kernel/KernelKillSwitch.sol | 2 +- .../kernel/KernelSeveritiesKillSwitch.sol | 4 ++- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/contracts/kill_switch/base/IssuesRegistry.sol b/contracts/kill_switch/base/IssuesRegistry.sol index 1cad627dd..8a84359fb 100644 --- a/contracts/kill_switch/base/IssuesRegistry.sol +++ b/contracts/kill_switch/base/IssuesRegistry.sol @@ -24,7 +24,7 @@ contract IssuesRegistry is AragonApp { return issuesSeverity[entry]; } - function setSeverityFor(address entry, Severity severity) authP(SET_ENTRY_SEVERITY_ROLE, arr(entry, msg.sender)) public { + 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); } diff --git a/contracts/kill_switch/base/KillSwitch.sol b/contracts/kill_switch/base/KillSwitch.sol index 3305cc938..0228e3f86 100644 --- a/contracts/kill_switch/base/KillSwitch.sol +++ b/contracts/kill_switch/base/KillSwitch.sol @@ -14,12 +14,12 @@ contract KillSwitch { 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 setContractAction(address _contract, ContractAction _action) external; - function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool); function isContractIgnored(address _contract) public view returns (bool) { @@ -32,20 +32,30 @@ contract KillSwitch { 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 (!_shouldEvaluateCall(_base, _instance, _sender, _data, _value)) { + return false; + } // if the call should be denied, then deny given call - if (isContractDenied(_base)) return true; + if (isContractDenied(_base)) { + return true; + } // if the contract issues are ignored, then allow given call - if (isContractIgnored(_base)) return false; + if (isContractIgnored(_base)) { + return false; + } // if the issues registry has not been set, then allow given call - if (issuesRegistry == address(0)) return false; + 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 (isSeverityIgnored(_base, _severityFound)) { + return false; + } // if none of the conditions above were met, then deny given call return true; @@ -58,7 +68,7 @@ contract KillSwitch { * block information, among many other options. * @return Always true by default. */ - function _shouldEvaluateCall(address /*_base*/, address /*_instance*/, address /*_sender*/, bytes /*_data*/, uint256 /*_value*/) internal returns (bool) { + function _shouldEvaluateCall(address, address, address, bytes, uint256) internal returns (bool) { return true; } diff --git a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol index 8c8d3eeb7..f3f3749b1 100644 --- a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol +++ b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol @@ -5,7 +5,9 @@ import "../base/BinaryKillSwitch.sol"; contract KernelBinaryKillSwitch is KernelKillSwitch, BinaryKillSwitch { - constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} + constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public { + // solium-disable-previous-line no-empty-blocks + } function setContractAction(address _contract, ContractAction _action) external diff --git a/contracts/kill_switch/kernel/KernelKillSwitch.sol b/contracts/kill_switch/kernel/KernelKillSwitch.sol index 16c96a691..8f2261509 100644 --- a/contracts/kill_switch/kernel/KernelKillSwitch.sol +++ b/contracts/kill_switch/kernel/KernelKillSwitch.sol @@ -35,6 +35,6 @@ contract KernelKillSwitch is Kernel, KillSwitch { /* not correspond to the application call in this context. */ /**************************************************************************************************/ - return super._shouldEvaluateCall(_base, _instance ,_sender, _data, _value); + return super._shouldEvaluateCall(_base, _instance, _sender, _data, _value); } } diff --git a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol index 00503324b..5dc83147c 100644 --- a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol +++ b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol @@ -5,7 +5,9 @@ import "../base/SeveritiesKillSwitch.sol"; contract KernelSeveritiesKillSwitch is KernelKillSwitch, SeveritiesKillSwitch { - constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} + constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public { + // solium-disable-previous-line no-empty-blocks + } function setContractAction(address _contract, ContractAction _action) external From df3568f7f4111503e463df24871e1e28fad1076a Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 30 Apr 2019 14:51:39 -0300 Subject: [PATCH 09/37] kill-switch: cleanup models leaving only app level --- contracts/factory/DAOFactory.sol | 21 - .../kill_switch/{base => }/IssuesRegistry.sol | 2 +- .../kill_switch/{base => }/KillSwitch.sol | 62 ++- .../kill_switch/{app => }/KillSwitchedApp.sol | 12 +- .../kill_switch/app/AppBinaryKillSwitch.sol | 14 - contracts/kill_switch/app/AppKillSwitch.sol | 15 - .../app/AppSeveritiesKillSwitch.sol | 21 - .../kill_switch/base/BinaryKillSwitch.sol | 11 - .../kill_switch/base/SeveritiesKillSwitch.sol | 25 - .../kernel/KernelBinaryKillSwitch.sol | 18 - .../kill_switch/kernel/KernelKillSwitch.sol | 40 -- .../kernel/KernelSeveritiesKillSwitch.sol | 25 - ...SwitchedAppMock.sol => KillSwitchMock.sol} | 8 +- ...hedAppMock.sol => KillSwitchedAppMock.sol} | 8 +- .../app/SeveritiesKillSwitchedAppMock.sol | 21 - .../kernel/KernelKillSwitchAppMock.sol | 27 - test/{kill_switch => }/helpers/events.js | 0 .../{base => }/IssuesRegistry.test.js | 8 +- test/kill_switch/KillSwitch.test.js | 457 ++++++++++++++++ test/kill_switch/KillSwitchCustom.test.js | 264 +++++++++ .../app/AppBinaryKillSwitch.test.js | 320 ----------- .../app/AppSeveritiesKillSwitch.test.js | 507 ------------------ .../base/itBehavesLikeBinaryKillSwitch.js | 86 --- .../base/itBehavesLikeSeveritiesKillSwitch.js | 143 ----- test/kill_switch/{helpers => }/enums.js | 0 .../kernel/KernelBinaryKillSwitch.test.js | 147 ----- .../kernel/KernelSeveritiesKillSwitch.test.js | 175 ------ 27 files changed, 787 insertions(+), 1650 deletions(-) rename contracts/kill_switch/{base => }/IssuesRegistry.sol (96%) rename contracts/kill_switch/{base => }/KillSwitch.sol (62%) rename contracts/kill_switch/{app => }/KillSwitchedApp.sol (55%) delete mode 100644 contracts/kill_switch/app/AppBinaryKillSwitch.sol delete mode 100644 contracts/kill_switch/app/AppKillSwitch.sol delete mode 100644 contracts/kill_switch/app/AppSeveritiesKillSwitch.sol delete mode 100644 contracts/kill_switch/base/BinaryKillSwitch.sol delete mode 100644 contracts/kill_switch/base/SeveritiesKillSwitch.sol delete mode 100644 contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol delete mode 100644 contracts/kill_switch/kernel/KernelKillSwitch.sol delete mode 100644 contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol rename contracts/test/mocks/{kill_switch/app/BinaryKillSwitchedAppMock.sol => KillSwitchMock.sol} (71%) rename contracts/test/mocks/{kill_switch/app/AppKillSwitchedAppMock.sol => KillSwitchedAppMock.sol} (60%) delete mode 100644 contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol delete mode 100644 contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol rename test/{kill_switch => }/helpers/events.js (100%) rename test/kill_switch/{base => }/IssuesRegistry.test.js (93%) create mode 100644 test/kill_switch/KillSwitch.test.js create mode 100644 test/kill_switch/KillSwitchCustom.test.js delete mode 100644 test/kill_switch/app/AppBinaryKillSwitch.test.js delete mode 100644 test/kill_switch/app/AppSeveritiesKillSwitch.test.js delete mode 100644 test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js delete mode 100644 test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js rename test/kill_switch/{helpers => }/enums.js (100%) delete mode 100644 test/kill_switch/kernel/KernelBinaryKillSwitch.test.js delete mode 100644 test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index c88ac9771..f56b0fbac 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -3,7 +3,6 @@ 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"; @@ -54,26 +53,6 @@ contract DAOFactory { 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 - * @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)); - - if (address(regFactory) == address(0)) { - dao.initialize(_issuesRegistry, baseACL, _root); - } else { - dao.initialize(_issuesRegistry, baseACL, address(this)); - _setupNewDaoPermissions(_root, Kernel(dao)); - } - - emit DeployDAO(address(dao)); - return dao; - } - function _setupNewDaoPermissions(address _root, Kernel _dao) internal { ACL acl = ACL(_dao.acl()); bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE(); diff --git a/contracts/kill_switch/base/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol similarity index 96% rename from contracts/kill_switch/base/IssuesRegistry.sol rename to contracts/kill_switch/IssuesRegistry.sol index 8a84359fb..2eb6f8e63 100644 --- a/contracts/kill_switch/base/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -1,6 +1,6 @@ pragma solidity 0.4.24; -import "../../apps/AragonApp.sol"; +import "../apps/AragonApp.sol"; contract IssuesRegistry is AragonApp { diff --git a/contracts/kill_switch/base/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol similarity index 62% rename from contracts/kill_switch/base/KillSwitch.sol rename to contracts/kill_switch/KillSwitch.sol index 0228e3f86..4ee30fe9b 100644 --- a/contracts/kill_switch/base/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -3,24 +3,56 @@ pragma solidity 0.4.24; import "./IssuesRegistry.sol"; -contract KillSwitch { +contract KillSwitch is AragonApp { + bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); + bytes32 constant public SET_LOWEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_LOWEST_ALLOWED_SEVERITY_ROLE"); enum ContractAction { Check, Ignore, Deny } IssuesRegistry public issuesRegistry; mapping (address => ContractAction) internal contractActions; + mapping (address => IssuesRegistry.Severity) internal lowestAllowedSeverityByContract; event IssuesRegistrySet(address issuesRegistry, address sender); event ContractActionSet(address contractAddress, ContractAction action); + event LowestAllowedSeveritySet(address indexed _contract, IssuesRegistry.Severity severity); - function setContractAction(address _contract, ContractAction _action) external; + function initialize(IssuesRegistry _issuesRegistry) external onlyInit { + initialized(); + _setIssuesRegistry(_issuesRegistry); + } + + function setContractAction(address _contract, ContractAction _action) + external + authP(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender)) + { + contractActions[_contract] = _action; + emit ContractActionSet(_contract, _action); + } + + function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) + external + authP(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) + { + lowestAllowedSeverityByContract[_contract] = _severity; + emit LowestAllowedSeveritySet(_contract, _severity); + } + + function setIssuesRegistry(IssuesRegistry _issuesRegistry) + external + authP(SET_ISSUES_REGISTRY_ROLE, arr(msg.sender)) + { + _setIssuesRegistry(_issuesRegistry); + } function getContractAction(address _contract) public view returns (ContractAction) { return contractActions[_contract]; } - function isSeverityIgnored(address _contract, IssuesRegistry.Severity _severity) public view returns (bool); + function getLowestAllowedSeverity(address _contract) public view returns (IssuesRegistry.Severity) { + return lowestAllowedSeverityByContract[_contract]; + } function isContractIgnored(address _contract) public view returns (bool) { return getContractAction(_contract) == ContractAction.Ignore; @@ -30,6 +62,12 @@ contract KillSwitch { return getContractAction(_contract) == ContractAction.Deny; } + function isSeverityIgnored(address _contract) public view returns (bool) { + IssuesRegistry.Severity severityFound = issuesRegistry.getSeverityFor(_contract); + IssuesRegistry.Severity lowestAllowedSeverity = getLowestAllowedSeverity(_contract); + return lowestAllowedSeverity >= severityFound; + } + 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)) { @@ -52,8 +90,7 @@ contract KillSwitch { } // if the contract severity found is ignored, then allow given call - IssuesRegistry.Severity _severityFound = issuesRegistry.getSeverityFor(_base); - if (isSeverityIgnored(_base, _severityFound)) { + if (isSeverityIgnored(_base)) { return false; } @@ -61,6 +98,11 @@ contract KillSwitch { return true; } + function _setIssuesRegistry(IssuesRegistry _issuesRegistry) internal { + issuesRegistry = _issuesRegistry; + emit IssuesRegistrySet(_issuesRegistry, msg.sender); + } + /** * @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 @@ -71,14 +113,4 @@ contract KillSwitch { 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); - } } diff --git a/contracts/kill_switch/app/KillSwitchedApp.sol b/contracts/kill_switch/KillSwitchedApp.sol similarity index 55% rename from contracts/kill_switch/app/KillSwitchedApp.sol rename to contracts/kill_switch/KillSwitchedApp.sol index d091433a9..5b5664c28 100644 --- a/contracts/kill_switch/app/KillSwitchedApp.sol +++ b/contracts/kill_switch/KillSwitchedApp.sol @@ -1,23 +1,23 @@ pragma solidity 0.4.24; -import "./AppKillSwitch.sol"; -import "../../apps/AragonApp.sol"; +import "./KillSwitch.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; + KillSwitch internal killSwitch; modifier killSwitched { - bool _isCallAllowed = !appKillSwitch.shouldDenyCallingContract(_baseApp(), address(this), msg.sender, msg.data, msg.value); + bool _isCallAllowed = !killSwitch.shouldDenyCallingContract(_baseApp(), address(this), msg.sender, msg.data, msg.value); require(_isCallAllowed, ERROR_CONTRACT_CALL_NOT_ALLOWED); _; } - function initialize(AppKillSwitch _appKillSwitch) public onlyInit { + function initialize(KillSwitch _killSwitch) public onlyInit { initialized(); - appKillSwitch = _appKillSwitch; + killSwitch = _killSwitch; } function _baseApp() internal view returns (address) { diff --git a/contracts/kill_switch/app/AppBinaryKillSwitch.sol b/contracts/kill_switch/app/AppBinaryKillSwitch.sol deleted file mode 100644 index b813755bd..000000000 --- a/contracts/kill_switch/app/AppBinaryKillSwitch.sol +++ /dev/null @@ -1,14 +0,0 @@ -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); - } -} diff --git a/contracts/kill_switch/app/AppKillSwitch.sol b/contracts/kill_switch/app/AppKillSwitch.sol deleted file mode 100644 index a3e5569ff..000000000 --- a/contracts/kill_switch/app/AppKillSwitch.sol +++ /dev/null @@ -1,15 +0,0 @@ -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()); - } -} diff --git a/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol b/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol deleted file mode 100644 index 005732f9b..000000000 --- a/contracts/kill_switch/app/AppSeveritiesKillSwitch.sol +++ /dev/null @@ -1,21 +0,0 @@ -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); - } -} diff --git a/contracts/kill_switch/base/BinaryKillSwitch.sol b/contracts/kill_switch/base/BinaryKillSwitch.sol deleted file mode 100644 index e5f507edd..000000000 --- a/contracts/kill_switch/base/BinaryKillSwitch.sol +++ /dev/null @@ -1,11 +0,0 @@ -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; - } -} diff --git a/contracts/kill_switch/base/SeveritiesKillSwitch.sol b/contracts/kill_switch/base/SeveritiesKillSwitch.sol deleted file mode 100644 index e38867944..000000000 --- a/contracts/kill_switch/base/SeveritiesKillSwitch.sol +++ /dev/null @@ -1,25 +0,0 @@ -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); - } -} diff --git a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol b/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol deleted file mode 100644 index f3f3749b1..000000000 --- a/contracts/kill_switch/kernel/KernelBinaryKillSwitch.sol +++ /dev/null @@ -1,18 +0,0 @@ -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); - } -} diff --git a/contracts/kill_switch/kernel/KernelKillSwitch.sol b/contracts/kill_switch/kernel/KernelKillSwitch.sol deleted file mode 100644 index 8f2261509..000000000 --- a/contracts/kill_switch/kernel/KernelKillSwitch.sol +++ /dev/null @@ -1,40 +0,0 @@ -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); - } -} diff --git a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol b/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol deleted file mode 100644 index 5dc83147c..000000000 --- a/contracts/kill_switch/kernel/KernelSeveritiesKillSwitch.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity 0.4.24; - -import "./KernelKillSwitch.sol"; -import "../base/SeveritiesKillSwitch.sol"; - - -contract KernelSeveritiesKillSwitch is KernelKillSwitch, SeveritiesKillSwitch { - 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); - } - - function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) - external - auth(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) - { - _setLowestAllowedSeverity(_contract, _severity); - } -} diff --git a/contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol b/contracts/test/mocks/KillSwitchMock.sol similarity index 71% rename from contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol rename to contracts/test/mocks/KillSwitchMock.sol index d746aad91..429a1c352 100644 --- a/contracts/test/mocks/kill_switch/app/BinaryKillSwitchedAppMock.sol +++ b/contracts/test/mocks/KillSwitchMock.sol @@ -1,16 +1,16 @@ pragma solidity 0.4.24; -import "./AppKillSwitchedAppMock.sol"; -import "../../../../kill_switch/app/AppBinaryKillSwitch.sol"; +import "./KillSwitchedAppMock.sol"; +import "../../kill_switch/KillSwitch.sol"; -contract AppBinaryKillSwitchMock is AppBinaryKillSwitch { +contract KillSwitchMock is KillSwitch { function _shouldEvaluateCall(address /*_base*/, address _instance, address _sender, bytes _data, uint256 /*_value*/) internal returns (bool) { bytes4 methodID; assembly { methodID := mload(add(_data, 0x20)) } // since this will act for every tx of the app, we provide a whitelist of functions - AppKillSwitchedAppMock app = AppKillSwitchedAppMock(_instance); + KillSwitchedAppMock app = KillSwitchedAppMock(_instance); // if called method is #reset, and the sender is the owner, do not evaluate if (methodID == app.reset.selector && _sender == app.owner()) return false; diff --git a/contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol b/contracts/test/mocks/KillSwitchedAppMock.sol similarity index 60% rename from contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol rename to contracts/test/mocks/KillSwitchedAppMock.sol index 2bce791c1..13b295d98 100644 --- a/contracts/test/mocks/kill_switch/app/AppKillSwitchedAppMock.sol +++ b/contracts/test/mocks/KillSwitchedAppMock.sol @@ -1,14 +1,14 @@ pragma solidity 0.4.24; -import "../../../../kill_switch/app/KillSwitchedApp.sol"; +import "../../kill_switch/KillSwitchedApp.sol"; -contract AppKillSwitchedAppMock is KillSwitchedApp { +contract KillSwitchedAppMock is KillSwitchedApp { address public owner; uint256 internal data; - function initialize(AppKillSwitch _appKillSwitch, address _owner) public onlyInit { - super.initialize(_appKillSwitch); + function initialize(KillSwitch _killSwitch, address _owner) public onlyInit { + super.initialize(_killSwitch); data = 42; owner = _owner; } diff --git a/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol deleted file mode 100644 index bbe682a1e..000000000 --- a/contracts/test/mocks/kill_switch/app/SeveritiesKillSwitchedAppMock.sol +++ /dev/null @@ -1,21 +0,0 @@ -pragma solidity 0.4.24; - -import "./AppKillSwitchedAppMock.sol"; -import "../../../../kill_switch/app/AppSeveritiesKillSwitch.sol"; - - -contract AppSeveritiesKillSwitchMock is AppSeveritiesKillSwitch { - function _shouldEvaluateCall(address /*_base*/, address _instance, address _sender, bytes _data, uint256 /*_value*/) internal returns (bool) { - bytes4 methodID; - assembly { methodID := mload(add(_data, 0x20)) } - - // since this will act for every tx of the app, we provide a whitelist of functions - AppKillSwitchedAppMock app = AppKillSwitchedAppMock(_instance); - - // if called method is #reset, and the sender is the owner, do not evaluate - if (methodID == app.reset.selector && _sender == app.owner()) return false; - - // evaluate otherwise - return true; - } -} diff --git a/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol b/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol deleted file mode 100644 index 9bfec0be7..000000000 --- a/contracts/test/mocks/kill_switch/kernel/KernelKillSwitchAppMock.sol +++ /dev/null @@ -1,27 +0,0 @@ -pragma solidity 0.4.24; - -import "../../../../apps/AragonApp.sol"; - - -contract KernelKillSwitchAppMock is AragonApp { - address public owner; - uint256 internal data; - - function initialize(address _owner) public onlyInit { - initialized(); - data = 42; - owner = _owner; - } - - function read() public view returns (uint256) { - return data; - } - - function write(uint256 _data) public { - data = _data; - } - - function reset() public { - data = 0; - } -} diff --git a/test/kill_switch/helpers/events.js b/test/helpers/events.js similarity index 100% rename from test/kill_switch/helpers/events.js rename to test/helpers/events.js diff --git a/test/kill_switch/base/IssuesRegistry.test.js b/test/kill_switch/IssuesRegistry.test.js similarity index 93% rename from test/kill_switch/base/IssuesRegistry.test.js rename to test/kill_switch/IssuesRegistry.test.js index bdc78d618..9310229e3 100644 --- a/test/kill_switch/base/IssuesRegistry.test.js +++ b/test/kill_switch/IssuesRegistry.test.js @@ -1,5 +1,5 @@ -const { SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') +const { SEVERITY } = require('./enums') +const { assertRevert } = require('../helpers/assertThrow') const { getEventArgument } = require('../helpers/events') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -28,8 +28,8 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { }) beforeEach('create issues registry', async () => { - const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) + const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) await issuesRegistry.initialize() const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() await acl.createPermission(owner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) diff --git a/test/kill_switch/KillSwitch.test.js b/test/kill_switch/KillSwitch.test.js new file mode 100644 index 000000000..bd48d4fd6 --- /dev/null +++ b/test/kill_switch/KillSwitch.test.js @@ -0,0 +1,457 @@ +const { ACTION, SEVERITY } = require('./enums') +const { assertRevert } = require('../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') + +const KillSwitch = artifacts.require('KillSwitch') +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +contract('KillSwitch', ([_, root, owner, securityPartner]) => { + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase + let registryFactory, dao, acl, issuesRegistry, app, killSwitch + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + killSwitchBase = await KillSwitch.new() + issuesRegistryBase = await IssuesRegistry.new() + appBase = await KillSwitchedApp.new() + }) + + before('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const kernelReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry', async () => { + const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switch', async () => { + const receipt = await dao.newAppInstance('0x1235', killSwitchBase.address, '0x', false, { from: root }) + killSwitch = KillSwitch.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await killSwitch.initialize(issuesRegistry.address) + const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) + const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(killSwitch.address, owner) + }) + + describe('isContractIgnored', function () { + context('when the contract is checked', function () { + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractIgnored(appBase.address)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractIgnored(appBase.address)) + }) + }) + }) + + describe('isContractDenied', function () { + context('when the contract is not denied', function () { + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) + }) + + context('when the contract is ignored', function () { + beforeEach('ignore contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + }) + }) + }) + + describe('setContractAction', function () { + context('when the sender is the owner', function () { + const from = owner + + context('when there was no action set yet', function () { + it('sets a new action', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + }) + }) + + context('when there was an action already set', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + }) + + it('changes the contract action', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from }) + + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.IGNORE) + }) + }) + }) + + context('when the sender is not the owner', function () { + it('reverts', async function () { + await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY)) + }) + }) + }) + + describe('isSeverityIgnored', function () { + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + it('returns false', async () => { + assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + it('returns false', async () => { + assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + }) + }) + }) + + describe('setLowestAllowedSeverity', function () { + context('when the contract is the owner', function () { + const from = owner + + context('when there was no severity set', function () { + it('sets the lowest allowed severity', async function () { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + + assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.HIGH) + }) + }) + + context('when there was a previous severity set', function () { + beforeEach('set lowest allowed severity', async function () { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) + assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.LOW) + }) + + it('changes the lowest allowed severity', async function () { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) + + assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.MID) + }) + }) + }) + + context('when the sender is not the owner', function () { + it('reverts', async function () { + await assertRevert(killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID)) + }) + }) + }) + + describe('integration', () => { + context('when the function being called is not tagged', () => { + + const itExecutesTheCallEvenIfDenied = () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + context('when the contract being called is not denied', () => { + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itExecutesTheCall() + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallEvenIfDenied() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallEvenIfDenied() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallEvenIfDenied() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + itExecutesTheCallEvenIfDenied() + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallEvenIfDenied() + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallEvenIfDenied() + }) + }) + }) + }) + + context('when the function being called is tagged', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + const itExecutesTheCallWhenNotDenied = () => { + context('when the contract being called is checked', () => { + itExecutesTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallWhenNotDenied() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall() + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallWhenNotDenied() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + }) + }) + }) + }) + }) +}) diff --git a/test/kill_switch/KillSwitchCustom.test.js b/test/kill_switch/KillSwitchCustom.test.js new file mode 100644 index 000000000..ef66e1ff0 --- /dev/null +++ b/test/kill_switch/KillSwitchCustom.test.js @@ -0,0 +1,264 @@ +const { ACTION, SEVERITY } = require('./enums') +const { assertRevert } = require('../helpers/assertThrow') +const { getEventArgument } = require('../helpers/events') + +const KillSwitch = artifacts.require('KillSwitchMock') +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase + let registryFactory, dao, acl, issuesRegistry, app, killSwitch + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + killSwitchBase = await KillSwitch.new() + issuesRegistryBase = await IssuesRegistry.new() + appBase = await KillSwitchedApp.new() + }) + + before('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const kernelReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create issues registry', async () => { + const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await issuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switch', async () => { + const receipt = await dao.newAppInstance('0x1235', killSwitchBase.address, '0x', false, { from: root }) + killSwitch = KillSwitch.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await killSwitch.initialize(issuesRegistry.address) + const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) + const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(killSwitch.address, owner) + }) + + describe('custom kill switch handling', () => { + const itExecutesTheCall = (from) => { + it('executes the call', async () => { + await app.reset({ from }) + assert.equal(await app.read(), 0) + }) + } + + const itDoesNotExecuteTheCall = (from) => { + it('does not execute the call', async () => { + await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + const itExecutesTheCallEvenIfDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(from) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itExecutesTheCall(from) + }) + } + + const itExecutesTheCallWhenNotDenied = (from) => { + context('when the contract being called is checked', () => { + itExecutesTheCall(from) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(from) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(from) + }) + } + + const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) + + context('when the sender is not the owner', () => { + itExecutesTheCallWhenNotDenied(anyone) + }) + } + + context('when there is no bug registered', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was not fixed yet', () => { + context('when there is no lowest allowed severity set for the contract being called', () => { + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) + + context('when the sender is not the owner', () => { + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall(anyone) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(anyone) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(anyone) + }) + }) + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + context('when the sender is the owner', () => { + itExecutesTheCallEvenIfDenied(owner) + }) + + context('when the sender is not the owner', () => { + context('when the contract being called is checked', () => { + itDoesNotExecuteTheCall(anyone) + }) + + context('when the contract being called is ignored', () => { + beforeEach('ignore calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + }) + + itExecutesTheCall(anyone) + }) + + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall(anyone) + }) + }) + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + }) + }) + + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no lowest allowed severity set for the contract being called', () => { + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + + context('when there is a lowest allowed severity set for the contract being called', () => { + context('when the lowest allowed severity is under the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + + context('when the lowest allowed severity is equal to the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + + context('when the lowest allowed severity is greater than the reported bug severity', () => { + beforeEach('set lowest allowed severity', async () => { + await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() + }) + }) + }) + }) + }) +}) diff --git a/test/kill_switch/app/AppBinaryKillSwitch.test.js b/test/kill_switch/app/AppBinaryKillSwitch.test.js deleted file mode 100644 index 7ae743735..000000000 --- a/test/kill_switch/app/AppBinaryKillSwitch.test.js +++ /dev/null @@ -1,320 +0,0 @@ -const { ACTION, SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') -const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') - -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') -const AppBinaryKillSwitch = artifacts.require('AppBinaryKillSwitchMock') - -const ACL = artifacts.require('ACL') -const Kernel = artifacts.require('Kernel') -const DAOFactory = artifacts.require('DAOFactory') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -contract('AppBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase - let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch - - before('deploy base implementations', async () => { - kernelBase = await Kernel.new(true) // petrify immediately - aclBase = await ACL.new() - registryFactory = await EVMScriptRegistryFactory.new() - appKillSwitchBase = await AppBinaryKillSwitch.new() - issuesRegistryBase = await IssuesRegistry.new() - appBase = await KillSwitchedApp.new() - }) - - before('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) - const kernelReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - - beforeEach('create issues registry', async () => { - const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create app kill switch', async () => { - const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) - appKillSwitch = AppBinaryKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) - await appKillSwitch.initialize(issuesRegistry.address) - const SET_CONTRACT_ACTION_ROLE = await appKillSwitchBase.SET_CONTRACT_ACTION_ROLE() - await acl.createPermission(owner, appKillSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - }) - - beforeEach('create kill switched app', async () => { - const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) - await app.initialize(appKillSwitch.address, owner) - }) - - describe('binary kill switch', function () { - beforeEach('bind kill switch', function () { - this.killSwitch = appKillSwitch - }) - - itBehavesLikeBinaryKillSwitch(owner, anyone) - }) - - describe('integration', () => { - context('when the function being called is not tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - context('when there is no bug registered', () => { - context('when the contract being called is checked', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - context('when the contract being called is checked', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - }) - - context('when the function being called is tagged', () => { - context('when the function being called is always evaluated', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - } - - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallWhenNotDenied = () => { - context('when the contract being called is checked', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - } - - context('when there is no bug registered', () => { - itExecutesTheCallWhenNotDenied() - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(owner) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - }) - }) - - context('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from) => { - it('executes the call', async () => { - await app.reset({ from }) - assert.equal(await app.read(), 0) - }) - } - - const itDoesNotExecuteTheCall = (from) => { - it('does not execute the call', async () => { - await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallEvenIfDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall(from) - }) - } - - const itExecutesTheCallWhenNotDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(from) - }) - } - - const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCallWhenNotDenied(anyone) - }) - } - - context('when there is no bug registered', () => { - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(anyone) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCall(anyone) - }) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(anyone) - }) - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - }) - }) - }) -}) diff --git a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js b/test/kill_switch/app/AppSeveritiesKillSwitch.test.js deleted file mode 100644 index 160080212..000000000 --- a/test/kill_switch/app/AppSeveritiesKillSwitch.test.js +++ /dev/null @@ -1,507 +0,0 @@ -const { ACTION, SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') -const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') - -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KillSwitchedApp = artifacts.require('AppKillSwitchedAppMock') -const AppSeveritiesKillSwitch = artifacts.require('AppSeveritiesKillSwitchMock') - -const ACL = artifacts.require('ACL') -const Kernel = artifacts.require('Kernel') -const DAOFactory = artifacts.require('DAOFactory') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -contract('AppSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let kernelBase, aclBase, appBase, appKillSwitchBase, issuesRegistryBase - let registryFactory, dao, acl, issuesRegistry, app, appKillSwitch - - before('deploy base implementations', async () => { - kernelBase = await Kernel.new(true) // petrify immediately - aclBase = await ACL.new() - registryFactory = await EVMScriptRegistryFactory.new() - appKillSwitchBase = await AppSeveritiesKillSwitch.new() - issuesRegistryBase = await IssuesRegistry.new() - appBase = await KillSwitchedApp.new() - }) - - before('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) - const kernelReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - - beforeEach('create issues registry', async () => { - const issuesRegistryReceipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create app kill switch', async () => { - const appKillSwitchReceipt = await dao.newAppInstance('0x1235', appKillSwitchBase.address, '0x', false, { from: root }) - appKillSwitch = AppSeveritiesKillSwitch.at(getEventArgument(appKillSwitchReceipt, 'NewAppProxy', 'proxy')) - await appKillSwitch.initialize(issuesRegistry.address) - const SET_CONTRACT_ACTION_ROLE = await appKillSwitchBase.SET_CONTRACT_ACTION_ROLE() - await acl.createPermission(owner, appKillSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await appKillSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() - await acl.createPermission(owner, appKillSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create kill switched app', async () => { - const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) - await app.initialize(appKillSwitch.address, owner) - }) - - describe('binary kill switch', function () { - beforeEach('bind kill switch', function () { - this.killSwitch = appKillSwitch - }) - - itBehavesLikeSeveritiesKillSwitch(owner, anyone) - }) - - describe('integration', () => { - context('when the function being called is not tagged', () => { - - const itExecutesTheCallEvenIfDenied = () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - context('when the contract being called is not denied', () => { - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall() - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallEvenIfDenied() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallEvenIfDenied() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - itExecutesTheCallEvenIfDenied() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - }) - }) - }) - - context('when the function being called is tagged', () => { - describe('when the function being called is always evaluated', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - } - - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallWhenNotDenied = () => { - context('when the contract being called is checked', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - }) - }) - }) - }) - - describe('when the function being called is evaluated only when the sender is not the owner', () => { - const itExecutesTheCall = (from) => { - it('executes the call', async () => { - await app.reset({ from }) - assert.equal(await app.read(), 0) - }) - } - - const itDoesNotExecuteTheCall = (from) => { - it('does not execute the call', async () => { - await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallEvenIfDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall(from) - }) - } - - const itExecutesTheCallWhenNotDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(from) - }) - } - - const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCallWhenNotDenied(anyone) - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(anyone) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(anyone) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(anyone) - }) - }) - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(anyone) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(anyone) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await appKillSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(anyone) - }) - }) - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await appKillSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - }) - }) - }) - }) - }) -}) diff --git a/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js b/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js deleted file mode 100644 index 043845b39..000000000 --- a/test/kill_switch/base/itBehavesLikeBinaryKillSwitch.js +++ /dev/null @@ -1,86 +0,0 @@ -const { ACTION, SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') - -module.exports = function (owner, anAddress) { - describe('isContractIgnored', function () { - context('when the contract is checked', function () { - it('returns false', async function () { - assert.isFalse(await this.killSwitch.isContractIgnored(anAddress)) - }) - }) - - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from: owner }) - }) - - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) - }) - }) - }) - - describe('isContractDenied', function () { - context('when the contract is not denied', function () { - it('returns false', async function () { - assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from: owner }) - }) - - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - }) - - describe('setContractAction', function () { - context('when the sender is the owner', function () { - const from = owner - - context('when there was no action set yet', function () { - it('sets a new action', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) - - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - - context('when there was an action already set', function () { - beforeEach('deny contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - - it('changes the contract action', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from }) - - assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) - assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - }) - - context('when the sender is not the owner', function () { - it('reverts', async function () { - await assertRevert(this.killSwitch.setContractAction(anAddress, ACTION.DENY)) - }) - }) - }) - - describe('isSeverityIgnored', function () { - it('returns true for none severities', async function () { - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.NONE)) - }) - - it('returns false for all the severities', async function () { - for (const key of Object.keys(SEVERITY).slice(1)) { - assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY[key])) - } - }) - }) -} diff --git a/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js b/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js deleted file mode 100644 index d6e8f0730..000000000 --- a/test/kill_switch/base/itBehavesLikeSeveritiesKillSwitch.js +++ /dev/null @@ -1,143 +0,0 @@ -const { ACTION, SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') - -module.exports = function (owner, anAddress) { - describe('isContractIgnored', function () { - context('when the contract is checked', function () { - it('returns false', async function () { - assert.isFalse(await this.killSwitch.isContractIgnored(anAddress)) - }) - }) - - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from: owner }) - }) - - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) - }) - }) - }) - - describe('isContractDenied', function () { - context('when the contract is not denied', function () { - it('returns false', async function () { - assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from: owner }) - }) - - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - }) - - describe('setContractAction', function () { - context('when the sender is the owner', function () { - const from = owner - - context('when there was no action set yet', function () { - it('sets a new action', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) - - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - - context('when there was an action already set', function () { - beforeEach('deny contract', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.DENY, { from }) - assert.isTrue(await this.killSwitch.isContractDenied(anAddress)) - }) - - it('changes the contract action', async function () { - await this.killSwitch.setContractAction(anAddress, ACTION.IGNORE, { from }) - - assert.isTrue(await this.killSwitch.isContractIgnored(anAddress)) - assert.isFalse(await this.killSwitch.isContractDenied(anAddress)) - }) - }) - }) - - context('when the sender is not the owner', function () { - it('reverts', async function () { - await assertRevert(this.killSwitch.setContractAction(anAddress, ACTION.DENY)) - }) - }) - }) - - describe('isSeverityIgnored', function () { - context('when no lowest allowed severity was set yet', function () { - it('returns false for all the severities', async function () { - for (const key of Object.keys(SEVERITY).slice(1)) { - assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY[key])) - } - }) - }) - - context('when a lowest allowed severity was set', function () { - beforeEach('set a lowest allowed severity', async function () { - await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID, { from: owner }) - }) - - context('when the given severity is lower than the one set', function () { - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.LOW)) - }) - }) - - context('when the given severity is equal to the one set', function () { - it('returns true', async function () { - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.MID)) - }) - }) - - context('when the given severity is greater than the one set', function () { - it('returns false', async function () { - assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) - }) - }) - }) - }) - - describe('setLowestAllowedSeverity', function () { - context('when the contract is the owner', function () { - const from = owner - - context('when there was no severity set', function () { - it('sets the lowest allowed severity', async function () { - await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.HIGH, { from }) - - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) - assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.CRITICAL)) - }) - }) - - context('when there was a previous severity set', function () { - beforeEach('set lowest allowed severity', async function () { - await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.LOW, { from }) - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.LOW)) - }) - - it('changes the lowest allowed severity', async function () { - await this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID, { from }) - - assert.isTrue(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.MID)) - assert.isFalse(await this.killSwitch.isSeverityIgnored(anAddress, SEVERITY.HIGH)) - }) - }) - }) - - context('when the sender is not the owner', function () { - it('reverts', async function () { - await assertRevert(this.killSwitch.setLowestAllowedSeverity(anAddress, SEVERITY.MID)) - }) - }) - }) -} diff --git a/test/kill_switch/helpers/enums.js b/test/kill_switch/enums.js similarity index 100% rename from test/kill_switch/helpers/enums.js rename to test/kill_switch/enums.js diff --git a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js b/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js deleted file mode 100644 index 91be1ff6b..000000000 --- a/test/kill_switch/kernel/KernelBinaryKillSwitch.test.js +++ /dev/null @@ -1,147 +0,0 @@ -const { assertRevert } = require('../../helpers/assertThrow') -const { ACTION, SEVERITY } = require('../helpers/enums') -const { getEventArgument } = require('../helpers/events') -const itBehavesLikeBinaryKillSwitch = require('../base/itBehavesLikeBinaryKillSwitch') - -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') - -const ACL = artifacts.require('ACL') -const RegularKernel = artifacts.require('Kernel') -const KernelKillSwitch = artifacts.require('KernelBinaryKillSwitch') -const DAOFactory = artifacts.require('DAOFactory') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -contract('KernelBinaryKillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory - let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app - - before('deploy base implementations', async () => { - regularKernelBase = await RegularKernel.new(true) // petrify immediately - killSwitchedKernelBase = await KernelKillSwitch.new(true) // petrify immediately - aclBase = await ACL.new() - appBase = await KernelKillSwitchAppMock.new() - issuesRegistryBase = await IssuesRegistry.new() - registryFactory = await EVMScriptRegistryFactory.new() - }) - - beforeEach('deploy DAO with regular kernel', async () => { - const regularDaoFactory = await DAOFactory.new(regularKernelBase.address, aclBase.address, registryFactory.address) - const regularKernelReceipt = await regularDaoFactory.newDAO(root) - regularDao = RegularKernel.at(getEventArgument(regularKernelReceipt, 'DeployDAO', 'dao')) - regularAcl = ACL.at(await regularDao.acl()) - - const APP_MANAGER_ROLE = await regularKernelBase.APP_MANAGER_ROLE() - await regularAcl.createPermission(root, regularDao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - - beforeEach('create issues registry app from DAO with regular kernel', async () => { - const issuesRegistryReceipt = await regularDao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await regularAcl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('deploy DAO with kernel binary kill switch', async () => { - const killSwitchedDaoFactory = await DAOFactory.new(killSwitchedKernelBase.address, aclBase.address, registryFactory.address) - const killSwitchedKernelReceipt = await killSwitchedDaoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) - killSwitchedDao = KernelKillSwitch.at(getEventArgument(killSwitchedKernelReceipt, 'DeployDAO', 'dao')) - killSwitchedAcl = ACL.at(await killSwitchedDao.acl()) - - const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() - await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) - const SET_CONTRACT_ACTION_ROLE = await killSwitchedDao.SET_CONTRACT_ACTION_ROLE() - await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - }) - - beforeEach('create sample app from DAO with kernel binary kill switch', async () => { - const appReceipt = await killSwitchedDao.newAppInstance('0x1235', appBase.address, '0x', false, { from: root }) - app = KernelKillSwitchAppMock.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) - await app.initialize(owner) - }) - - describe('binary kill switch', function () { - beforeEach('bind kill switch', function () { - this.killSwitch = killSwitchedDao - }) - - itBehavesLikeBinaryKillSwitch(owner, anyone) - }) - - describe('integration', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallWhenNotDenied = () => { - context('when the contract being called is checked', () => { - itExecutesTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitchedDao.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - } - - context('when there is no bug registered', () => { - itExecutesTheCallWhenNotDenied() - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall() - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitchedDao.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitchedDao.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - itExecutesTheCallWhenNotDenied() - }) - }) - }) -}) diff --git a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js b/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js deleted file mode 100644 index 6165b5dff..000000000 --- a/test/kill_switch/kernel/KernelSeveritiesKillSwitch.test.js +++ /dev/null @@ -1,175 +0,0 @@ -const { ACTION, SEVERITY } = require('../helpers/enums') -const { assertRevert } = require('../../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') -const itBehavesLikeSeveritiesKillSwitch = require('../base/itBehavesLikeSeveritiesKillSwitch') - -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KernelKillSwitchAppMock = artifacts.require('KernelKillSwitchAppMock') - -const ACL = artifacts.require('ACL') -const RegularKernel = artifacts.require('Kernel') -const KernelKillSwitch = artifacts.require('KernelSeveritiesKillSwitch') -const DAOFactory = artifacts.require('DAOFactory') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -contract('KernelSeveritiesKillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let killSwitchedKernelBase, regularKernelBase, aclBase, appBase, issuesRegistryBase, registryFactory - let regularDao, regularAcl, killSwitchedDao, killSwitchedAcl, issuesRegistry, app - - before('deploy base implementations', async () => { - regularKernelBase = await RegularKernel.new(true) // petrify immediately - killSwitchedKernelBase = await KernelKillSwitch.new(true) // petrify immediately - aclBase = await ACL.new() - appBase = await KernelKillSwitchAppMock.new() - issuesRegistryBase = await IssuesRegistry.new() - registryFactory = await EVMScriptRegistryFactory.new() - }) - - before('deploy DAO with regular kernel', async () => { - const regularDaoFactory = await DAOFactory.new(regularKernelBase.address, aclBase.address, registryFactory.address) - const regularKernelReceipt = await regularDaoFactory.newDAO(root) - regularDao = RegularKernel.at(getEventArgument(regularKernelReceipt, 'DeployDAO', 'dao')) - regularAcl = ACL.at(await regularDao.acl()) - - const APP_MANAGER_ROLE = await regularKernelBase.APP_MANAGER_ROLE() - await regularAcl.createPermission(root, regularDao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - - beforeEach('create issues registry app from DAO with regular kernel', async () => { - const issuesRegistryReceipt = await regularDao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(issuesRegistryReceipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await regularAcl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('deploy DAO with kernel severities kill switch', async () => { - const killSwitchedDaoFactory = await DAOFactory.new(killSwitchedKernelBase.address, aclBase.address, registryFactory.address) - const killSwitchedKernelReceipt = await killSwitchedDaoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) - killSwitchedDao = KernelKillSwitch.at(getEventArgument(killSwitchedKernelReceipt, 'DeployDAO', 'dao')) - killSwitchedAcl = ACL.at(await killSwitchedDao.acl()) - - const APP_MANAGER_ROLE = await killSwitchedKernelBase.APP_MANAGER_ROLE() - await killSwitchedAcl.createPermission(root, killSwitchedDao.address, APP_MANAGER_ROLE, root, { from: root }) - const SET_CONTRACT_ACTION_ROLE = await killSwitchedDao.SET_CONTRACT_ACTION_ROLE() - await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchedDao.SET_LOWEST_ALLOWED_SEVERITY_ROLE() - await killSwitchedAcl.createPermission(owner, killSwitchedDao.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create sample app from DAO with kernel severities kill switch', async () => { - const appReceipt = await killSwitchedDao.newAppInstance('0x1235', appBase.address, '0x', false, { from: root }) - app = KernelKillSwitchAppMock.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) - await app.initialize(owner) - }) - - describe('severities kill switch', function () { - beforeEach('bind kill switch', function () { - this.killSwitch = killSwitchedDao - }) - - itBehavesLikeSeveritiesKillSwitch(owner, anyone) - }) - - describe('integration', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.read(), 'KERNEL_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itDoesNotExecuteTheCall() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCall() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitchedDao.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - }) - }) - }) -}) From b6c55315068c82a8bc605510de06524affd3996d Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 30 Apr 2019 14:54:56 -0300 Subject: [PATCH 10/37] kill-switch: use `highest` instead of `lowest` allowed severities --- contracts/kill_switch/KillSwitch.sol | 22 ++-- test/kill_switch/KillSwitch.test.js | 134 +++++++++++----------- test/kill_switch/KillSwitchCustom.test.js | 18 +-- 3 files changed, 87 insertions(+), 87 deletions(-) diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index 4ee30fe9b..d9ad87b5a 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -6,17 +6,17 @@ import "./IssuesRegistry.sol"; contract KillSwitch is AragonApp { bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); - bytes32 constant public SET_LOWEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_LOWEST_ALLOWED_SEVERITY_ROLE"); + bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); enum ContractAction { Check, Ignore, Deny } IssuesRegistry public issuesRegistry; mapping (address => ContractAction) internal contractActions; - mapping (address => IssuesRegistry.Severity) internal lowestAllowedSeverityByContract; + mapping (address => IssuesRegistry.Severity) internal highestAllowedSeverityByContract; event IssuesRegistrySet(address issuesRegistry, address sender); event ContractActionSet(address contractAddress, ContractAction action); - event LowestAllowedSeveritySet(address indexed _contract, IssuesRegistry.Severity severity); + event HighestAllowedSeveritySet(address indexed _contract, IssuesRegistry.Severity severity); function initialize(IssuesRegistry _issuesRegistry) external onlyInit { initialized(); @@ -31,12 +31,12 @@ contract KillSwitch is AragonApp { emit ContractActionSet(_contract, _action); } - function setLowestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) + function setHighestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) external - authP(SET_LOWEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) + authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) { - lowestAllowedSeverityByContract[_contract] = _severity; - emit LowestAllowedSeveritySet(_contract, _severity); + highestAllowedSeverityByContract[_contract] = _severity; + emit HighestAllowedSeveritySet(_contract, _severity); } function setIssuesRegistry(IssuesRegistry _issuesRegistry) @@ -50,8 +50,8 @@ contract KillSwitch is AragonApp { return contractActions[_contract]; } - function getLowestAllowedSeverity(address _contract) public view returns (IssuesRegistry.Severity) { - return lowestAllowedSeverityByContract[_contract]; + function getHighestAllowedSeverity(address _contract) public view returns (IssuesRegistry.Severity) { + return highestAllowedSeverityByContract[_contract]; } function isContractIgnored(address _contract) public view returns (bool) { @@ -64,8 +64,8 @@ contract KillSwitch is AragonApp { function isSeverityIgnored(address _contract) public view returns (bool) { IssuesRegistry.Severity severityFound = issuesRegistry.getSeverityFor(_contract); - IssuesRegistry.Severity lowestAllowedSeverity = getLowestAllowedSeverity(_contract); - return lowestAllowedSeverity >= severityFound; + IssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_contract); + return highestAllowedSeverity >= severityFound; } function shouldDenyCallingContract(address _base, address _instance, address _sender, bytes _data, uint256 _value) public returns (bool) { diff --git a/test/kill_switch/KillSwitch.test.js b/test/kill_switch/KillSwitch.test.js index bd48d4fd6..b6b6cba36 100644 --- a/test/kill_switch/KillSwitch.test.js +++ b/test/kill_switch/KillSwitch.test.js @@ -47,8 +47,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { await killSwitch.initialize(issuesRegistry.address) const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + const SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { @@ -128,15 +128,15 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { describe('isSeverityIgnored', function () { context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { it('returns true', async () => { assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) }) }) - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) it('returns true', async () => { @@ -150,16 +150,16 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { it('returns false', async () => { assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) }) }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) it('returns false', async () => { @@ -167,9 +167,9 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) }) - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) it('returns true', async () => { @@ -177,9 +177,9 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) }) - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) it('returns true', async () => { @@ -190,35 +190,35 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) }) - describe('setLowestAllowedSeverity', function () { + describe('setHighestAllowedSeverity', function () { context('when the contract is the owner', function () { const from = owner context('when there was no severity set', function () { - it('sets the lowest allowed severity', async function () { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + it('sets the highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) - assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.HIGH) + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.HIGH) }) }) context('when there was a previous severity set', function () { - beforeEach('set lowest allowed severity', async function () { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) - assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.LOW) + beforeEach('set highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.LOW) }) - it('changes the lowest allowed severity', async function () { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) + it('changes the highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) - assert.equal(await killSwitch.getLowestAllowedSeverity(appBase.address), SEVERITY.MID) + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.MID) }) }) }) context('when the sender is not the owner', function () { it('reverts', async function () { - await assertRevert(killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID)) + await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID)) }) }) }) @@ -247,13 +247,13 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { } context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { itExecutesTheCallEvenIfDenied() }) - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallEvenIfDenied() @@ -265,26 +265,26 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { itExecutesTheCallEvenIfDenied() }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { itExecutesTheCallEvenIfDenied() }) - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) itExecutesTheCallEvenIfDenied() }) - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) itExecutesTheCallEvenIfDenied() @@ -330,13 +330,13 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { } context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { itExecutesTheCallWhenNotDenied() }) - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallWhenNotDenied() @@ -349,7 +349,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { context('when the contract being called is checked', () => { itDoesNotExecuteTheCall() }) @@ -371,10 +371,10 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) context('when the contract being called is checked', () => { @@ -398,17 +398,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) }) - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) itExecutesTheCallWhenNotDenied() }) - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) itExecutesTheCallWhenNotDenied() @@ -421,30 +421,30 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - context('when there is no lowest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the contract being called', () => { itExecutesTheCallWhenNotDenied() }) - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallWhenNotDenied() }) - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) itExecutesTheCallWhenNotDenied() }) - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) itExecutesTheCallWhenNotDenied() diff --git a/test/kill_switch/KillSwitchCustom.test.js b/test/kill_switch/KillSwitchCustom.test.js index ef66e1ff0..c95b38a20 100644 --- a/test/kill_switch/KillSwitchCustom.test.js +++ b/test/kill_switch/KillSwitchCustom.test.js @@ -47,8 +47,8 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.initialize(issuesRegistry.address) const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - const SET_LOWEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_LOWEST_ALLOWED_SEVERITY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_LOWEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + const SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { @@ -132,7 +132,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when there is a lowest allowed severity set for the contract being called', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() @@ -176,7 +176,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when there is a lowest allowed severity set for the contract being called', () => { context('when the lowest allowed severity is under the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) context('when the sender is the owner', () => { @@ -208,7 +208,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when the lowest allowed severity is equal to the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() @@ -216,7 +216,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when the lowest allowed severity is greater than the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() @@ -236,7 +236,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when there is a lowest allowed severity set for the contract being called', () => { context('when the lowest allowed severity is under the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() @@ -244,7 +244,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when the lowest allowed severity is equal to the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() @@ -252,7 +252,7 @@ contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { context('when the lowest allowed severity is greater than the reported bug severity', () => { beforeEach('set lowest allowed severity', async () => { - await killSwitch.setLowestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) }) itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() From c20c608a1b8dd8d46e57d0e58a6de9eee62bc30c Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 30 Apr 2019 16:46:51 -0300 Subject: [PATCH 11/37] kill-switch: Support one issues registry per contract --- contracts/kill_switch/KillSwitch.sol | 70 ++++++++----- test/helpers/events.js | 8 +- test/kill_switch/KillSwitch.test.js | 143 ++++++++++++++++++++++++--- 3 files changed, 180 insertions(+), 41 deletions(-) diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index d9ad87b5a..9c3821c21 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -1,33 +1,50 @@ pragma solidity 0.4.24; import "./IssuesRegistry.sol"; +import "../common/IsContract.sol"; -contract KillSwitch is AragonApp { +contract KillSwitch is IsContract, AragonApp { + bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); + string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; + enum ContractAction { Check, Ignore, Deny } - IssuesRegistry public issuesRegistry; - mapping (address => ContractAction) internal contractActions; - mapping (address => IssuesRegistry.Severity) internal highestAllowedSeverityByContract; + struct Settings { + ContractAction action; + IssuesRegistry.Severity highestAllowedSeverity; + IssuesRegistry issuesRegistry; + } + + IssuesRegistry public defaultIssuesRegistry; + mapping (address => Settings) internal contractSettings; - event IssuesRegistrySet(address issuesRegistry, address sender); - event ContractActionSet(address contractAddress, ContractAction action); - event HighestAllowedSeveritySet(address indexed _contract, IssuesRegistry.Severity severity); + event DefaultIssuesRegistrySet(address issuesRegistry); + event ContractActionSet(address indexed contractAddress, ContractAction action); + event IssuesRegistrySet(address indexed contractAddress, address issuesRegistry); + event HighestAllowedSeveritySet(address indexed contractAddress, IssuesRegistry.Severity severity); - function initialize(IssuesRegistry _issuesRegistry) external onlyInit { + function initialize(IssuesRegistry _defaultIssuesRegistry) external onlyInit { initialized(); - _setIssuesRegistry(_issuesRegistry); + _setDefaultIssuesRegistry(_defaultIssuesRegistry); + } + + function setDefaultIssuesRegistry(IssuesRegistry _defaultIssuesRegistry) + external + authP(SET_DEFAULT_ISSUES_REGISTRY_ROLE, arr(msg.sender)) + { + _setDefaultIssuesRegistry(_defaultIssuesRegistry); } function setContractAction(address _contract, ContractAction _action) external authP(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender)) { - contractActions[_contract] = _action; + contractSettings[_contract].action = _action; emit ContractActionSet(_contract, _action); } @@ -35,23 +52,30 @@ contract KillSwitch is AragonApp { external authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) { - highestAllowedSeverityByContract[_contract] = _severity; + contractSettings[_contract].highestAllowedSeverity = _severity; emit HighestAllowedSeveritySet(_contract, _severity); } - function setIssuesRegistry(IssuesRegistry _issuesRegistry) + function setIssuesRegistry(address _contract, IssuesRegistry _issuesRegistry) external - authP(SET_ISSUES_REGISTRY_ROLE, arr(msg.sender)) + authP(SET_ISSUES_REGISTRY_ROLE, arr(_contract, msg.sender)) { - _setIssuesRegistry(_issuesRegistry); + require(isContract(_issuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); + contractSettings[_contract].issuesRegistry = _issuesRegistry; + emit IssuesRegistrySet(_contract, address(_issuesRegistry)); } function getContractAction(address _contract) public view returns (ContractAction) { - return contractActions[_contract]; + return contractSettings[_contract].action; } function getHighestAllowedSeverity(address _contract) public view returns (IssuesRegistry.Severity) { - return highestAllowedSeverityByContract[_contract]; + return contractSettings[_contract].highestAllowedSeverity; + } + + function getIssuesRegistry(address _contract) public view returns (IssuesRegistry) { + IssuesRegistry foundRegistry = contractSettings[_contract].issuesRegistry; + return foundRegistry == IssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; } function isContractIgnored(address _contract) public view returns (bool) { @@ -63,7 +87,7 @@ contract KillSwitch is AragonApp { } function isSeverityIgnored(address _contract) public view returns (bool) { - IssuesRegistry.Severity severityFound = issuesRegistry.getSeverityFor(_contract); + IssuesRegistry.Severity severityFound = getIssuesRegistry(_contract).getSeverityFor(_contract); IssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_contract); return highestAllowedSeverity >= severityFound; } @@ -84,11 +108,6 @@ contract KillSwitch is AragonApp { 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 if (isSeverityIgnored(_base)) { return false; @@ -98,9 +117,10 @@ contract KillSwitch is AragonApp { return true; } - function _setIssuesRegistry(IssuesRegistry _issuesRegistry) internal { - issuesRegistry = _issuesRegistry; - emit IssuesRegistrySet(_issuesRegistry, msg.sender); + function _setDefaultIssuesRegistry(IssuesRegistry _defaultIssuesRegistry) internal { + require(isContract(_defaultIssuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); + defaultIssuesRegistry = _defaultIssuesRegistry; + emit DefaultIssuesRegistrySet(address(_defaultIssuesRegistry)); } /** diff --git a/test/helpers/events.js b/test/helpers/events.js index de6e1b63a..a59f1e19f 100644 --- a/test/helpers/events.js +++ b/test/helpers/events.js @@ -1,5 +1,9 @@ -const getEventArgument = (receipt, event, arg) => receipt.logs.find(l => l.event === event).args[arg] +const getEvent = (receipt, event) => getEvents(receipt, event)[0] +const getEvents = (receipt, event) => receipt.logs.filter(l => l.event === event) +const getEventArgument = (receipt, event, arg) => getEvent(receipt, event).args[arg] module.exports = { - getEventArgument + getEvent, + getEvents, + getEventArgument, } diff --git a/test/kill_switch/KillSwitch.test.js b/test/kill_switch/KillSwitch.test.js index b6b6cba36..f0f9d99c0 100644 --- a/test/kill_switch/KillSwitch.test.js +++ b/test/kill_switch/KillSwitch.test.js @@ -1,6 +1,6 @@ const { ACTION, SEVERITY } = require('./enums') const { assertRevert } = require('../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') +const { getEvents, getEvent, getEventArgument } = require('../helpers/events') const KillSwitch = artifacts.require('KillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') @@ -11,9 +11,11 @@ const Kernel = artifacts.require('Kernel') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -contract('KillSwitch', ([_, root, owner, securityPartner]) => { +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase - let registryFactory, dao, acl, issuesRegistry, app, killSwitch + let registryFactory, dao, acl, defaultIssuesRegistry, specificIssuesRegistry, app, killSwitch before('deploy base implementations', async () => { kernelBase = await Kernel.new(true) // petrify immediately @@ -33,20 +35,36 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) }) - beforeEach('create issues registry', async () => { + beforeEach('create default issues registry', async () => { const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() + defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await defaultIssuesRegistry.initialize() const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + await acl.createPermission(securityPartner, defaultIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create specific issues registry', async () => { + const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + specificIssuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await specificIssuesRegistry.initialize() + const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + await acl.createPermission(securityPartner, specificIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switch', async () => { const receipt = await dao.newAppInstance('0x1235', killSwitchBase.address, '0x', false, { from: root }) killSwitch = KillSwitch.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await killSwitch.initialize(issuesRegistry.address) + await killSwitch.initialize(defaultIssuesRegistry.address) + + const SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + + const SET_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_ISSUES_REGISTRY_ROLE() + await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) + const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) + const SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) @@ -103,6 +121,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) }) + + it('emits an event', async () => { + const receipt = await await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + + const events = getEvents(receipt, 'ContractActionSet') + assert.equal(events.length, 1, 'number of ContractActionSet events does not match') + + const event = getEvent(receipt, 'ContractActionSet').args + assert.equal(event.action, ACTION.DENY, 'action does not match') + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + }) }) context('when there was an action already set', function () { @@ -120,8 +149,81 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) context('when the sender is not the owner', function () { + const from = anyone + it('reverts', async function () { - await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY)) + await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY, { from })) + }) + }) + }) + + describe('getIssuesRegistry', function () { + context('when there was no specific issues registry set', () => { + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + }) + }) + + context('when there is a specific issues registry set', () => { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from: owner }) + }) + + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + }) + }) + }) + + describe('setIssuesRegistry', function () { + context('when the sender is the owner', function () { + const from = owner + + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(appBase.address, ZERO_ADDRESS, { from })) + }) + }) + + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + }) + + it('emits an event', async () => { + const receipt = await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + + const events = getEvents(receipt, 'IssuesRegistrySet') + assert.equal(events.length, 1, 'number of IssuesRegistrySet events does not match') + + const event = getEvent(receipt, 'IssuesRegistrySet').args + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + }) + }) + + context('when there was a specific issues registry set', function () { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + }) + + it('changes the issues registry', async () => { + await killSwitch.setIssuesRegistry(appBase.address, defaultIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + }) + }) + }) + }) + + context('when the sender is not the owner', function () { + const from = anyone + + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from })) }) }) }) @@ -147,7 +249,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { context('when there is a bug registered', () => { beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) context('when there is no highest allowed severity set for the contract being called', () => { @@ -200,6 +302,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.HIGH) }) + + it('emits an event', async () => { + const receipt = await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + + const events = getEvents(receipt, 'HighestAllowedSeveritySet') + assert.equal(events.length, 1, 'number of ContractActionSet events does not match') + + const event = getEvent(receipt, 'HighestAllowedSeveritySet').args + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.severity, SEVERITY.HIGH, 'highest severity does not match') + }) }) context('when there was a previous severity set', function () { @@ -217,8 +330,10 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { }) context('when the sender is not the owner', function () { + const from = anyone + it('reverts', async function () { - await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID)) + await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from })) }) }) }) @@ -262,7 +377,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { context('when there is a bug registered', () => { beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) context('when there is no highest allowed severity set for the contract being called', () => { @@ -345,7 +460,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { context('when there is a bug registered', () => { beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) context('when the bug was not fixed yet', () => { @@ -418,7 +533,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner]) => { context('when the bug was already fixed', () => { beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) context('when there is no highest allowed severity set for the contract being called', () => { From 1f17dcb8514b770271ecaa1cfce5cb166ea2ee97 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 30 Apr 2019 17:03:32 -0300 Subject: [PATCH 12/37] kill-switch: drop custom programmatic handling --- contracts/kill_switch/KillSwitch.sol | 24 +- contracts/kill_switch/KillSwitchedApp.sol | 2 +- contracts/test/mocks/KillSwitchMock.sol | 21 -- test/kill_switch/KillSwitchCustom.test.js | 264 ---------------------- 4 files changed, 5 insertions(+), 306 deletions(-) delete mode 100644 contracts/test/mocks/KillSwitchMock.sol delete mode 100644 test/kill_switch/KillSwitchCustom.test.js diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index 9c3821c21..5bff9ab01 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -92,24 +92,19 @@ contract KillSwitch is IsContract, AragonApp { return highestAllowedSeverity >= severityFound; } - 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; - } - + function shouldDenyCallingContract(address _contract) public returns (bool) { // if the call should be denied, then deny given call - if (isContractDenied(_base)) { + if (isContractDenied(_contract)) { return true; } // if the contract issues are ignored, then allow given call - if (isContractIgnored(_base)) { + if (isContractIgnored(_contract)) { return false; } // if the contract severity found is ignored, then allow given call - if (isSeverityIgnored(_base)) { + if (isSeverityIgnored(_contract)) { return false; } @@ -122,15 +117,4 @@ contract KillSwitch is IsContract, AragonApp { defaultIssuesRegistry = _defaultIssuesRegistry; emit DefaultIssuesRegistrySet(address(_defaultIssuesRegistry)); } - - /** - * @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; - } } diff --git a/contracts/kill_switch/KillSwitchedApp.sol b/contracts/kill_switch/KillSwitchedApp.sol index 5b5664c28..31d3faf17 100644 --- a/contracts/kill_switch/KillSwitchedApp.sol +++ b/contracts/kill_switch/KillSwitchedApp.sol @@ -10,7 +10,7 @@ contract KillSwitchedApp is AragonApp { KillSwitch internal killSwitch; modifier killSwitched { - bool _isCallAllowed = !killSwitch.shouldDenyCallingContract(_baseApp(), address(this), msg.sender, msg.data, msg.value); + bool _isCallAllowed = !killSwitch.shouldDenyCallingContract(_baseApp()); require(_isCallAllowed, ERROR_CONTRACT_CALL_NOT_ALLOWED); _; } diff --git a/contracts/test/mocks/KillSwitchMock.sol b/contracts/test/mocks/KillSwitchMock.sol deleted file mode 100644 index 429a1c352..000000000 --- a/contracts/test/mocks/KillSwitchMock.sol +++ /dev/null @@ -1,21 +0,0 @@ -pragma solidity 0.4.24; - -import "./KillSwitchedAppMock.sol"; -import "../../kill_switch/KillSwitch.sol"; - - -contract KillSwitchMock is KillSwitch { - function _shouldEvaluateCall(address /*_base*/, address _instance, address _sender, bytes _data, uint256 /*_value*/) internal returns (bool) { - bytes4 methodID; - assembly { methodID := mload(add(_data, 0x20)) } - - // since this will act for every tx of the app, we provide a whitelist of functions - KillSwitchedAppMock app = KillSwitchedAppMock(_instance); - - // if called method is #reset, and the sender is the owner, do not evaluate - if (methodID == app.reset.selector && _sender == app.owner()) return false; - - // evaluate otherwise - return true; - } -} diff --git a/test/kill_switch/KillSwitchCustom.test.js b/test/kill_switch/KillSwitchCustom.test.js deleted file mode 100644 index c95b38a20..000000000 --- a/test/kill_switch/KillSwitchCustom.test.js +++ /dev/null @@ -1,264 +0,0 @@ -const { ACTION, SEVERITY } = require('./enums') -const { assertRevert } = require('../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') - -const KillSwitch = artifacts.require('KillSwitchMock') -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') - -const ACL = artifacts.require('ACL') -const Kernel = artifacts.require('Kernel') -const DAOFactory = artifacts.require('DAOFactory') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -contract('KillSwitchCustom', ([_, root, owner, securityPartner, anyone]) => { - let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase - let registryFactory, dao, acl, issuesRegistry, app, killSwitch - - before('deploy base implementations', async () => { - kernelBase = await Kernel.new(true) // petrify immediately - aclBase = await ACL.new() - registryFactory = await EVMScriptRegistryFactory.new() - killSwitchBase = await KillSwitch.new() - issuesRegistryBase = await IssuesRegistry.new() - appBase = await KillSwitchedApp.new() - }) - - before('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) - const kernelReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - - beforeEach('create issues registry', async () => { - const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create kill switch', async () => { - const receipt = await dao.newAppInstance('0x1235', killSwitchBase.address, '0x', false, { from: root }) - killSwitch = KillSwitch.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await killSwitch.initialize(issuesRegistry.address) - const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - const SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await app.initialize(killSwitch.address, owner) - }) - - describe('custom kill switch handling', () => { - const itExecutesTheCall = (from) => { - it('executes the call', async () => { - await app.reset({ from }) - assert.equal(await app.read(), 0) - }) - } - - const itDoesNotExecuteTheCall = (from) => { - it('does not execute the call', async () => { - await assertRevert(app.reset({ from }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } - - const itExecutesTheCallEvenIfDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itExecutesTheCall(from) - }) - } - - const itExecutesTheCallWhenNotDenied = (from) => { - context('when the contract being called is checked', () => { - itExecutesTheCall(from) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(from) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(from) - }) - } - - const itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner = () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - itExecutesTheCallWhenNotDenied(anyone) - }) - } - - context('when there is no bug registered', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when the bug was not fixed yet', () => { - context('when there is no lowest allowed severity set for the contract being called', () => { - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(anyone) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(anyone) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(anyone) - }) - }) - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - context('when the sender is the owner', () => { - itExecutesTheCallEvenIfDenied(owner) - }) - - context('when the sender is not the owner', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall(anyone) - }) - - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) - }) - - itExecutesTheCall(anyone) - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall(anyone) - }) - }) - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - }) - - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await issuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) - - context('when there is no lowest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when there is a lowest allowed severity set for the contract being called', () => { - context('when the lowest allowed severity is under the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is equal to the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - - context('when the lowest allowed severity is greater than the reported bug severity', () => { - beforeEach('set lowest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallUnlessItsDeniedAndSenderIsNotOwner() - }) - }) - }) - }) - }) -}) From 130c343b6dd2a6a5e2219d9798466d212ebae650 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 30 Apr 2019 21:26:53 -0300 Subject: [PATCH 13/37] kill-switch: integrate with aragonOS components --- contracts/apps/AragonApp.sol | 15 +++ contracts/factory/DAOFactory.sol | 39 +++++- contracts/kernel/IKernel.sol | 2 + contracts/kernel/Kernel.sol | 32 +++++ contracts/kernel/KernelConstants.sol | 2 + contracts/kill_switch/IIssuesRegistry.sol | 16 +++ contracts/kill_switch/IKillSwitch.sol | 10 ++ contracts/kill_switch/IssuesRegistry.sol | 19 ++- contracts/kill_switch/KillSwitch.sol | 76 ++++++------ contracts/kill_switch/KillSwitchedApp.sol | 26 ---- contracts/test/mocks/KillSwitchedAppMock.sol | 8 +- .../test/mocks/common/KeccakConstants.sol | 1 + .../test/mocks/kernel/KernelConstantsMock.sol | 1 + .../test/mocks/kernel/KernelOverloadMock.sol | 44 +++---- scripts/deploy-daofactory.js | 14 ++- test/contracts/apm/apm_registry.js | 4 +- test/contracts/common/keccak_constants.js | 8 +- test/contracts/ens/ens_subdomains.js | 5 +- test/contracts/factory/evm_script_factory.js | 5 +- test/contracts/kernel/kernel_apps.js | 74 ++++-------- test/contracts/kernel/kernel_lifecycle.js | 2 +- test/kill_switch/IssuesRegistry.test.js | 6 +- test/kill_switch/KillSwitch.test.js | 113 +++++++++++++----- 23 files changed, 318 insertions(+), 204 deletions(-) create mode 100644 contracts/kill_switch/IIssuesRegistry.sol create mode 100644 contracts/kill_switch/IKillSwitch.sol delete mode 100644 contracts/kill_switch/KillSwitchedApp.sol diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index f53c40721..44b921fe1 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -20,6 +20,7 @@ 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"; modifier auth(bytes32 _role) { require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); @@ -31,6 +32,12 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua _; } + modifier killSwitched { + bool _shouldDenyCall = kernel().killSwitch().shouldDenyCallingContract(_baseApp()); + require(!_shouldDenyCall, 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 @@ -65,4 +72,12 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua // Funds recovery via a vault is only available when used with a kernel return kernel().getRecoveryVault(); // if kernel is not set, it will revert } + + /** + * @dev Get the address of the base implementation for the current app + * @return Address of the base implementation + */ + function _baseApp() internal view returns (address) { + return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, appId()); + } } diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index f56b0fbac..bd5d3e6ac 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -3,6 +3,8 @@ pragma solidity 0.4.24; import "../kernel/IKernel.sol"; import "../kernel/Kernel.sol"; import "../kernel/KernelProxy.sol"; +import "../kill_switch/IKillSwitch.sol"; +import "../kill_switch/IIssuesRegistry.sol"; import "../acl/IACL.sol"; import "../acl/ACL.sol"; @@ -13,6 +15,7 @@ import "./EVMScriptRegistryFactory.sol"; contract DAOFactory { IKernel public baseKernel; IACL public baseACL; + IKillSwitch public baseKillSwitch; EVMScriptRegistryFactory public regFactory; event DeployDAO(address dao); @@ -24,7 +27,14 @@ contract DAOFactory { * @param _baseACL Base ACL * @param _regFactory EVMScriptRegistry factory */ - constructor(IKernel _baseKernel, IACL _baseACL, EVMScriptRegistryFactory _regFactory) public { + constructor( + IKernel _baseKernel, + IACL _baseACL, + IKillSwitch _baseKillSwitch, + EVMScriptRegistryFactory _regFactory + ) + public + { // No need to init as it cannot be killed by devops199 if (address(_regFactory) != address(0)) { regFactory = _regFactory; @@ -32,6 +42,7 @@ contract DAOFactory { baseKernel = _baseKernel; baseACL = _baseACL; + baseKillSwitch = _baseKillSwitch; } /** @@ -40,7 +51,7 @@ contract DAOFactory { * @return Newly created DAO */ function newDAO(address _root) public returns (Kernel) { - Kernel dao = Kernel(new KernelProxy(baseKernel)); + Kernel dao = _newDAO(); if (address(regFactory) == address(0)) { dao.initialize(baseACL, _root); @@ -49,6 +60,30 @@ contract DAOFactory { _setupNewDaoPermissions(_root, dao); } + 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 + * @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 + * @return Newly created DAO + */ + function newDAOWithKillSwitch(address _root, IIssuesRegistry _issuesRegistry) public returns (Kernel) { + Kernel dao = _newDAO(); + + if (address(regFactory) == address(0)) { + dao.initializeWithKillSwitch(baseACL, _root, baseKillSwitch, _issuesRegistry); + } else { + dao.initializeWithKillSwitch(baseACL, address(this), baseKillSwitch, _issuesRegistry); + _setupNewDaoPermissions(_root, Kernel(dao)); + } + + return dao; + } + + function _newDAO() internal returns (Kernel) { + Kernel dao = Kernel(new KernelProxy(baseKernel)); emit DeployDAO(address(dao)); return dao; } diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index e1a2b40e5..0d9b92a38 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -5,6 +5,7 @@ pragma solidity ^0.4.24; import "../acl/IACL.sol"; +import "../kill_switch/IKillSwitch.sol"; import "../common/IVaultRecoverable.sol"; @@ -16,6 +17,7 @@ interface IKernelEvents { // This should be an interface, but interfaces can't inherit yet :( contract IKernel is IKernelEvents, IVaultRecoverable { function acl() public view returns (IACL); + function killSwitch() public view returns (IKillSwitch); function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); function setApp(bytes32 namespace, bytes32 appId, address app) public; diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 1fc919055..331191ed9 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -10,6 +10,8 @@ import "../common/IsContract.sol"; import "../common/Petrifiable.sol"; import "../common/VaultRecoverable.sol"; import "../factory/AppProxyFactory.sol"; +import "../kill_switch/IKillSwitch.sol"; +import "../kill_switch/IIssuesRegistry.sol"; import "../lib/misc/ERCProxy.sol"; @@ -54,6 +56,27 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant recoveryVaultAppId = KERNEL_DEFAULT_VAULT_APP_ID; } + /** + * @dev Initialize can only be called once. It saves the block number in which it was initialized. + * @notice Initialize this kernel instance, its ACL setting `_permissionsCreator` as the entity that can create other permissions, and a KillSwitch instance setting `_issuesRegistry + * @param _baseAcl Address of base ACL app + * @param _permissionsCreator Entity that will be given permission over createPermission + * @param _baseKillSwitch Address of base KillSwitch app + * @param _issuesRegistry Issues registry that will act as the default source of truth to provide info about applications issues + */ + function initializeWithKillSwitch(IACL _baseAcl, address _permissionsCreator, IKillSwitch _baseKillSwitch, IIssuesRegistry _issuesRegistry) + public onlyInit + { + // Set and create ACL app + initialize(_baseAcl, _permissionsCreator); + + // Set and create KillSwitch app + _setApp(KERNEL_APP_BASES_NAMESPACE, KERNEL_DEFAULT_KILL_SWITCH_APP_ID, _baseKillSwitch); + IKillSwitch killSwitch = IKillSwitch(newAppProxy(this, KERNEL_DEFAULT_KILL_SWITCH_APP_ID)); + killSwitch.initialize(_issuesRegistry); + _setApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_KILL_SWITCH_APP_ID, killSwitch); + } + /** * @dev Create a new instance of an app linked to this kernel * @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase` @@ -169,6 +192,7 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant function APP_ADDR_NAMESPACE() external pure returns (bytes32) { return KERNEL_APP_ADDR_NAMESPACE; } function KERNEL_APP_ID() external pure returns (bytes32) { return KERNEL_CORE_APP_ID; } function DEFAULT_ACL_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_ACL_APP_ID; } + function DEFAULT_KILL_SWITCH_APP_ID() external pure returns (bytes32) { return KERNEL_DEFAULT_KILL_SWITCH_APP_ID; } /* solium-enable function-order, mixedcase */ /** @@ -197,6 +221,14 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant return IACL(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_ACL_APP_ID)); } + /** + * @dev Get the installed KillSwitch app + * @return KillSwitch app + */ + function killSwitch() public view returns (IKillSwitch) { + return IKillSwitch(getApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_KILL_SWITCH_APP_ID)); + } + /** * @dev Function called by apps to check ACL on kernel or to check permission status * @param _who Sender of the original call diff --git a/contracts/kernel/KernelConstants.sol b/contracts/kernel/KernelConstants.sol index 77816a74c..b54ab44ba 100644 --- a/contracts/kernel/KernelConstants.sol +++ b/contracts/kernel/KernelConstants.sol @@ -10,10 +10,12 @@ contract KernelAppIds { bytes32 internal constant KERNEL_CORE_APP_ID = apmNamehash("kernel"); bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = apmNamehash("acl"); bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("vault"); + bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = apmNamehash("killSwitch"); */ bytes32 internal constant KERNEL_CORE_APP_ID = 0x3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c; bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = 0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a; bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; + bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = 0x05b6cbc146cecc3a8014843768ab6e17332ef00418da7f6babf4ea94c76ab6a1; } diff --git a/contracts/kill_switch/IIssuesRegistry.sol b/contracts/kill_switch/IIssuesRegistry.sol new file mode 100644 index 000000000..92be768ea --- /dev/null +++ b/contracts/kill_switch/IIssuesRegistry.sol @@ -0,0 +1,16 @@ +pragma solidity 0.4.24; + + +contract IIssuesRegistry { + enum Severity { None, Low, Mid, High, Critical } + + event SeveritySet(address indexed entry, Severity severity, address sender); + + function initialize() external; + + function setSeverityFor(address entry, Severity severity) external; + + function isSeverityFor(address entry) public view returns (bool); + + function getSeverityFor(address entry) public view returns (Severity); +} diff --git a/contracts/kill_switch/IKillSwitch.sol b/contracts/kill_switch/IKillSwitch.sol new file mode 100644 index 000000000..f7ed881cc --- /dev/null +++ b/contracts/kill_switch/IKillSwitch.sol @@ -0,0 +1,10 @@ +pragma solidity 0.4.24; + +import "./IIssuesRegistry.sol"; + + +contract IKillSwitch { + function initialize(IIssuesRegistry _issuesRegistry) external; + + function shouldDenyCallingContract(address _contract) external returns (bool); +} diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol index 2eb6f8e63..657a841d4 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -1,21 +1,23 @@ pragma solidity 0.4.24; import "../apps/AragonApp.sol"; +import "./IIssuesRegistry.sol"; -contract IssuesRegistry is AragonApp { +contract IssuesRegistry is IIssuesRegistry, 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 { + function initialize() external onlyInit { initialized(); } + function setSeverityFor(address entry, Severity severity) external authP(SET_ENTRY_SEVERITY_ROLE, arr(entry, msg.sender)) { + issuesSeverity[entry] = severity; + emit SeveritySet(entry, severity, msg.sender); + } + function isSeverityFor(address entry) public view isInitialized returns (bool) { return issuesSeverity[entry] != Severity.None; } @@ -23,9 +25,4 @@ contract IssuesRegistry is AragonApp { 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); - } } diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index 5bff9ab01..88356d68c 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -1,10 +1,12 @@ pragma solidity 0.4.24; -import "./IssuesRegistry.sol"; +import "./IKillSwitch.sol"; +import "./IIssuesRegistry.sol"; +import "../apps/AragonApp.sol"; import "../common/IsContract.sol"; -contract KillSwitch is IsContract, AragonApp { +contract KillSwitch is IKillSwitch, IsContract, AragonApp { bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); @@ -16,24 +18,24 @@ contract KillSwitch is IsContract, AragonApp { struct Settings { ContractAction action; - IssuesRegistry.Severity highestAllowedSeverity; - IssuesRegistry issuesRegistry; + IIssuesRegistry.Severity highestAllowedSeverity; + IIssuesRegistry issuesRegistry; } - IssuesRegistry public defaultIssuesRegistry; + IIssuesRegistry public defaultIssuesRegistry; mapping (address => Settings) internal contractSettings; event DefaultIssuesRegistrySet(address issuesRegistry); event ContractActionSet(address indexed contractAddress, ContractAction action); event IssuesRegistrySet(address indexed contractAddress, address issuesRegistry); - event HighestAllowedSeveritySet(address indexed contractAddress, IssuesRegistry.Severity severity); + event HighestAllowedSeveritySet(address indexed contractAddress, IIssuesRegistry.Severity severity); - function initialize(IssuesRegistry _defaultIssuesRegistry) external onlyInit { + function initialize(IIssuesRegistry _defaultIssuesRegistry) external onlyInit { initialized(); _setDefaultIssuesRegistry(_defaultIssuesRegistry); } - function setDefaultIssuesRegistry(IssuesRegistry _defaultIssuesRegistry) + function setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) external authP(SET_DEFAULT_ISSUES_REGISTRY_ROLE, arr(msg.sender)) { @@ -48,7 +50,7 @@ contract KillSwitch is IsContract, AragonApp { emit ContractActionSet(_contract, _action); } - function setHighestAllowedSeverity(address _contract, IssuesRegistry.Severity _severity) + function setHighestAllowedSeverity(address _contract, IIssuesRegistry.Severity _severity) external authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) { @@ -56,7 +58,7 @@ contract KillSwitch is IsContract, AragonApp { emit HighestAllowedSeveritySet(_contract, _severity); } - function setIssuesRegistry(address _contract, IssuesRegistry _issuesRegistry) + function setIssuesRegistry(address _contract, IIssuesRegistry _issuesRegistry) external authP(SET_ISSUES_REGISTRY_ROLE, arr(_contract, msg.sender)) { @@ -65,17 +67,37 @@ contract KillSwitch is IsContract, AragonApp { emit IssuesRegistrySet(_contract, address(_issuesRegistry)); } + function shouldDenyCallingContract(address _contract) external returns (bool) { + // if the call should be denied, then deny given call + if (isContractDenied(_contract)) { + return true; + } + + // if the contract issues are ignored, then allow given call + if (isContractIgnored(_contract)) { + return false; + } + + // if the contract severity found is ignored, then allow given call + if (isSeverityIgnored(_contract)) { + return false; + } + + // if none of the conditions above were met, then deny given call + return true; + } + function getContractAction(address _contract) public view returns (ContractAction) { return contractSettings[_contract].action; } - function getHighestAllowedSeverity(address _contract) public view returns (IssuesRegistry.Severity) { + function getHighestAllowedSeverity(address _contract) public view returns (IIssuesRegistry.Severity) { return contractSettings[_contract].highestAllowedSeverity; } - function getIssuesRegistry(address _contract) public view returns (IssuesRegistry) { - IssuesRegistry foundRegistry = contractSettings[_contract].issuesRegistry; - return foundRegistry == IssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; + function getIssuesRegistry(address _contract) public view returns (IIssuesRegistry) { + IIssuesRegistry foundRegistry = contractSettings[_contract].issuesRegistry; + return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; } function isContractIgnored(address _contract) public view returns (bool) { @@ -87,32 +109,12 @@ contract KillSwitch is IsContract, AragonApp { } function isSeverityIgnored(address _contract) public view returns (bool) { - IssuesRegistry.Severity severityFound = getIssuesRegistry(_contract).getSeverityFor(_contract); - IssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_contract); + IIssuesRegistry.Severity severityFound = getIssuesRegistry(_contract).getSeverityFor(_contract); + IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_contract); return highestAllowedSeverity >= severityFound; } - function shouldDenyCallingContract(address _contract) public returns (bool) { - // if the call should be denied, then deny given call - if (isContractDenied(_contract)) { - return true; - } - - // if the contract issues are ignored, then allow given call - if (isContractIgnored(_contract)) { - return false; - } - - // if the contract severity found is ignored, then allow given call - if (isSeverityIgnored(_contract)) { - return false; - } - - // if none of the conditions above were met, then deny given call - return true; - } - - function _setDefaultIssuesRegistry(IssuesRegistry _defaultIssuesRegistry) internal { + function _setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) internal { require(isContract(_defaultIssuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); defaultIssuesRegistry = _defaultIssuesRegistry; emit DefaultIssuesRegistrySet(address(_defaultIssuesRegistry)); diff --git a/contracts/kill_switch/KillSwitchedApp.sol b/contracts/kill_switch/KillSwitchedApp.sol deleted file mode 100644 index 31d3faf17..000000000 --- a/contracts/kill_switch/KillSwitchedApp.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity 0.4.24; - -import "./KillSwitch.sol"; -import "../apps/AragonApp.sol"; - - -contract KillSwitchedApp is AragonApp { - string private constant ERROR_CONTRACT_CALL_NOT_ALLOWED = "APP_CONTRACT_CALL_NOT_ALLOWED"; - - KillSwitch internal killSwitch; - - modifier killSwitched { - bool _isCallAllowed = !killSwitch.shouldDenyCallingContract(_baseApp()); - require(_isCallAllowed, ERROR_CONTRACT_CALL_NOT_ALLOWED); - _; - } - - function initialize(KillSwitch _killSwitch) public onlyInit { - initialized(); - killSwitch = _killSwitch; - } - - function _baseApp() internal view returns (address) { - return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, appId()); - } -} diff --git a/contracts/test/mocks/KillSwitchedAppMock.sol b/contracts/test/mocks/KillSwitchedAppMock.sol index 13b295d98..d98f6c997 100644 --- a/contracts/test/mocks/KillSwitchedAppMock.sol +++ b/contracts/test/mocks/KillSwitchedAppMock.sol @@ -1,14 +1,14 @@ pragma solidity 0.4.24; -import "../../kill_switch/KillSwitchedApp.sol"; +import "../../apps/AragonApp.sol"; -contract KillSwitchedAppMock is KillSwitchedApp { +contract KillSwitchedAppMock is AragonApp { address public owner; uint256 internal data; - function initialize(KillSwitch _killSwitch, address _owner) public onlyInit { - super.initialize(_killSwitch); + function initialize(address _owner) public onlyInit { + initialized(); data = 42; owner = _owner; } diff --git a/contracts/test/mocks/common/KeccakConstants.sol b/contracts/test/mocks/common/KeccakConstants.sol index 6cc9a5bb5..274f8b5d9 100644 --- a/contracts/test/mocks/common/KeccakConstants.sol +++ b/contracts/test/mocks/common/KeccakConstants.sol @@ -22,6 +22,7 @@ contract KeccakConstants { bytes32 public constant KERNEL_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("kernel"))); bytes32 public constant DEFAULT_ACL_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("acl"))); bytes32 public constant DEFAULT_VAULT_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("vault"))); + bytes32 public constant DEFAULT_KILL_SWITCH_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("killSwitch"))); // ACL bytes32 public constant CREATE_PERMISSIONS_ROLE = keccak256(abi.encodePacked("CREATE_PERMISSIONS_ROLE")); diff --git a/contracts/test/mocks/kernel/KernelConstantsMock.sol b/contracts/test/mocks/kernel/KernelConstantsMock.sol index 47c634848..a7b263606 100644 --- a/contracts/test/mocks/kernel/KernelConstantsMock.sol +++ b/contracts/test/mocks/kernel/KernelConstantsMock.sol @@ -12,4 +12,5 @@ contract KernelConstantsMock is Kernel { function getKernelAppId() external pure returns (bytes32) { return KERNEL_CORE_APP_ID; } function getDefaultACLAppId() external pure returns (bytes32) { return KERNEL_DEFAULT_ACL_APP_ID; } function getDefaultVaultAppId() external pure returns (bytes32) { return KERNEL_DEFAULT_VAULT_APP_ID; } + function getDefaultKillSwitchAppId() external pure returns (bytes32) { return KERNEL_DEFAULT_KILL_SWITCH_APP_ID; } } diff --git a/contracts/test/mocks/kernel/KernelOverloadMock.sol b/contracts/test/mocks/kernel/KernelOverloadMock.sol index 66d87ec2b..57ba0014b 100644 --- a/contracts/test/mocks/kernel/KernelOverloadMock.sol +++ b/contracts/test/mocks/kernel/KernelOverloadMock.sol @@ -11,40 +11,26 @@ import "../../../lib/misc/ERCProxy.sol"; * NOTE: awkwardly, by default we have access to the full version of `newAppInstance()` but only the * minimized version for `newPinnedAppInstance()` */ -contract KernelOverloadMock { - Kernel public kernel; +contract KernelOverloadMock is Kernel { + constructor(bool _shouldPetrify) Kernel(_shouldPetrify) public {} - event NewAppProxy(address proxy); + // Overriding function to bypass Truffle's overloading issues + function newAppInstanceWithoutPayload(bytes32 _appId, address _appBase) public returns (ERCProxy) { + return super.newAppInstance(_appId, _appBase); + } - constructor(Kernel _kernel) public { - kernel = _kernel; + // Overriding function to bypass Truffle's overloading issues + function newAppInstanceWithPayload(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault) public returns (ERCProxy) { + return super.newAppInstance(_appId, _appBase, _initializePayload, _setDefault); } - /* - function newAppInstance(bytes32 _appId, address _appBase) - public - auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId)) - returns (ERCProxy appProxy) - */ - function newAppInstance(bytes32 _appId, address _appBase) - public - returns (ERCProxy appProxy) - { - appProxy = kernel.newAppInstance(_appId, _appBase); - emit NewAppProxy(appProxy); + // Overriding function to bypass Truffle's overloading issues + function newPinnedAppInstanceWithoutPayload(bytes32 _appId, address _appBase) public returns (ERCProxy) { + return super.newPinnedAppInstance(_appId, _appBase); } - /* - function newPinnedAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault) - public - auth(APP_MANAGER_ROLE, arr(KERNEL_APP_BASES_NAMESPACE, _appId)) - returns (ERCProxy appProxy) - */ - function newPinnedAppInstance(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault) - public - returns (ERCProxy appProxy) - { - appProxy = kernel.newPinnedAppInstance(_appId, _appBase, _initializePayload, _setDefault); - emit NewAppProxy(appProxy); + // Overriding function to bypass Truffle's overloading issues + function newPinnedAppInstanceWithPayload(bytes32 _appId, address _appBase, bytes _initializePayload, bool _setDefault) public returns (ERCProxy) { + return super.newPinnedAppInstance(_appId, _appBase, _initializePayload, _setDefault); } } diff --git a/scripts/deploy-daofactory.js b/scripts/deploy-daofactory.js index f3bfa9829..06c018ae8 100644 --- a/scripts/deploy-daofactory.js +++ b/scripts/deploy-daofactory.js @@ -6,6 +6,7 @@ const ZERO_ADDR = '0x0000000000000000000000000000000000000000' const defaultKernelBase = process.env.KERNEL_BASE const defaultAclBaseAddress = process.env.ACL_BASE +const defaultKillSwitchBaseAddress = process.env.KILL_SWITCH_BASE module.exports = async ( truffleExecCallback, @@ -23,7 +24,7 @@ module.exports = async ( const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') - + const KillSwitch = artifacts.require('KillSwitch') const DAOFactory = artifacts.require('DAOFactory') let kernelBase @@ -44,6 +45,15 @@ module.exports = async ( await logDeploy(aclBase, { verbose }) } + let killSwitchBase + if (defaultKillSwitchBaseAddress) { + killSwitchBase = KillSwitch.at(defaultKillSwitchBaseAddress) + log(`Skipping deploying new KillSwitch base, using provided address: ${defaultKillSwitchBaseAddress}`) + } else { + killSwitchBase = await KillSwitch.new() + await logDeploy(killSwitchBase, { verbose }) + } + let evmScriptRegistryFactory if (withEvmScriptRegistryFactory) { const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') @@ -53,6 +63,7 @@ module.exports = async ( const daoFactory = await DAOFactory.new( kernelBase.address, aclBase.address, + killSwitchBase.address, evmScriptRegistryFactory ? evmScriptRegistryFactory.address : ZERO_ADDR ) @@ -64,6 +75,7 @@ module.exports = async ( } else { return { aclBase, + killSwitchBase, daoFactory, evmScriptRegistryFactory, kernelBase, diff --git a/test/contracts/apm/apm_registry.js b/test/contracts/apm/apm_registry.js index 876a5ec8d..723dbd32e 100644 --- a/test/contracts/apm/apm_registry.js +++ b/test/contracts/apm/apm_registry.js @@ -8,6 +8,7 @@ const PublicResolver = artifacts.require('PublicResolver') const Kernel = artifacts.require('Kernel') const ACL = artifacts.require('ACL') +const KillSwitch = artifacts.require('KillSwitch') const DAOFactory = artifacts.require('DAOFactory') const APMRegistry = artifacts.require('APMRegistry') @@ -41,7 +42,8 @@ contract('APMRegistry', accounts => { const kernelBase = await Kernel.new(true) // petrify immediately const aclBase = await ACL.new() - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR) + const killSwitchBase = await KillSwitch.new() + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) }) beforeEach(async () => { diff --git a/test/contracts/common/keccak_constants.js b/test/contracts/common/keccak_constants.js index c6567bf6d..efba4facc 100644 --- a/test/contracts/common/keccak_constants.js +++ b/test/contracts/common/keccak_constants.js @@ -1,10 +1,6 @@ -const namehash = require('eth-ens-namehash').hash -const keccak_256 = require('js-sha3').keccak_256 - const getContract = name => artifacts.require(name) -const keccak256 = (name) => '0x' + keccak_256(name) -contract('Constants', accounts => { +contract('Constants', () => { let keccakConstants before(async () => { @@ -31,6 +27,7 @@ contract('Constants', accounts => { assert.equal(await kernelConstants.getKernelAppId(), await keccakConstants.KERNEL_APP_ID(), "kernel app id doesn't match") assert.equal(await kernelConstants.getDefaultACLAppId(), await keccakConstants.DEFAULT_ACL_APP_ID(), "default ACL id doesn't match") assert.equal(await kernelConstants.getDefaultVaultAppId(), await keccakConstants.DEFAULT_VAULT_APP_ID(), "default vault id doesn't match") + assert.equal(await kernelConstants.getDefaultKillSwitchAppId(), await keccakConstants.DEFAULT_KILL_SWITCH_APP_ID(), "default kill switch id doesn't match") assert.equal(await kernelConstants.getKernelCoreNamespace(), await keccakConstants.KERNEL_CORE_NAMESPACE(), "core namespace doesn't match") assert.equal(await kernelConstants.getKernelAppBasesNamespace(), await keccakConstants.KERNEL_APP_BASES_NAMESPACE(), "base namespace doesn't match") assert.equal(await kernelConstants.getKernelAppAddrNamespace(), await keccakConstants.KERNEL_APP_ADDR_NAMESPACE(), "app namespace doesn't match") @@ -39,6 +36,7 @@ contract('Constants', accounts => { assert.equal(await kernel.APP_MANAGER_ROLE(), await keccakConstants.APP_MANAGER_ROLE(), "app manager role doesn't match") assert.equal(await kernel.KERNEL_APP_ID(), await keccakConstants.KERNEL_APP_ID(), "app id doesn't match") assert.equal(await kernel.DEFAULT_ACL_APP_ID(), await keccakConstants.DEFAULT_ACL_APP_ID(), "default acl id doesn't match") + assert.equal(await kernel.DEFAULT_KILL_SWITCH_APP_ID(), await keccakConstants.DEFAULT_KILL_SWITCH_APP_ID(), "default kill switch id doesn't match") assert.equal(await kernel.CORE_NAMESPACE(), await keccakConstants.KERNEL_CORE_NAMESPACE(), "core namespace doesn't match") assert.equal(await kernel.APP_BASES_NAMESPACE(), await keccakConstants.KERNEL_APP_BASES_NAMESPACE(), "base namespace doesn't match") assert.equal(await kernel.APP_ADDR_NAMESPACE(), await keccakConstants.KERNEL_APP_ADDR_NAMESPACE(), "app namespace doesn't match") diff --git a/test/contracts/ens/ens_subdomains.js b/test/contracts/ens/ens_subdomains.js index f3168f684..b51e1cb81 100644 --- a/test/contracts/ens/ens_subdomains.js +++ b/test/contracts/ens/ens_subdomains.js @@ -4,10 +4,10 @@ const keccak256 = require('js-sha3').keccak_256 const ENS = artifacts.require('ENS') const ENSFactory = artifacts.require('ENSFactory') -const PublicResolver = artifacts.require('PublicResolver') const Kernel = artifacts.require('Kernel') const ACL = artifacts.require('ACL') +const KillSwitch = artifacts.require('KillSwitch') const APMRegistry = artifacts.require('APMRegistry') const AppProxyUpgradeable = artifacts.require('AppProxyUpgradeable') @@ -41,7 +41,8 @@ contract('ENSSubdomainRegistrar', accounts => { const kernelBase = await Kernel.new(true) // petrify immediately const aclBase = await ACL.new() - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR) + const killSwitchBase = await KillSwitch.new() + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) APP_BASES_NAMESPACE = await kernelBase.APP_BASES_NAMESPACE() }) diff --git a/test/contracts/factory/evm_script_factory.js b/test/contracts/factory/evm_script_factory.js index 4c81dd5a3..f582bee3b 100644 --- a/test/contracts/factory/evm_script_factory.js +++ b/test/contracts/factory/evm_script_factory.js @@ -1,8 +1,8 @@ -const { soliditySha3 } = require('web3-utils') const { createExecutorId, encodeCallScript } = require('../../helpers/evmScript') const Kernel = artifacts.require('Kernel') const ACL = artifacts.require('ACL') +const KillSwitch = artifacts.require('KillSwitch') const EVMScriptRegistry = artifacts.require('EVMScriptRegistry') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') @@ -28,9 +28,10 @@ contract('EVM Script Factory', accounts => { before(async () => { const kernelBase = await Kernel.new(true) // petrify immediately const aclBase = await ACL.new() + const killSwitchBase = await KillSwitch.new() regFact = await EVMScriptRegistryFactory.new() - daoFact = await DAOFactory.new(kernelBase.address, aclBase.address, regFact.address) + daoFact = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, regFact.address) callsScriptBase = await regFact.baseCallScript() evmScriptRegBase = EVMScriptRegistry.at(await regFact.baseReg()) const evmScriptRegConstants = await EVMScriptRegistryConstantsMock.new() diff --git a/test/contracts/kernel/kernel_apps.js b/test/contracts/kernel/kernel_apps.js index 6e6610035..19aaab82e 100644 --- a/test/contracts/kernel/kernel_apps.js +++ b/test/contracts/kernel/kernel_apps.js @@ -1,10 +1,9 @@ -const { assertRevert } = require('../../helpers/assertThrow') -const { onlyIf } = require('../../helpers/onlyIf') -const { getBalance } = require('../../helpers/web3') const { hash } = require('eth-ens-namehash') +const { onlyIf } = require('../../helpers/onlyIf') +const { assertRevert } = require('../../helpers/assertThrow') const ACL = artifacts.require('ACL') -const Kernel = artifacts.require('Kernel') +const Kernel = artifacts.require('KernelOverloadMock') const KernelProxy = artifacts.require('KernelProxy') const AppProxyUpgradeable = artifacts.require('AppProxyUpgradeable') const AppProxyPinned = artifacts.require('AppProxyPinned') @@ -13,7 +12,6 @@ const AppProxyPinned = artifacts.require('AppProxyPinned') const AppStub = artifacts.require('AppStub') const AppStub2 = artifacts.require('AppStub2') const ERCProxyMock = artifacts.require('ERCProxyMock') -const KernelOverloadMock = artifacts.require('KernelOverloadMock') const APP_ID = hash('stub.aragonpm.test') const EMPTY_BYTES = '0x' @@ -21,7 +19,7 @@ const ZERO_ADDR = '0x0000000000000000000000000000000000000000' contract('Kernel apps', accounts => { let aclBase, appBase1, appBase2 - let APP_BASES_NAMESPACE, APP_ADDR_NAMESPACE + let APP_BASES_NAMESPACE, APP_ADDR_NAMESPACE, APP_MANAGER_ROLE let UPGRADEABLE, FORWARDING const permissionsRoot = accounts[0] @@ -46,7 +44,7 @@ contract('Kernel apps', accounts => { // Test both the Kernel itself and the KernelProxy to make sure their behaviours are the same for (const kernelType of ['Kernel', 'KernelProxy']) { context(`> ${kernelType}`, () => { - let acl, kernel, kernelBase, app, appProxy + let acl, kernel, kernelBase before(async () => { if (kernelType === 'KernelProxy') { @@ -82,26 +80,14 @@ contract('Kernel apps', accounts => { }) }) - const newAppProxyMapping = { - 'AppProxy': 'newAppInstance', - 'AppProxyPinned': 'newPinnedAppInstance', - } - for (const appProxyType of Object.keys(newAppProxyMapping)) { - // NOTE: we have to do really hacky workarounds here due to truffle not supporting - // function overloads. - // Especially awful is how we only get the full version of `newAppInstance()` but - // not `newPinnedAppInstance()`, forcing us to apply the KernelOverloadMock on - // different proxy instances - let kernelOverload - const newInstanceFn = newAppProxyMapping[appProxyType] - + for (const appProxyType of ['AppProxy', 'AppProxyPinned']) { const onlyAppProxy = onlyIf(() => appProxyType === 'AppProxy') const onlyAppProxyPinned = onlyIf(() => appProxyType === 'AppProxyPinned') context(`> new ${appProxyType} instances`, () => { onlyAppProxy(() => it('creates a new upgradeable app proxy instance', async () => { - const receipt = await kernel.newAppInstance(APP_ID, appBase1.address, EMPTY_BYTES, false) + const receipt = await kernel.newAppInstanceWithPayload(APP_ID, appBase1.address, EMPTY_BYTES, false) const appProxy = AppProxyUpgradeable.at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) assert.equal(await appProxy.kernel(), kernel.address, "new appProxy instance's kernel should be set to the originating kernel") @@ -113,7 +99,7 @@ contract('Kernel apps', accounts => { onlyAppProxyPinned(() => it('creates a new non upgradeable app proxy instance', async () => { - const receipt = await kernel.newPinnedAppInstance(APP_ID, appBase1.address) + const receipt = await kernel.newPinnedAppInstanceWithoutPayload(APP_ID, appBase1.address) const appProxy = AppProxyPinned.at(receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy) assert.equal(await appProxy.kernel(), kernel.address, "new appProxy instance's kernel should be set to the originating kernel") @@ -124,31 +110,23 @@ contract('Kernel apps', accounts => { ) context('> full new app instance overload', async () => { - beforeEach(async () => { - if (appProxyType === 'AppProxy') { - // No need to apply the overload - kernelOverload = kernel - } else if (appProxyType === 'AppProxyPinned') { - kernelOverload = await KernelOverloadMock.new(kernel.address) - await acl.grantPermission(kernelOverload.address, kernel.address, APP_MANAGER_ROLE) - } - }) + const newInstanceFn = appProxyType === 'AppProxy' ? 'newAppInstanceWithPayload' : 'newPinnedAppInstanceWithPayload' it('sets the app base when not previously registered', async() => { assert.equal(ZERO_ADDR, await kernel.getApp(APP_BASES_NAMESPACE, APP_ID)) - await kernelOverload[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, false) + await kernel[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, false) assert.equal(appBase1.address, await kernel.getApp(APP_BASES_NAMESPACE, APP_ID)) }) it("doesn't set the app base when already set", async() => { await kernel.setApp(APP_BASES_NAMESPACE, APP_ID, appBase1.address) - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, false) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, false) assert.isFalse(receipt.logs.includes(l => l.event == 'SetApp')) }) it("also sets the default app", async () => { - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, true) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address, EMPTY_BYTES, true) const appProxyAddr = receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy // Check that both the app base and default app are set @@ -162,7 +140,7 @@ contract('Kernel apps', accounts => { it("allows initializing proxy", async () => { const initData = appBase1.initialize.request().params[0].data - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address, initData, false) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address, initData, false) const appProxyAddr = receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy // Make sure app was initialized @@ -175,7 +153,7 @@ contract('Kernel apps', accounts => { it("fails if the app base is not given", async() => { return assertRevert(async () => { - await kernelOverload[newInstanceFn](APP_ID, ZERO_ADDR, EMPTY_BYTES, false) + await kernel[newInstanceFn](APP_ID, ZERO_ADDR, EMPTY_BYTES, false) }) }) @@ -186,37 +164,29 @@ contract('Kernel apps', accounts => { await kernel.setApp(APP_BASES_NAMESPACE, APP_ID, existingBase) return assertRevert(async () => { - await kernelOverload[newInstanceFn](APP_ID, differentBase, EMPTY_BYTES, false) + await kernel[newInstanceFn](APP_ID, differentBase, EMPTY_BYTES, false) }) }) }) context('> minimized new app instance overload', async () => { - beforeEach(async () => { - if (appProxyType === 'AppProxy') { - kernelOverload = await KernelOverloadMock.new(kernel.address) - await acl.grantPermission(kernelOverload.address, kernel.address, APP_MANAGER_ROLE) - } else if (appProxyType === 'AppProxyPinned') { - // No need to apply the overload - kernelOverload = kernel - } - }) + const newInstanceFn = appProxyType === 'AppProxy' ? 'newAppInstanceWithoutPayload' : 'newPinnedAppInstanceWithoutPayload' it('sets the app base when not previously registered', async() => { assert.equal(ZERO_ADDR, await kernel.getApp(APP_BASES_NAMESPACE, APP_ID)) - await kernelOverload[newInstanceFn](APP_ID, appBase1.address) + await kernel[newInstanceFn](APP_ID, appBase1.address) assert.equal(appBase1.address, await kernel.getApp(APP_BASES_NAMESPACE, APP_ID)) }) it("doesn't set the app base when already set", async() => { await kernel.setApp(APP_BASES_NAMESPACE, APP_ID, appBase1.address) - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address) assert.isFalse(receipt.logs.includes(l => l.event == 'SetApp')) }) it("does not set the default app", async () => { - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address) const appProxyAddr = receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy // Check that only the app base is set @@ -230,7 +200,7 @@ contract('Kernel apps', accounts => { it("does not allow initializing proxy", async () => { const initData = appBase1.initialize.request().params[0].data - const receipt = await kernelOverload[newInstanceFn](APP_ID, appBase1.address) + const receipt = await kernel[newInstanceFn](APP_ID, appBase1.address) const appProxyAddr = receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy // Make sure app was not initialized @@ -243,7 +213,7 @@ contract('Kernel apps', accounts => { it("fails if the app base is not given", async() => { return assertRevert(async () => { - await kernelOverload[newInstanceFn](APP_ID, ZERO_ADDR) + await kernel[newInstanceFn](APP_ID, ZERO_ADDR) }) }) @@ -254,7 +224,7 @@ contract('Kernel apps', accounts => { await kernel.setApp(APP_BASES_NAMESPACE, APP_ID, existingBase) return assertRevert(async () => { - await kernelOverload[newInstanceFn](APP_ID, differentBase) + await kernel[newInstanceFn](APP_ID, differentBase) }) }) }) diff --git a/test/contracts/kernel/kernel_lifecycle.js b/test/contracts/kernel/kernel_lifecycle.js index 60c46007d..a362f152b 100644 --- a/test/contracts/kernel/kernel_lifecycle.js +++ b/test/contracts/kernel/kernel_lifecycle.js @@ -23,7 +23,7 @@ contract('Kernel lifecycle', accounts => { assert.isFalse(await kernel.hasPermission(accounts[1], kernel.address, APP_MANAGER_ROLE, EMPTY_BYTES)) await assertRevert(() => kernel.newAppInstance(APP_ID, appBase.address, EMPTY_BYTES, false)) - await assertRevert(() => kernel.newPinnedAppInstance(APP_ID, appBase.address)) + await assertRevert(() => kernel.newPinnedAppInstance(APP_ID, appBase.address, EMPTY_BYTES, false)) await assertRevert(() => kernel.setApp(APP_BASES_NAMESPACE, APP_ID, appBase.address)) await assertRevert(() => kernel.setRecoveryVaultAppId(VAULT_ID)) } diff --git a/test/kill_switch/IssuesRegistry.test.js b/test/kill_switch/IssuesRegistry.test.js index 9310229e3..9f05b62e0 100644 --- a/test/kill_switch/IssuesRegistry.test.js +++ b/test/kill_switch/IssuesRegistry.test.js @@ -5,21 +5,23 @@ const { getEventArgument } = require('../helpers/events') const IssuesRegistry = artifacts.require('IssuesRegistry') const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') +const KillSwitch = artifacts.require('KillSwitch') const DAOFactory = artifacts.require('DAOFactory') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { - let kernelBase, aclBase, issuesRegistryBase, registryFactory, dao, acl, issuesRegistry + let kernelBase, aclBase, issuesRegistryBase, registryFactory, dao, acl, issuesRegistry, killSwitchBase before('deploy base implementations', async () => { kernelBase = await Kernel.new(true) // petrify immediately aclBase = await ACL.new() + killSwitchBase = await KillSwitch.new() registryFactory = await EVMScriptRegistryFactory.new() issuesRegistryBase = await IssuesRegistry.new() }) before('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) const kernelReceipt = await daoFactory.newDAO(root) dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) diff --git a/test/kill_switch/KillSwitch.test.js b/test/kill_switch/KillSwitch.test.js index f0f9d99c0..a71b414b7 100644 --- a/test/kill_switch/KillSwitch.test.js +++ b/test/kill_switch/KillSwitch.test.js @@ -26,36 +26,38 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { appBase = await KillSwitchedApp.new() }) - before('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, registryFactory.address) - const kernelReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(kernelReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) + beforeEach('create issues registries', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + const daoReceipt = await daoFactory.newDAO(root) + const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - beforeEach('create default issues registry', async () => { - const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await defaultIssuesRegistry.initialize() const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, defaultIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) - }) - beforeEach('create specific issues registry', async () => { - const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - specificIssuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(defaultRegistryReceipt, 'NewAppProxy', 'proxy')) + await defaultIssuesRegistry.initialize() + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + + const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + specificIssuesRegistry = IssuesRegistry.at(getEventArgument(specificRegistryReceipt, 'NewAppProxy', 'proxy')) await specificIssuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(securityPartner, specificIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) }) - beforeEach('create kill switch', async () => { - const receipt = await dao.newAppInstance('0x1235', killSwitchBase.address, '0x', false, { from: root }) - killSwitch = KillSwitch.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await killSwitch.initialize(defaultIssuesRegistry.address) + beforeEach('deploy DAO', async () => { + const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + + acl = ACL.at(await dao.acl()) + const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + killSwitch = KillSwitch.at(await dao.killSwitch()) const SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) @@ -72,7 +74,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { beforeEach('create kill switched app', async () => { const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await app.initialize(killSwitch.address, owner) + await app.initialize(owner) }) describe('isContractIgnored', function () { @@ -112,7 +114,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) describe('setContractAction', function () { - context('when the sender is the owner', function () { + context('when the sender is authorized', function () { const from = owner context('when there was no action set yet', function () { @@ -148,7 +150,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - context('when the sender is not the owner', function () { + context('when the sender is not authorized', function () { const from = anyone it('reverts', async function () { @@ -176,7 +178,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) describe('setIssuesRegistry', function () { - context('when the sender is the owner', function () { + context('when the sender is authorized', function () { const from = owner context('when the given address is not a contract', () => { @@ -219,7 +221,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - context('when the sender is not the owner', function () { + context('when the sender is not authorized', function () { const from = anyone it('reverts', async () => { @@ -228,6 +230,59 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) + describe('setDefaultIssuesRegistry', function () { + context('when the sender is authorized', function () { + const from = owner + + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(ZERO_ADDRESS, { from })) + }) + }) + + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) + }) + + it('emits an event', async () => { + const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + + const events = getEvents(receipt, 'DefaultIssuesRegistrySet') + assert.equal(events.length, 1, 'number of DefaultIssuesRegistrySet events does not match') + + const event = getEvent(receipt, 'DefaultIssuesRegistrySet').args + assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + }) + }) + + context('when there was a specific issues registry set', function () { + beforeEach('set specific issues registry', async () => { + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) + }) + + it('changes the issues registry', async () => { + await killSwitch.setDefaultIssuesRegistry(defaultIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.defaultIssuesRegistry(), defaultIssuesRegistry.address) + }) + }) + }) + }) + + context('when the sender is not authorized', function () { + const from = anyone + + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from })) + }) + }) + }) + describe('isSeverityIgnored', function () { context('when there is no bug registered', () => { context('when there is no highest allowed severity set for the contract being called', () => { @@ -293,7 +348,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) describe('setHighestAllowedSeverity', function () { - context('when the contract is the owner', function () { + context('when the sender is authorized', function () { const from = owner context('when there was no severity set', function () { @@ -329,7 +384,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - context('when the sender is not the owner', function () { + context('when the sender is not authorized', function () { const from = anyone it('reverts', async function () { From 3c9eb507ec6e34b656d3269cc917e69c89e7879c Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Thu, 2 May 2019 15:32:47 -0300 Subject: [PATCH 14/37] kill-switch: place mocks and test files in corresponding dirs --- .../test/mocks/{ => kill_switch}/KillSwitchedAppMock.sol | 2 +- test/{ => contracts}/kill_switch/IssuesRegistry.test.js | 4 ++-- test/{ => contracts}/kill_switch/KillSwitch.test.js | 4 ++-- test/{ => contracts}/kill_switch/enums.js | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename contracts/test/mocks/{ => kill_switch}/KillSwitchedAppMock.sol (92%) rename test/{ => contracts}/kill_switch/IssuesRegistry.test.js (97%) rename test/{ => contracts}/kill_switch/KillSwitch.test.js (99%) rename test/{ => contracts}/kill_switch/enums.js (100%) diff --git a/contracts/test/mocks/KillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol similarity index 92% rename from contracts/test/mocks/KillSwitchedAppMock.sol rename to contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol index d98f6c997..2e3e736d2 100644 --- a/contracts/test/mocks/KillSwitchedAppMock.sol +++ b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol @@ -1,6 +1,6 @@ pragma solidity 0.4.24; -import "../../apps/AragonApp.sol"; +import "../../../apps/AragonApp.sol"; contract KillSwitchedAppMock is AragonApp { diff --git a/test/kill_switch/IssuesRegistry.test.js b/test/contracts/kill_switch/IssuesRegistry.test.js similarity index 97% rename from test/kill_switch/IssuesRegistry.test.js rename to test/contracts/kill_switch/IssuesRegistry.test.js index 9f05b62e0..c6de38a62 100644 --- a/test/kill_switch/IssuesRegistry.test.js +++ b/test/contracts/kill_switch/IssuesRegistry.test.js @@ -1,6 +1,6 @@ const { SEVERITY } = require('./enums') -const { assertRevert } = require('../helpers/assertThrow') -const { getEventArgument } = require('../helpers/events') +const { assertRevert } = require('../../helpers/assertThrow') +const { getEventArgument } = require('../../helpers/events') const IssuesRegistry = artifacts.require('IssuesRegistry') const ACL = artifacts.require('ACL') diff --git a/test/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/KillSwitch.test.js similarity index 99% rename from test/kill_switch/KillSwitch.test.js rename to test/contracts/kill_switch/KillSwitch.test.js index a71b414b7..020f531e5 100644 --- a/test/kill_switch/KillSwitch.test.js +++ b/test/contracts/kill_switch/KillSwitch.test.js @@ -1,6 +1,6 @@ const { ACTION, SEVERITY } = require('./enums') -const { assertRevert } = require('../helpers/assertThrow') -const { getEvents, getEvent, getEventArgument } = require('../helpers/events') +const { assertRevert } = require('../../helpers/assertThrow') +const { getEvents, getEvent, getEventArgument } = require('../../helpers/events') const KillSwitch = artifacts.require('KillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') diff --git a/test/kill_switch/enums.js b/test/contracts/kill_switch/enums.js similarity index 100% rename from test/kill_switch/enums.js rename to test/contracts/kill_switch/enums.js From ffff1702521eebb43f4fdfe5cdf4a27a4cac0723 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 3 May 2019 09:18:39 -0300 Subject: [PATCH 15/37] kill-switch: minor tweaks based on @bingen's feedback --- contracts/kernel/Kernel.sol | 2 +- contracts/kill_switch/IssuesRegistry.sol | 2 +- contracts/kill_switch/KillSwitch.sol | 24 ++++++++++++------- .../test/mocks/common/KeccakConstants.sol | 6 +++++ .../mocks/kill_switch/KillSwitchedAppMock.sol | 4 ++++ test/contracts/common/keccak_constants.js | 9 +++++++ test/contracts/kill_switch/KillSwitch.test.js | 15 ++++++++++++ 7 files changed, 52 insertions(+), 10 deletions(-) diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 331191ed9..25e6d6a6f 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -58,7 +58,7 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant /** * @dev Initialize can only be called once. It saves the block number in which it was initialized. - * @notice Initialize this kernel instance, its ACL setting `_permissionsCreator` as the entity that can create other permissions, and a KillSwitch instance setting `_issuesRegistry + * @notice Initialize this kernel instance, its ACL setting `_permissionsCreator` as the entity that can create other permissions, and a KillSwitch instance setting `_issuesRegistry` * @param _baseAcl Address of base ACL app * @param _permissionsCreator Entity that will be given permission over createPermission * @param _baseKillSwitch Address of base KillSwitch app diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol index 657a841d4..4f5919cee 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -13,7 +13,7 @@ contract IssuesRegistry is IIssuesRegistry, AragonApp { initialized(); } - function setSeverityFor(address entry, Severity severity) external authP(SET_ENTRY_SEVERITY_ROLE, arr(entry, msg.sender)) { + function setSeverityFor(address entry, Severity severity) external authP(SET_ENTRY_SEVERITY_ROLE, arr(entry)) { issuesSeverity[entry] = severity; emit SeveritySet(entry, severity, msg.sender); } diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index 88356d68c..ff17a9647 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -7,10 +7,18 @@ import "../common/IsContract.sol"; contract KillSwitch is IKillSwitch, IsContract, AragonApp { - bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); - bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); - bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); - bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); + /* + * Hardcoded constants to save gas + * bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); + * bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); + * bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); + * bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); + */ + + bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = 0xec32b556caaf18ff28362d6b89f3f678177fb74ae2c5c78bfbac6b1dedfa6b43; + bytes32 constant public SET_ISSUES_REGISTRY_ROLE = 0xc347b194ad4bc72077d417e05508bb224b4be509950d86cc7756e39a78fb725b; + bytes32 constant public SET_CONTRACT_ACTION_ROLE = 0xc7e0b4d70cab2a2679fe330e7c518a6e245cc494b086c284bfeb5f5d03fbe3f6; + bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = 0xca159ccee5d02309b609308bfc70aecedaf2d2023cd19f9c223d8e9875a256ba; string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; @@ -37,14 +45,14 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { function setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) external - authP(SET_DEFAULT_ISSUES_REGISTRY_ROLE, arr(msg.sender)) + auth(SET_DEFAULT_ISSUES_REGISTRY_ROLE) { _setDefaultIssuesRegistry(_defaultIssuesRegistry); } function setContractAction(address _contract, ContractAction _action) external - authP(SET_CONTRACT_ACTION_ROLE, arr(_contract, msg.sender)) + authP(SET_CONTRACT_ACTION_ROLE, arr(_contract)) { contractSettings[_contract].action = _action; emit ContractActionSet(_contract, _action); @@ -60,7 +68,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { function setIssuesRegistry(address _contract, IIssuesRegistry _issuesRegistry) external - authP(SET_ISSUES_REGISTRY_ROLE, arr(_contract, msg.sender)) + authP(SET_ISSUES_REGISTRY_ROLE, arr(_contract)) { require(isContract(_issuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); contractSettings[_contract].issuesRegistry = _issuesRegistry; @@ -68,7 +76,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { } function shouldDenyCallingContract(address _contract) external returns (bool) { - // if the call should be denied, then deny given call + // if the contract should be denied, then deny given call if (isContractDenied(_contract)) { return true; } diff --git a/contracts/test/mocks/common/KeccakConstants.sol b/contracts/test/mocks/common/KeccakConstants.sol index 274f8b5d9..33fb80e32 100644 --- a/contracts/test/mocks/common/KeccakConstants.sol +++ b/contracts/test/mocks/common/KeccakConstants.sol @@ -28,6 +28,12 @@ contract KeccakConstants { bytes32 public constant CREATE_PERMISSIONS_ROLE = keccak256(abi.encodePacked("CREATE_PERMISSIONS_ROLE")); bytes32 public constant EMPTY_PARAM_HASH = keccak256(abi.encodePacked(uint256(0))); + // KillSwitch + bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); + bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); + bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); + bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); + // APMRegistry bytes32 public constant CREATE_REPO_ROLE = keccak256(abi.encodePacked("CREATE_REPO_ROLE")); diff --git a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol index 2e3e736d2..73e17955f 100644 --- a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol +++ b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol @@ -21,6 +21,10 @@ contract KillSwitchedAppMock is AragonApp { data = _data; } + function writeWithoutKillSwitch(uint256 _data) public { + data = _data; + } + function reset() public killSwitched { data = 0; } diff --git a/test/contracts/common/keccak_constants.js b/test/contracts/common/keccak_constants.js index efba4facc..14bc6055a 100644 --- a/test/contracts/common/keccak_constants.js +++ b/test/contracts/common/keccak_constants.js @@ -123,4 +123,13 @@ contract('Constants', () => { // redefined the storage position correctly in the mock. assert.equal(await reentrancyGuardMock.getReentrancyMutexPosition(), await keccakConstants.reentrancyGuardPosition(), "reentrancyGuardPosition doesn't match") }) + + it('checks KillSwitch constants', async () => { + const killSwitch = await getContract('KillSwitch').new() + + assert.equal(await killSwitch.SET_DEFAULT_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_DEFAULT_ISSUES_REGISTRY_ROLE()) + assert.equal(await killSwitch.SET_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_ISSUES_REGISTRY_ROLE()) + assert.equal(await killSwitch.SET_CONTRACT_ACTION_ROLE(), await keccakConstants.SET_CONTRACT_ACTION_ROLE()) + assert.equal(await killSwitch.SET_HIGHEST_ALLOWED_SEVERITY_ROLE(), await keccakConstants.SET_HIGHEST_ALLOWED_SEVERITY_ROLE()) + }) }) diff --git a/test/contracts/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/KillSwitch.test.js index 020f531e5..4deff842e 100644 --- a/test/contracts/kill_switch/KillSwitch.test.js +++ b/test/contracts/kill_switch/KillSwitch.test.js @@ -624,4 +624,19 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) }) + + describe('gas costs', () => { + beforeEach('set an allowed severity issue', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + it('kill switch should overload ~32k of gas to a function', async () => { + const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) + const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) + + const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch + assert(killSwitchCost <= 32000, 'kill switch should overload ~32k of gas') + }) + }) }) From 76f85211db0d891c2d4764d1f775b57f03720dab Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 3 May 2019 13:51:12 -0300 Subject: [PATCH 16/37] kill-switch: skip gas costs test for coverage --- test/contracts/kill_switch/KillSwitch.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/contracts/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/KillSwitch.test.js index 4deff842e..4b4a6c71c 100644 --- a/test/contracts/kill_switch/KillSwitch.test.js +++ b/test/contracts/kill_switch/KillSwitch.test.js @@ -1,4 +1,5 @@ const { ACTION, SEVERITY } = require('./enums') +const { skipCoverage } = require('../../helpers/coverage') const { assertRevert } = require('../../helpers/assertThrow') const { getEvents, getEvent, getEventArgument } = require('../../helpers/events') @@ -631,12 +632,12 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - it('kill switch should overload ~32k of gas to a function', async () => { + it('kill switch should overload ~32k of gas to a function', skipCoverage(async () => { const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch assert(killSwitchCost <= 32000, 'kill switch should overload ~32k of gas') - }) + })) }) }) From 2db3e46e358e47151e6f099ede8eaab1cb40ddae Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 3 May 2019 14:51:01 -0300 Subject: [PATCH 17/37] kill-switch: rename 'ignore' action to 'allow' --- contracts/kill_switch/KillSwitch.sol | 12 +- test/contracts/kill_switch/KillSwitch.test.js | 186 +++++++++++++----- test/contracts/kill_switch/enums.js | 2 +- 3 files changed, 143 insertions(+), 57 deletions(-) diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index ff17a9647..d8bd2d15a 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -22,7 +22,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; - enum ContractAction { Check, Ignore, Deny } + enum ContractAction { Allow, Check, Deny } struct Settings { ContractAction action; @@ -76,13 +76,13 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { } function shouldDenyCallingContract(address _contract) external returns (bool) { - // if the contract should be denied, then deny given call + // if the contract is denied, then deny given call if (isContractDenied(_contract)) { return true; } - // if the contract issues are ignored, then allow given call - if (isContractIgnored(_contract)) { + // if the contract is allowed, then allow given call + if (isContractAllowed(_contract)) { return false; } @@ -108,8 +108,8 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; } - function isContractIgnored(address _contract) public view returns (bool) { - return getContractAction(_contract) == ContractAction.Ignore; + function isContractAllowed(address _contract) public view returns (bool) { + return getContractAction(_contract) == ContractAction.Allow; } function isContractDenied(address _contract) public view returns (bool) { diff --git a/test/contracts/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/KillSwitch.test.js index 4b4a6c71c..68ff66e07 100644 --- a/test/contracts/kill_switch/KillSwitch.test.js +++ b/test/contracts/kill_switch/KillSwitch.test.js @@ -78,38 +78,82 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await app.initialize(owner) }) - describe('isContractIgnored', function () { - context('when the contract is checked', function () { - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractIgnored(appBase.address)) + describe('isContractAllowed', function () { + context('when there was no action previously set', function () { + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) }) }) - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + context('when there was an action set', function () { + context('when the contract is allowed', function () { + beforeEach('allow contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + }) }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractIgnored(appBase.address)) + context('when the contract is being checked', function () { + beforeEach('check contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + }) + }) + + context('when the contract is denied', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + }) }) }) }) describe('isContractDenied', function () { - context('when the contract is not denied', function () { + context('when there was no action previously set', function () { it('returns false', async function () { assert.isFalse(await killSwitch.isContractDenied(appBase.address)) }) }) - context('when the contract is ignored', function () { - beforeEach('ignore contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + context('when there was an action set', function () { + context('when the contract is allowed', function () { + beforeEach('allow contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + context('when the contract is being checked', function () { + beforeEach('check contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) + }) + + context('when the contract is denied', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + }) }) }) }) @@ -144,9 +188,9 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('changes the contract action', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from }) + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.IGNORE) + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.ALLOW) }) }) }) @@ -404,7 +448,19 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) } - context('when the contract being called is not denied', () => { + context('when the contract being called is denied', () => { + beforeEach('check calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the contract being called is denied', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + itExecutesTheCall() }) @@ -479,24 +535,34 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { } const itExecutesTheCallWhenNotDenied = () => { - context('when the contract being called is checked', () => { + context('when there was no action previously set', () => { itExecutesTheCall() }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + context('when there was an action set', () => { + context('when the contract being called is being checked', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + }) + + itExecutesTheCall() }) - itExecutesTheCall() - }) + context('when the contract being called is allowed', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, {from: owner}) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + itExecutesTheCall() }) - itDoesNotExecuteTheCall() + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, {from: owner}) + }) + + itDoesNotExecuteTheCall() + }) }) } @@ -521,24 +587,34 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the bug was not fixed yet', () => { context('when there is no highest allowed severity set for the contract being called', () => { - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall() + context('when there was no action previously set', () => { + itExecutesTheCall() }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + context('when there was an action set', () => { + context('when the contract being called is allowed', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + itExecutesTheCall() }) - itExecutesTheCall() - }) + context('when the contract being called is being checked', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + itDoesNotExecuteTheCall() }) - itDoesNotExecuteTheCall() + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) }) }) @@ -548,24 +624,34 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when the contract being called is checked', () => { - itDoesNotExecuteTheCall() + context('when there was no action previously set', () => { + itExecutesTheCall() }) - context('when the contract being called is ignored', () => { - beforeEach('ignore calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.IGNORE, { from: owner }) + context('when there was an action set', () => { + context('when the contract being called is allowed', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + itExecutesTheCall() }) - itExecutesTheCall() - }) + context('when the contract being called is being checked', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + itDoesNotExecuteTheCall() }) - itDoesNotExecuteTheCall() + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) }) }) diff --git a/test/contracts/kill_switch/enums.js b/test/contracts/kill_switch/enums.js index 01d118b73..dcdae8e07 100644 --- a/test/contracts/kill_switch/enums.js +++ b/test/contracts/kill_switch/enums.js @@ -1,4 +1,4 @@ -const ACTION = { CHECK: 0, IGNORE: 1, DENY: 2 } +const ACTION = { ALLOW: 0, CHECK: 1, DENY: 2 } const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } module.exports = { From d28cfeeb37ba39131a1f6d14d9faeeb61bf7ee86 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 08:44:15 -0300 Subject: [PATCH 18/37] kill-switch: rename issues registry `entry` by `implementation` --- contracts/kill_switch/IIssuesRegistry.sol | 8 ++++---- contracts/kill_switch/IssuesRegistry.sol | 16 ++++++++-------- .../contracts/kill_switch/IssuesRegistry.test.js | 16 ++++++++-------- test/contracts/kill_switch/KillSwitch.test.js | 6 +++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/contracts/kill_switch/IIssuesRegistry.sol b/contracts/kill_switch/IIssuesRegistry.sol index 92be768ea..425c4726e 100644 --- a/contracts/kill_switch/IIssuesRegistry.sol +++ b/contracts/kill_switch/IIssuesRegistry.sol @@ -4,13 +4,13 @@ pragma solidity 0.4.24; contract IIssuesRegistry { enum Severity { None, Low, Mid, High, Critical } - event SeveritySet(address indexed entry, Severity severity, address sender); + event SeveritySet(address indexed implementation, Severity severity, address sender); function initialize() external; - function setSeverityFor(address entry, Severity severity) external; + function setSeverityFor(address implementation, Severity severity) external; - function isSeverityFor(address entry) public view returns (bool); + function isSeverityFor(address implementation) public view returns (bool); - function getSeverityFor(address entry) public view returns (Severity); + function getSeverityFor(address implementation) public view returns (Severity); } diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol index 4f5919cee..c423dde16 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -5,7 +5,7 @@ import "./IIssuesRegistry.sol"; contract IssuesRegistry is IIssuesRegistry, AragonApp { - bytes32 constant public SET_ENTRY_SEVERITY_ROLE = keccak256("SET_ENTRY_SEVERITY_ROLE"); + bytes32 constant public SET_SEVERITY_ROLE = keccak256("SET_SEVERITY_ROLE"); mapping (address => Severity) internal issuesSeverity; @@ -13,16 +13,16 @@ contract IssuesRegistry is IIssuesRegistry, AragonApp { initialized(); } - function setSeverityFor(address entry, Severity severity) external authP(SET_ENTRY_SEVERITY_ROLE, arr(entry)) { - issuesSeverity[entry] = severity; - emit SeveritySet(entry, severity, msg.sender); + function setSeverityFor(address implementation, Severity severity) external authP(SET_SEVERITY_ROLE, arr(implementation)) { + issuesSeverity[implementation] = severity; + emit SeveritySet(implementation, severity, msg.sender); } - function isSeverityFor(address entry) public view isInitialized returns (bool) { - return issuesSeverity[entry] != Severity.None; + function isSeverityFor(address implementation) public view isInitialized returns (bool) { + return issuesSeverity[implementation] != Severity.None; } - function getSeverityFor(address entry) public view isInitialized returns (Severity) { - return issuesSeverity[entry]; + function getSeverityFor(address implementation) public view isInitialized returns (Severity) { + return issuesSeverity[implementation]; } } diff --git a/test/contracts/kill_switch/IssuesRegistry.test.js b/test/contracts/kill_switch/IssuesRegistry.test.js index c6de38a62..a5e85c07a 100644 --- a/test/contracts/kill_switch/IssuesRegistry.test.js +++ b/test/contracts/kill_switch/IssuesRegistry.test.js @@ -33,14 +33,14 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) issuesRegistry = IssuesRegistry.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) await issuesRegistry.initialize() - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() - await acl.createPermission(owner, issuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + const SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() + await acl.createPermission(owner, issuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) }) describe('isSeverityFor', () => { context('when there was no severity set before', () => { it('returns false', async () => { - assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') }) }) @@ -51,7 +51,7 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { context('when the issues was not fixed yet', () => { it('returns true', async () => { - assert.isTrue(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + assert.isTrue(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') }) }) @@ -61,7 +61,7 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { }) it('returns false', async () => { - assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given entry') + assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') }) }) }) @@ -94,13 +94,13 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { const events = logs.filter(l => l.event === 'SeveritySet') assert.equal(events.length, 1, 'number of SeveritySet events does not match') - assert.equal(events[0].args.entry, implementation, 'entry address does not match') + assert.equal(events[0].args.implementation, implementation, 'implementation address does not match') assert.equal(events[0].args.severity, SEVERITY.LOW, 'severity does not match') assert.equal(events[0].args.sender, owner, 'sender does not match') }) context('when there was no severity set before', () => { - it('sets the severity for the given entry', async () => { + it('sets the severity for the given implementation', async () => { await issuesRegistry.setSeverityFor(implementation, SEVERITY.MID, { from }) assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.MID, 'severity does not match') @@ -112,7 +112,7 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { await issuesRegistry.setSeverityFor(implementation, SEVERITY.MID, { from }) }) - it('changes the severity for the given entry', async () => { + it('changes the severity for the given implementation', async () => { await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from }) assert.equal(await issuesRegistry.getSeverityFor(implementation), SEVERITY.LOW, 'severity does not match') diff --git a/test/contracts/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/KillSwitch.test.js index 68ff66e07..4619ce36c 100644 --- a/test/contracts/kill_switch/KillSwitch.test.js +++ b/test/contracts/kill_switch/KillSwitch.test.js @@ -36,17 +36,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - const SET_ENTRY_SEVERITY_ROLE = await issuesRegistryBase.SET_ENTRY_SEVERITY_ROLE() + const SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(defaultRegistryReceipt, 'NewAppProxy', 'proxy')) await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) specificIssuesRegistry = IssuesRegistry.at(getEventArgument(specificRegistryReceipt, 'NewAppProxy', 'proxy')) await specificIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_ENTRY_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) }) beforeEach('deploy DAO', async () => { From dba7e42d96e483fafc9b79c225100a0b6969e611 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 08:44:59 -0300 Subject: [PATCH 19/37] kill-switch: rename test files to follow naming convention --- .../{IssuesRegistry.test.js => issues_registry.test.js} | 0 .../kill_switch/{KillSwitch.test.js => kill_switch.test.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/contracts/kill_switch/{IssuesRegistry.test.js => issues_registry.test.js} (100%) rename test/contracts/kill_switch/{KillSwitch.test.js => kill_switch.test.js} (100%) diff --git a/test/contracts/kill_switch/IssuesRegistry.test.js b/test/contracts/kill_switch/issues_registry.test.js similarity index 100% rename from test/contracts/kill_switch/IssuesRegistry.test.js rename to test/contracts/kill_switch/issues_registry.test.js diff --git a/test/contracts/kill_switch/KillSwitch.test.js b/test/contracts/kill_switch/kill_switch.test.js similarity index 100% rename from test/contracts/kill_switch/KillSwitch.test.js rename to test/contracts/kill_switch/kill_switch.test.js From 60268b2f83e42eca11da28374263131afd36ea33 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 08:56:30 -0300 Subject: [PATCH 20/37] kill-switch: improve authentication params --- contracts/kill_switch/IssuesRegistry.sol | 5 ++++- contracts/kill_switch/KillSwitch.sol | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol index c423dde16..339190e26 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -13,7 +13,10 @@ contract IssuesRegistry is IIssuesRegistry, AragonApp { initialized(); } - function setSeverityFor(address implementation, Severity severity) external authP(SET_SEVERITY_ROLE, arr(implementation)) { + function setSeverityFor(address implementation, Severity severity) + external + authP(SET_SEVERITY_ROLE, arr(implementation, uint256(severity))) + { issuesSeverity[implementation] = severity; emit SeveritySet(implementation, severity, msg.sender); } diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index d8bd2d15a..14f077cd9 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -60,7 +60,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { function setHighestAllowedSeverity(address _contract, IIssuesRegistry.Severity _severity) external - authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract, msg.sender)) + authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract)) { contractSettings[_contract].highestAllowedSeverity = _severity; emit HighestAllowedSeveritySet(_contract, _severity); From 8f91fecf0a882284cf4c375367bffb3fe4301f08 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 09:12:47 -0300 Subject: [PATCH 21/37] kill-switch: fix app id constant value --- contracts/kernel/KernelConstants.sol | 4 ++-- contracts/test/mocks/common/KeccakConstants.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/kernel/KernelConstants.sol b/contracts/kernel/KernelConstants.sol index b54ab44ba..d9663b882 100644 --- a/contracts/kernel/KernelConstants.sol +++ b/contracts/kernel/KernelConstants.sol @@ -10,12 +10,12 @@ contract KernelAppIds { bytes32 internal constant KERNEL_CORE_APP_ID = apmNamehash("kernel"); bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = apmNamehash("acl"); bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = apmNamehash("vault"); - bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = apmNamehash("killSwitch"); + bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = apmNamehash("kill-switch"); */ bytes32 internal constant KERNEL_CORE_APP_ID = 0x3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c; bytes32 internal constant KERNEL_DEFAULT_ACL_APP_ID = 0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a; bytes32 internal constant KERNEL_DEFAULT_VAULT_APP_ID = 0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1; - bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = 0x05b6cbc146cecc3a8014843768ab6e17332ef00418da7f6babf4ea94c76ab6a1; + bytes32 internal constant KERNEL_DEFAULT_KILL_SWITCH_APP_ID = 0x498cc0d31a6b7824a695121dd7e3f77a2b8f1108ed5a3367ec52d064799ee9cc; } diff --git a/contracts/test/mocks/common/KeccakConstants.sol b/contracts/test/mocks/common/KeccakConstants.sol index 33fb80e32..d5df4dd8c 100644 --- a/contracts/test/mocks/common/KeccakConstants.sol +++ b/contracts/test/mocks/common/KeccakConstants.sol @@ -22,7 +22,7 @@ contract KeccakConstants { bytes32 public constant KERNEL_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("kernel"))); bytes32 public constant DEFAULT_ACL_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("acl"))); bytes32 public constant DEFAULT_VAULT_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("vault"))); - bytes32 public constant DEFAULT_KILL_SWITCH_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("killSwitch"))); + bytes32 public constant DEFAULT_KILL_SWITCH_APP_ID = keccak256(abi.encodePacked(APM_NODE, keccak256("kill-switch"))); // ACL bytes32 public constant CREATE_PERMISSIONS_ROLE = keccak256(abi.encodePacked("CREATE_PERMISSIONS_ROLE")); From 1ce842df399e72b933a1616c8d47e125800d0b31 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 14:01:07 -0300 Subject: [PATCH 22/37] kill-switch: improve gas costs and support current kernel versions --- contracts/apps/AragonApp.sol | 32 +- contracts/kernel/IKernel.sol | 1 + contracts/kernel/Kernel.sol | 15 + .../KernelWithoutKillSwitchMock.sol | 22 + test/contracts/apps/recovery_to_vault.js | 2 - test/contracts/evmscript/evm_script.js | 4 +- .../contracts/kill_switch/kill_switch.test.js | 1042 +++++++++-------- 7 files changed, 618 insertions(+), 500 deletions(-) create mode 100644 contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index 44b921fe1..f4f8e17a9 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -21,6 +21,7 @@ import "../evmscript/EVMScriptRunner.sol"; 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) { require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); @@ -33,8 +34,27 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua } modifier killSwitched { - bool _shouldDenyCall = kernel().killSwitch().shouldDenyCallingContract(_baseApp()); - require(!_shouldDenyCall, ERROR_CONTRACT_CALL_NOT_ALLOWED); + IKernel _kernel = kernel(); + bytes4 selector = _kernel.shouldDenyCallingContract.selector; + bytes memory callData = abi.encodeWithSelector(selector, appId()); + bool success = address(_kernel).call(callData); + + // perform a check only if kernel supports "shouldDenyCallingContract" method + if (success) { + uint256 _outputLength; + assembly { _outputLength := returndatasize } + // we expect 32 bytes length returned value here + require(_outputLength == 32, ERROR_UNEXPECTED_KERNEL_RESPONSE); + + bool _shouldDenyCall; + assembly { + let ptr := mload(0x40) // get next free memory pointer + mstore(0x40, add(ptr, returndatasize)) // set next free memory pointer + returndatacopy(ptr, 0, returndatasize) // copy call return value + _shouldDenyCall := mload(ptr) // read data + } + require(!_shouldDenyCall, ERROR_CONTRACT_CALL_NOT_ALLOWED); + } _; } @@ -72,12 +92,4 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua // Funds recovery via a vault is only available when used with a kernel return kernel().getRecoveryVault(); // if kernel is not set, it will revert } - - /** - * @dev Get the address of the base implementation for the current app - * @return Address of the base implementation - */ - function _baseApp() internal view returns (address) { - return kernel().getApp(KERNEL_APP_BASES_NAMESPACE, appId()); - } } diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index 0d9b92a38..f66b609ca 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -22,4 +22,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) public returns (bool); } diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 25e6d6a6f..1dd800af0 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -185,6 +185,21 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant recoveryVaultAppId = _recoveryVaultAppId; } + /** + * @dev Tells whether a call to an instance of an app should be denied or not based on the kill-switch settings + * @param _appId Identifier for app to be checked + * @return True is the given call should be denied, false otherwise + */ + function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + IKillSwitch _killSwitch = killSwitch(); + if (address(_killSwitch) == address(0)) { + return false; + } + + address _baseApp = getApp(KERNEL_APP_BASES_NAMESPACE, _appId); + return _killSwitch.shouldDenyCallingContract(_baseApp); + } + // External access to default app id and namespace constants to mimic default getters for constants /* solium-disable function-order, mixedcase */ function CORE_NAMESPACE() external pure returns (bytes32) { return KERNEL_CORE_NAMESPACE; } diff --git a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol new file mode 100644 index 000000000..ed9c888bb --- /dev/null +++ b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol @@ -0,0 +1,22 @@ +pragma solidity 0.4.24; + +import "../../../kernel/Kernel.sol"; + + +/** + * @title KernelWithoutKillSwitchMock + * @dev This mock mimics an already deployed Kernel version that does not have a kill-switch integrated + */ +contract KernelWithoutKillSwitchMock is Kernel { + string private constant ERROR_METHOD_NOT_FOUND = "KERNEL_METHOD_NOT_FOUND"; + + constructor() Kernel(true) public {} + + function killSwitch() public view returns (IKillSwitch) { + revert(ERROR_METHOD_NOT_FOUND); + } + + function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + revert(ERROR_METHOD_NOT_FOUND); + } +} diff --git a/test/contracts/apps/recovery_to_vault.js b/test/contracts/apps/recovery_to_vault.js index 76dd7ddb9..eb5f4d512 100644 --- a/test/contracts/apps/recovery_to_vault.js +++ b/test/contracts/apps/recovery_to_vault.js @@ -4,11 +4,9 @@ const { skipCoverage } = require('../../helpers/coverage') const { getBalance } = require('../../helpers/web3') const { hash } = require('eth-ens-namehash') -const DAOFactory = artifacts.require('DAOFactory') const Kernel = artifacts.require('Kernel') const KernelProxy = artifacts.require('KernelProxy') const ACL = artifacts.require('ACL') -const AppProxyUpgradeable = artifacts.require('AppProxyUpgradeable') // Mocks const AppStubDepositable = artifacts.require('AppStubDepositable') diff --git a/test/contracts/evmscript/evm_script.js b/test/contracts/evmscript/evm_script.js index 8f919421e..4eca50674 100644 --- a/test/contracts/evmscript/evm_script.js +++ b/test/contracts/evmscript/evm_script.js @@ -6,9 +6,9 @@ const { assertRevert } = require('../../helpers/assertThrow') const { createExecutorId, encodeCallScript } = require('../../helpers/evmScript') const reverts = require('../../helpers/revertStrings') +const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') const KernelProxy = artifacts.require('KernelProxy') -const ACL = artifacts.require('ACL') const EVMScriptRegistry = artifacts.require('EVMScriptRegistry') const CallsScript = artifacts.require('CallsScript') const IEVMScriptExecutor = artifacts.require('IEVMScriptExecutor') @@ -194,7 +194,7 @@ contract('EVM Script', accounts => { }) context('> ScriptRunner', () => { - let scriptRunnerApp + let scriptRunnerAppBase, scriptRunnerApp before(async () => { scriptRunnerAppBase = await AppStubScriptRunner.new() diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill_switch/kill_switch.test.js index 4619ce36c..56bcc62fb 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill_switch/kill_switch.test.js @@ -6,6 +6,7 @@ const { getEvents, getEvent, getEventArgument } = require('../../helpers/events' const KillSwitch = artifacts.require('KillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') +const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') @@ -15,8 +16,9 @@ const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase - let registryFactory, dao, acl, defaultIssuesRegistry, specificIssuesRegistry, app, killSwitch + let registryFactory, dao, acl, app + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory, oldKernelBase + let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, SET_SEVERITY_ROLE, SET_DEFAULT_ISSUES_REGISTRY_ROLE, SET_ISSUES_REGISTRY_ROLE, SET_CONTRACT_ACTION_ROLE, SET_HIGHEST_ALLOWED_SEVERITY_ROLE before('deploy base implementations', async () => { kernelBase = await Kernel.new(true) // petrify immediately @@ -25,705 +27,773 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { killSwitchBase = await KillSwitch.new() issuesRegistryBase = await IssuesRegistry.new() appBase = await KillSwitchedApp.new() + oldKernelBase = await KernelWithoutKillSwitchMock.new() + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) }) - beforeEach('create issues registries', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) - const daoReceipt = await daoFactory.newDAO(root) - const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) - const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) - - const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - - const SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() - - const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(defaultRegistryReceipt, 'NewAppProxy', 'proxy')) - await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) - - const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - specificIssuesRegistry = IssuesRegistry.at(getEventArgument(specificRegistryReceipt, 'NewAppProxy', 'proxy')) - await specificIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + before('load constants and roles', async () => { + CORE_NAMESPACE = await kernelBase.CORE_NAMESPACE() + KERNEL_APP_ID = await kernelBase.KERNEL_APP_ID() + APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() + SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() + SET_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_ISSUES_REGISTRY_ROLE() + SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() + SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() }) - beforeEach('deploy DAO', async () => { - const daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) - const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) - - acl = ACL.at(await dao.acl()) - const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - - killSwitch = KillSwitch.at(await dao.killSwitch()) - const SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + context('when the kernel version does not support kill-switch logic', async () => { + beforeEach('deploy DAO with a kernel version not supporting kill-switch logic', async () => { + const receipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - const SET_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_ISSUES_REGISTRY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) - - const SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) - - const SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() - await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) - }) - - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) - await app.initialize(owner) - }) + // update the kernel to a mock version that doesn't supports kill-switch logic to mimic already deployed ones + await dao.setApp(CORE_NAMESPACE, KERNEL_APP_ID, oldKernelBase.address, { from: root }) + }) - describe('isContractAllowed', function () { - context('when there was no action previously set', function () { - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) - }) + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) }) - context('when there was an action set', function () { - context('when the contract is allowed', function () { - beforeEach('allow contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + describe('integration', () => { + context('when the function being called is not tagged', () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) }) + }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + context('when the function being called is tagged', () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) }) }) + }) + }) - context('when the contract is being checked', function () { - beforeEach('check contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) - }) + context('when the kernel version does support kill-switch logic', async () => { + context('when the kernel was not initialized with a kill-switch', async () => { + beforeEach('deploy DAO without a kill switch', async () => { + const receipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) - }) + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) }) - context('when the contract is denied', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + describe('integration', () => { + context('when the function being called is not tagged', () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) }) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + context('when the function being called is tagged', () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) }) }) }) - }) - describe('isContractDenied', function () { - context('when there was no action previously set', function () { - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) - }) - }) + context('when the kernel was initialized with a kill-switch', async () => { + let killSwitch, defaultIssuesRegistry, specificIssuesRegistry - context('when there was an action set', function () { - context('when the contract is allowed', function () { - beforeEach('allow contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) - }) + beforeEach('create issues registries', async () => { + const daoReceipt = await daoFactory.newDAO(root) + const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) - }) + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) + + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(defaultRegistryReceipt, 'NewAppProxy', 'proxy')) + await defaultIssuesRegistry.initialize() + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + + const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + specificIssuesRegistry = IssuesRegistry.at(getEventArgument(specificRegistryReceipt, 'NewAppProxy', 'proxy')) + await specificIssuesRegistry.initialize() + await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) }) - context('when the contract is being checked', function () { - beforeEach('check contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) - }) + beforeEach('deploy DAO with a kill switch', async () => { + const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + killSwitch = KillSwitch.at(await dao.killSwitch()) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) - }) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) - context('when the contract is denied', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) + }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + describe('isContractAllowed', function () { + context('when there was no action previously set', function () { + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + }) }) - }) - }) - }) - describe('setContractAction', function () { - context('when the sender is authorized', function () { - const from = owner + context('when there was an action set', function () { + context('when the contract is allowed', function () { + beforeEach('allow contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) - context('when there was no action set yet', function () { - it('sets a new action', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + }) + }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) - }) + context('when the contract is being checked', function () { + beforeEach('check contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) - it('emits an event', async () => { - const receipt = await await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + }) + }) - const events = getEvents(receipt, 'ContractActionSet') - assert.equal(events.length, 1, 'number of ContractActionSet events does not match') + context('when the contract is denied', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) - const event = getEvent(receipt, 'ContractActionSet').args - assert.equal(event.action, ACTION.DENY, 'action does not match') - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + }) + }) }) }) - context('when there was an action already set', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + describe('isContractDenied', function () { + context('when there was no action previously set', function () { + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) }) - it('changes the contract action', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from }) + context('when there was an action set', function () { + context('when the contract is allowed', function () { + beforeEach('allow contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) + }) + + context('when the contract is being checked', function () { + beforeEach('check contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) + + it('returns false', async function () { + assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + }) + }) + + context('when the contract is denied', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.ALLOW) + it('returns true', async function () { + assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + }) + }) }) }) - }) - context('when the sender is not authorized', function () { - const from = anyone + describe('setContractAction', function () { + context('when the sender is authorized', function () { + const from = owner - it('reverts', async function () { - await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY, { from })) - }) - }) - }) + context('when there was no action set yet', function () { + it('sets a new action', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) - describe('getIssuesRegistry', function () { - context('when there was no specific issues registry set', () => { - it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) - }) - }) + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + }) - context('when there is a specific issues registry set', () => { - beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from: owner }) - }) + it('emits an event', async () => { + const receipt = await await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) - it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) - }) - }) - }) + const events = getEvents(receipt, 'ContractActionSet') + assert.equal(events.length, 1, 'number of ContractActionSet events does not match') - describe('setIssuesRegistry', function () { - context('when the sender is authorized', function () { - const from = owner + const event = getEvent(receipt, 'ContractActionSet').args + assert.equal(event.action, ACTION.DENY, 'action does not match') + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + }) + }) - context('when the given address is not a contract', () => { - it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(appBase.address, ZERO_ADDRESS, { from })) - }) - }) + context('when there was an action already set', function () { + beforeEach('deny contract', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + }) - context('when the given address is a contract', () => { - context('when there was no specific issues registry set yet', function () { - it('sets the given implementation', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + it('changes the contract action', async function () { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from }) - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.ALLOW) + }) }) + }) - it('emits an event', async () => { - const receipt = await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + context('when the sender is not authorized', function () { + const from = anyone - const events = getEvents(receipt, 'IssuesRegistrySet') - assert.equal(events.length, 1, 'number of IssuesRegistrySet events does not match') + it('reverts', async function () { + await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY, { from })) + }) + }) + }) - const event = getEvent(receipt, 'IssuesRegistrySet').args - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') - assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + describe('getIssuesRegistry', function () { + context('when there was no specific issues registry set', () => { + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) }) }) - context('when there was a specific issues registry set', function () { + context('when there is a specific issues registry set', () => { beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from: owner }) }) - it('changes the issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, defaultIssuesRegistry.address, { from }) - - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) }) }) }) - }) - context('when the sender is not authorized', function () { - const from = anyone + describe('setIssuesRegistry', function () { + context('when the sender is authorized', function () { + const from = owner - it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from })) - }) - }) - }) + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(appBase.address, ZERO_ADDRESS, { from })) + }) + }) - describe('setDefaultIssuesRegistry', function () { - context('when the sender is authorized', function () { - const from = owner + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) - context('when the given address is not a contract', () => { - it('reverts', async () => { - await assertRevert(killSwitch.setDefaultIssuesRegistry(ZERO_ADDRESS, { from })) - }) - }) + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + }) - context('when the given address is a contract', () => { - context('when there was no specific issues registry set yet', function () { - it('sets the given implementation', async () => { - await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + it('emits an event', async () => { + const receipt = await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) - assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) - }) + const events = getEvents(receipt, 'IssuesRegistrySet') + assert.equal(events.length, 1, 'number of IssuesRegistrySet events does not match') + + const event = getEvent(receipt, 'IssuesRegistrySet').args + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + }) + }) - it('emits an event', async () => { - const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + context('when there was a specific issues registry set', function () { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + }) - const events = getEvents(receipt, 'DefaultIssuesRegistrySet') - assert.equal(events.length, 1, 'number of DefaultIssuesRegistrySet events does not match') + it('changes the issues registry', async () => { + await killSwitch.setIssuesRegistry(appBase.address, defaultIssuesRegistry.address, { from }) - const event = getEvent(receipt, 'DefaultIssuesRegistrySet').args - assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + }) + }) }) }) - context('when there was a specific issues registry set', function () { - beforeEach('set specific issues registry', async () => { - await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) - }) + context('when the sender is not authorized', function () { + const from = anyone - it('changes the issues registry', async () => { - await killSwitch.setDefaultIssuesRegistry(defaultIssuesRegistry.address, { from }) - - assert.equal(await killSwitch.defaultIssuesRegistry(), defaultIssuesRegistry.address) + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from })) }) }) }) - }) - context('when the sender is not authorized', function () { - const from = anyone + describe('setDefaultIssuesRegistry', function () { + context('when the sender is authorized', function () { + const from = owner - it('reverts', async () => { - await assertRevert(killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from })) - }) - }) - }) + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(ZERO_ADDRESS, { from })) + }) + }) - describe('isSeverityIgnored', function () { - context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) - }) - }) + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - context('when there is a highest allowed severity set for the contract being called', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) + }) - it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) - }) - }) - }) + it('emits an event', async () => { + const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + const events = getEvents(receipt, 'DefaultIssuesRegistrySet') + assert.equal(events.length, 1, 'number of DefaultIssuesRegistrySet events does not match') - context('when there is no highest allowed severity set for the contract being called', () => { - it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) - }) - }) + const event = getEvent(receipt, 'DefaultIssuesRegistrySet').args + assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') + }) + }) - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when there was a specific issues registry set', function () { + beforeEach('set specific issues registry', async () => { + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) + }) - it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + it('changes the issues registry', async () => { + await killSwitch.setDefaultIssuesRegistry(defaultIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.defaultIssuesRegistry(), defaultIssuesRegistry.address) + }) + }) }) }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + context('when the sender is not authorized', function () { + const from = anyone - it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from })) }) }) + }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + describe('isSeverityIgnored', function () { + context('when there is no bug registered', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) }) - it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) }) }) - }) - }) - }) - describe('setHighestAllowedSeverity', function () { - context('when the sender is authorized', function () { - const from = owner - - context('when there was no severity set', function () { - it('sets the highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.HIGH) - }) + context('when there is no highest allowed severity set for the contract being called', () => { + it('returns false', async () => { + assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) - it('emits an event', async () => { - const receipt = await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - const events = getEvents(receipt, 'HighestAllowedSeveritySet') - assert.equal(events.length, 1, 'number of ContractActionSet events does not match') + it('returns false', async () => { + assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) - const event = getEvent(receipt, 'HighestAllowedSeveritySet').args - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') - assert.equal(event.severity, SEVERITY.HIGH, 'highest severity does not match') - }) - }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when there was a previous severity set', function () { - beforeEach('set highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.LOW) - }) + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) - it('changes the highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.MID) + it('returns true', async () => { + assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + }) + }) + }) }) }) - }) - context('when the sender is not authorized', function () { - const from = anyone + describe('setHighestAllowedSeverity', function () { + context('when the sender is authorized', function () { + const from = owner - it('reverts', async function () { - await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from })) - }) - }) - }) - - describe('integration', () => { - context('when the function being called is not tagged', () => { + context('when there was no severity set', function () { + it('sets the highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) - const itExecutesTheCallEvenIfDenied = () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.HIGH) + }) - context('when the contract being called is denied', () => { - beforeEach('check calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) - }) + it('emits an event', async () => { + const receipt = await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) - itExecutesTheCall() - }) + const events = getEvents(receipt, 'HighestAllowedSeveritySet') + assert.equal(events.length, 1, 'number of ContractActionSet events does not match') - context('when the contract being called is denied', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + const event = getEvent(receipt, 'HighestAllowedSeveritySet').args + assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.severity, SEVERITY.HIGH, 'highest severity does not match') + }) }) - itExecutesTheCall() - }) + context('when there was a previous severity set', function () { + beforeEach('set highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.LOW) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) + it('changes the highest allowed severity', async function () { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) - itExecutesTheCall() + assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.MID) + }) + }) }) - } - context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallEvenIfDenied() - }) + context('when the sender is not authorized', function () { + const from = anyone - context('when there is a highest allowed severity set for the contract being called', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + it('reverts', async function () { + await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from })) }) - - itExecutesTheCallEvenIfDenied() }) }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + describe('integration', () => { + context('when the function being called is not tagged', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallEvenIfDenied() - }) + const itExecutesTheCallEvenIfDenied = () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - itExecutesTheCallEvenIfDenied() - }) + context('when the contract being called is denied', () => { + beforeEach('check calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + itExecutesTheCall() }) - itExecutesTheCallEvenIfDenied() - }) + context('when the contract being called is denied', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + itExecutesTheCall() }) - itExecutesTheCallEvenIfDenied() - }) - }) - }) - }) + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) - context('when the function being called is tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - } + itExecutesTheCall() + }) + } - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') - }) - } + context('when there is no bug registered', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallEvenIfDenied() + }) - const itExecutesTheCallWhenNotDenied = () => { - context('when there was no action previously set', () => { - itExecutesTheCall() - }) + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - context('when there was an action set', () => { - context('when the contract being called is being checked', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + itExecutesTheCallEvenIfDenied() }) - - itExecutesTheCall() }) - context('when the contract being called is allowed', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, {from: owner}) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - itExecutesTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, {from: owner}) + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallEvenIfDenied() }) - itDoesNotExecuteTheCall() - }) - }) - } + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + itExecutesTheCallEvenIfDenied() + }) - context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() - }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - context('when there is a highest allowed severity set for the contract being called', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + itExecutesTheCallEvenIfDenied() + }) - itExecutesTheCallWhenNotDenied() - }) - }) + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + itExecutesTheCallEvenIfDenied() + }) + }) + }) }) - context('when the bug was not fixed yet', () => { - context('when there is no highest allowed severity set for the contract being called', () => { + context('when the function being called is tagged', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + }) + } + + const itExecutesTheCallWhenNotDenied = () => { context('when there was no action previously set', () => { itExecutesTheCall() }) context('when there was an action set', () => { - context('when the contract being called is allowed', () => { + context('when the contract being called is being checked', () => { beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) }) itExecutesTheCall() }) - context('when the contract being called is being checked', () => { + context('when the contract being called is allowed', () => { beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, {from: owner}) }) - itDoesNotExecuteTheCall() + itExecutesTheCall() }) context('when the contract being called is denied', () => { beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + await killSwitch.setContractAction(appBase.address, ACTION.DENY, {from: owner}) }) itDoesNotExecuteTheCall() }) }) - }) + } - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { + context('when there is no bug registered', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallWhenNotDenied() + }) + + context('when there is a highest allowed severity set for the contract being called', () => { beforeEach('set highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) }) - context('when there was no action previously set', () => { - itExecutesTheCall() - }) + itExecutesTheCallWhenNotDenied() + }) + }) - context('when there was an action set', () => { - context('when the contract being called is allowed', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) - }) + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + context('when the bug was not fixed yet', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + context('when there was no action previously set', () => { itExecutesTheCall() }) - context('when the contract being called is being checked', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + context('when there was an action set', () => { + context('when the contract being called is allowed', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) + + itExecutesTheCall() }) - itDoesNotExecuteTheCall() - }) + context('when the contract being called is being checked', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + itDoesNotExecuteTheCall() }) - itDoesNotExecuteTheCall() + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) }) }) - }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) - itExecutesTheCallWhenNotDenied() - }) + context('when there was no action previously set', () => { + itExecutesTheCall() + }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) - }) + context('when there was an action set', () => { + context('when the contract being called is allowed', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + }) - itExecutesTheCallWhenNotDenied() - }) - }) - }) + itExecutesTheCall() + }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) + context('when the contract being called is being checked', () => { + beforeEach('allow calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + }) - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() - }) + itDoesNotExecuteTheCall() + }) - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) + context('when the contract being called is denied', () => { + beforeEach('deny calling contract', async () => { + await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + }) - itExecutesTheCallWhenNotDenied() - }) + itDoesNotExecuteTheCall() + }) + }) + }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallWhenNotDenied() + }) + + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + }) }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + context('when the bug was already fixed', () => { + beforeEach('fix bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) - itExecutesTheCallWhenNotDenied() + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallWhenNotDenied() + }) + + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallWhenNotDenied() + }) + }) }) }) }) }) - }) - }) - describe('gas costs', () => { - beforeEach('set an allowed severity issue', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) + describe('gas costs', () => { + beforeEach('set an allowed severity issue', async () => { + await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) - it('kill switch should overload ~32k of gas to a function', skipCoverage(async () => { - const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) - const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) + it('kill switch should overload ~16k of gas to a function', skipCoverage(async () => { + const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) + const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) - const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch - assert(killSwitchCost <= 32000, 'kill switch should overload ~32k of gas') - })) + const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch + assert(killSwitchCost <= 16000, 'kill switch should overload ~16k of gas') + })) + }) + }) }) }) From a13d491fbd86a23b8e30a671d08f0d0f76c80214 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 15:32:24 -0300 Subject: [PATCH 23/37] kill-switch: move kernel initialization logic to DAO factory --- contracts/factory/DAOFactory.sol | 110 ++++++++++++------- contracts/kernel/IKernel.sol | 1 - contracts/kernel/Kernel.sol | 23 +--- contracts/kill_switch/IIssuesRegistry.sol | 2 - contracts/kill_switch/IKillSwitch.sol | 2 - test/contracts/factory/evm_script_factory.js | 2 +- 6 files changed, 73 insertions(+), 67 deletions(-) diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index bd5d3e6ac..48228ec7b 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -3,8 +3,8 @@ pragma solidity 0.4.24; import "../kernel/IKernel.sol"; import "../kernel/Kernel.sol"; import "../kernel/KernelProxy.sol"; -import "../kill_switch/IKillSwitch.sol"; -import "../kill_switch/IIssuesRegistry.sol"; +import "../kill_switch/KillSwitch.sol"; +import "../kill_switch/IssuesRegistry.sol"; import "../acl/IACL.sol"; import "../acl/ACL.sol"; @@ -15,29 +15,29 @@ import "./EVMScriptRegistryFactory.sol"; contract DAOFactory { IKernel public baseKernel; IACL public baseACL; - IKillSwitch public baseKillSwitch; - EVMScriptRegistryFactory public regFactory; + KillSwitch public baseKillSwitch; + EVMScriptRegistryFactory public scriptsRegistryFactory; event DeployDAO(address dao); - event DeployEVMScriptRegistry(address reg); + 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 `_regFactory`. + * @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 _regFactory EVMScriptRegistry factory + * @param _scriptsRegistryFactory EVMScriptRegistry factory */ constructor( IKernel _baseKernel, IACL _baseACL, - IKillSwitch _baseKillSwitch, - EVMScriptRegistryFactory _regFactory + KillSwitch _baseKillSwitch, + EVMScriptRegistryFactory _scriptsRegistryFactory ) public { // No need to init as it cannot be killed by devops199 - if (address(_regFactory) != address(0)) { - regFactory = _regFactory; + if (address(_scriptsRegistryFactory) != address(0)) { + scriptsRegistryFactory = _scriptsRegistryFactory; } baseKernel = _baseKernel; @@ -51,15 +51,12 @@ contract DAOFactory { * @return Newly created DAO */ function newDAO(address _root) public returns (Kernel) { - Kernel dao = _newDAO(); - - if (address(regFactory) == address(0)) { - dao.initialize(baseACL, _root); - } else { - dao.initialize(baseACL, this); - _setupNewDaoPermissions(_root, dao); + if (address(scriptsRegistryFactory) == address(0)) { + return _createDAO(_root); } + Kernel dao = _createDAO(address(this)); + _setupNewDaoPermissions(dao, _root); return dao; } @@ -69,46 +66,81 @@ contract DAOFactory { * @param _issuesRegistry Address of the registry of issues that will be used in case of critical situations by the kill switch * @return Newly created DAO */ - function newDAOWithKillSwitch(address _root, IIssuesRegistry _issuesRegistry) public returns (Kernel) { - Kernel dao = _newDAO(); + function newDAOWithKillSwitch(address _root, IssuesRegistry _issuesRegistry) public returns (Kernel) { + Kernel dao = _createDAO(address(this)); + _createKillSwitch(dao, _issuesRegistry); - if (address(regFactory) == address(0)) { - dao.initializeWithKillSwitch(baseACL, _root, baseKillSwitch, _issuesRegistry); + if (address(scriptsRegistryFactory) == address(0)) { + _transferCreatePermissionsRole(dao, _root); } else { - dao.initializeWithKillSwitch(baseACL, address(this), baseKillSwitch, _issuesRegistry); - _setupNewDaoPermissions(_root, Kernel(dao)); + _setupNewDaoPermissions(dao, _root); } return dao; } - function _newDAO() internal returns (Kernel) { + function _createDAO(address _permissionsCreator) internal returns (Kernel) { Kernel dao = Kernel(new KernelProxy(baseKernel)); + dao.initialize(baseACL, _permissionsCreator); emit DeployDAO(address(dao)); return dao; } - function _setupNewDaoPermissions(address _root, Kernel _dao) internal { + function _createKillSwitch(Kernel _dao, IssuesRegistry _issuesRegistry) internal { + // create app manager role for this ACL acl = ACL(_dao.acl()); - bytes32 permRole = acl.CREATE_PERMISSIONS_ROLE(); bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); + acl.createPermission(address(this), _dao, appManagerRole, 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); + + // remove app manager role permissions from this + _removeAppManagerRole(_dao, address(this)); + } + + function _setupNewDaoPermissions(Kernel _dao, address _root) internal { + ACL acl = ACL(_dao.acl()); + + // grant create permissions role to the scripts registry factory + bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); + acl.grantPermission(scriptsRegistryFactory, acl, createPermissionsRole); + + // create app manager role to scripts registry factory and call + _createAppManagerRole(_dao, scriptsRegistryFactory); + EVMScriptRegistry scriptsRegistry = scriptsRegistryFactory.newEVMScriptRegistry(_dao); + emit DeployEVMScriptRegistry(address(scriptsRegistry)); - acl.grantPermission(regFactory, acl, permRole); + // remove app manager role permissions from the script registry factory + _removeAppManagerRole(_dao, scriptsRegistryFactory); - acl.createPermission(regFactory, _dao, appManagerRole, this); + // revoke create permissions role to the scripts registry factory + acl.revokePermission(scriptsRegistryFactory, acl, createPermissionsRole); - EVMScriptRegistry reg = regFactory.newEVMScriptRegistry(_dao); - emit DeployEVMScriptRegistry(address(reg)); + // transfer create permissions role from this to the root address + _transferCreatePermissionsRole(_dao, _root); + } + + function _createAppManagerRole(Kernel _dao, address _to) internal { + ACL acl = ACL(_dao.acl()); + bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); + acl.createPermission(_to, _dao, appManagerRole, address(this)); + } - // Clean up permissions - // First, completely reset the APP_MANAGER_ROLE - acl.revokePermission(regFactory, _dao, appManagerRole); + function _removeAppManagerRole(Kernel _dao, address _from) internal { + ACL acl = ACL(_dao.acl()); + bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); + acl.revokePermission(_from, _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); + function _transferCreatePermissionsRole(Kernel _dao, address _to) internal { + ACL acl = ACL(_dao.acl()); + bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); + acl.revokePermission(address(this), acl, createPermissionsRole); + acl.grantPermission(_to, acl, createPermissionsRole); + acl.setPermissionManager(_to, acl, createPermissionsRole); } } diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index f66b609ca..4be90a018 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -17,7 +17,6 @@ interface IKernelEvents { // This should be an interface, but interfaces can't inherit yet :( contract IKernel is IKernelEvents, IVaultRecoverable { function acl() public view returns (IACL); - function killSwitch() public view returns (IKillSwitch); function hasPermission(address who, address where, bytes32 what, bytes how) public view returns (bool); function setApp(bytes32 namespace, bytes32 appId, address app) public; diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 1dd800af0..3fd0c7950 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -22,9 +22,9 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant */ bytes32 public constant APP_MANAGER_ROLE = 0xb6d92708f3d4817afc106147d969e229ced5c46e65e0a5002a0d391287762bd0; + string private constant ERROR_AUTH_FAILED = "KERNEL_AUTH_FAILED"; string private constant ERROR_APP_NOT_CONTRACT = "KERNEL_APP_NOT_CONTRACT"; string private constant ERROR_INVALID_APP_CHANGE = "KERNEL_INVALID_APP_CHANGE"; - string private constant ERROR_AUTH_FAILED = "KERNEL_AUTH_FAILED"; /** * @dev Constructor that allows the deployer to choose if the base instance should be petrified immediately. @@ -56,27 +56,6 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant recoveryVaultAppId = KERNEL_DEFAULT_VAULT_APP_ID; } - /** - * @dev Initialize can only be called once. It saves the block number in which it was initialized. - * @notice Initialize this kernel instance, its ACL setting `_permissionsCreator` as the entity that can create other permissions, and a KillSwitch instance setting `_issuesRegistry` - * @param _baseAcl Address of base ACL app - * @param _permissionsCreator Entity that will be given permission over createPermission - * @param _baseKillSwitch Address of base KillSwitch app - * @param _issuesRegistry Issues registry that will act as the default source of truth to provide info about applications issues - */ - function initializeWithKillSwitch(IACL _baseAcl, address _permissionsCreator, IKillSwitch _baseKillSwitch, IIssuesRegistry _issuesRegistry) - public onlyInit - { - // Set and create ACL app - initialize(_baseAcl, _permissionsCreator); - - // Set and create KillSwitch app - _setApp(KERNEL_APP_BASES_NAMESPACE, KERNEL_DEFAULT_KILL_SWITCH_APP_ID, _baseKillSwitch); - IKillSwitch killSwitch = IKillSwitch(newAppProxy(this, KERNEL_DEFAULT_KILL_SWITCH_APP_ID)); - killSwitch.initialize(_issuesRegistry); - _setApp(KERNEL_APP_ADDR_NAMESPACE, KERNEL_DEFAULT_KILL_SWITCH_APP_ID, killSwitch); - } - /** * @dev Create a new instance of an app linked to this kernel * @notice Create a new upgradeable instance of `_appId` app linked to the Kernel, setting its code to `_appBase` diff --git a/contracts/kill_switch/IIssuesRegistry.sol b/contracts/kill_switch/IIssuesRegistry.sol index 425c4726e..5e7e8d0c8 100644 --- a/contracts/kill_switch/IIssuesRegistry.sol +++ b/contracts/kill_switch/IIssuesRegistry.sol @@ -6,8 +6,6 @@ contract IIssuesRegistry { event SeveritySet(address indexed implementation, Severity severity, address sender); - function initialize() external; - function setSeverityFor(address implementation, Severity severity) external; function isSeverityFor(address implementation) public view returns (bool); diff --git a/contracts/kill_switch/IKillSwitch.sol b/contracts/kill_switch/IKillSwitch.sol index f7ed881cc..1a2bf3533 100644 --- a/contracts/kill_switch/IKillSwitch.sol +++ b/contracts/kill_switch/IKillSwitch.sol @@ -4,7 +4,5 @@ import "./IIssuesRegistry.sol"; contract IKillSwitch { - function initialize(IIssuesRegistry _issuesRegistry) external; - function shouldDenyCallingContract(address _contract) external returns (bool); } diff --git a/test/contracts/factory/evm_script_factory.js b/test/contracts/factory/evm_script_factory.js index f582bee3b..1fce475eb 100644 --- a/test/contracts/factory/evm_script_factory.js +++ b/test/contracts/factory/evm_script_factory.js @@ -49,7 +49,7 @@ contract('EVM Script Factory', accounts => { beforeEach(async () => { const receipt = await daoFact.newDAO(permissionsRoot) dao = Kernel.at(receipt.logs.filter(l => l.event == 'DeployDAO')[0].args.dao) - evmScriptReg = EVMScriptRegistry.at(receipt.logs.filter(l => l.event == 'DeployEVMScriptRegistry')[0].args.reg) + evmScriptReg = EVMScriptRegistry.at(receipt.logs.filter(l => l.event == 'DeployEVMScriptRegistry')[0].args.registry) acl = ACL.at(await dao.acl()) }) From 77b8b2e7251e55e7709dffffb076da3e5b61e3e7 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 15:55:14 -0300 Subject: [PATCH 24/37] kill-switch: rename issues registry `isSeverityFor` to `hasSeverity` --- contracts/kill_switch/IIssuesRegistry.sol | 4 ++-- contracts/kill_switch/IssuesRegistry.sol | 2 +- test/contracts/kill_switch/issues_registry.test.js | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/kill_switch/IIssuesRegistry.sol b/contracts/kill_switch/IIssuesRegistry.sol index 5e7e8d0c8..a807a15dc 100644 --- a/contracts/kill_switch/IIssuesRegistry.sol +++ b/contracts/kill_switch/IIssuesRegistry.sol @@ -4,11 +4,11 @@ pragma solidity 0.4.24; contract IIssuesRegistry { enum Severity { None, Low, Mid, High, Critical } - event SeveritySet(address indexed implementation, Severity severity, address sender); + event SeveritySet(address indexed implementation, Severity severity, address indexed sender); function setSeverityFor(address implementation, Severity severity) external; - function isSeverityFor(address implementation) public view returns (bool); + function hasSeverity(address implementation) public view returns (bool); function getSeverityFor(address implementation) public view returns (Severity); } diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill_switch/IssuesRegistry.sol index 339190e26..dd8f730ad 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill_switch/IssuesRegistry.sol @@ -21,7 +21,7 @@ contract IssuesRegistry is IIssuesRegistry, AragonApp { emit SeveritySet(implementation, severity, msg.sender); } - function isSeverityFor(address implementation) public view isInitialized returns (bool) { + function hasSeverity(address implementation) public view isInitialized returns (bool) { return issuesSeverity[implementation] != Severity.None; } diff --git a/test/contracts/kill_switch/issues_registry.test.js b/test/contracts/kill_switch/issues_registry.test.js index a5e85c07a..cb7aa3a6e 100644 --- a/test/contracts/kill_switch/issues_registry.test.js +++ b/test/contracts/kill_switch/issues_registry.test.js @@ -37,10 +37,10 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { await acl.createPermission(owner, issuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) }) - describe('isSeverityFor', () => { + describe('hasSeverity', () => { context('when there was no severity set before', () => { it('returns false', async () => { - assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') + assert.isFalse(await issuesRegistry.hasSeverity(implementation), 'did not expect severity for given implementation') }) }) @@ -51,7 +51,7 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { context('when the issues was not fixed yet', () => { it('returns true', async () => { - assert.isTrue(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') + assert.isTrue(await issuesRegistry.hasSeverity(implementation), 'did not expect severity for given implementation') }) }) @@ -61,7 +61,7 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { }) it('returns false', async () => { - assert.isFalse(await issuesRegistry.isSeverityFor(implementation), 'did not expect severity for given implementation') + assert.isFalse(await issuesRegistry.hasSeverity(implementation), 'did not expect severity for given implementation') }) }) }) From d49926762b8bc6a9ecd65cdddc484ccb5441c6ad Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 15:59:10 -0300 Subject: [PATCH 25/37] kill-switch: rename `killSwitched` modifier to `killSwitchProtected` --- contracts/apps/AragonApp.sol | 2 +- contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index f4f8e17a9..d8e73461d 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -33,7 +33,7 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua _; } - modifier killSwitched { + modifier killSwitchProtected { IKernel _kernel = kernel(); bytes4 selector = _kernel.shouldDenyCallingContract.selector; bytes memory callData = abi.encodeWithSelector(selector, appId()); diff --git a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol index 73e17955f..863b3e315 100644 --- a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol +++ b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol @@ -17,7 +17,7 @@ contract KillSwitchedAppMock is AragonApp { return data; } - function write(uint256 _data) public killSwitched { + function write(uint256 _data) public killSwitchProtected { data = _data; } @@ -25,7 +25,7 @@ contract KillSwitchedAppMock is AragonApp { data = _data; } - function reset() public killSwitched { + function reset() public killSwitchProtected { data = 0; } } From 17922cc31386cb1d008941c07f03ab34c409c71f Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 7 May 2019 23:56:28 -0300 Subject: [PATCH 26/37] kill-switch: improve settings to customize different scenarios --- contracts/apps/AragonApp.sol | 2 +- contracts/kernel/Kernel.sol | 2 +- contracts/kill_switch/IKillSwitch.sol | 2 +- contracts/kill_switch/KillSwitch.sol | 110 ++-- .../test/mocks/common/KeccakConstants.sol | 3 +- test/contracts/common/keccak_constants.js | 3 +- test/contracts/kill_switch/enums.js | 2 - .../contracts/kill_switch/kill_switch.test.js | 493 ++++++++++-------- 8 files changed, 342 insertions(+), 275 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index d8e73461d..ddc306058 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -39,7 +39,7 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua bytes memory callData = abi.encodeWithSelector(selector, appId()); bool success = address(_kernel).call(callData); - // perform a check only if kernel supports "shouldDenyCallingContract" method + // perform a check only if kernel supports "shouldDenyCallingApp" method if (success) { uint256 _outputLength; assembly { _outputLength := returndatasize } diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 3fd0c7950..889056bde 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -176,7 +176,7 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant } address _baseApp = getApp(KERNEL_APP_BASES_NAMESPACE, _appId); - return _killSwitch.shouldDenyCallingContract(_baseApp); + return _killSwitch.shouldDenyCallingApp(_appId, _baseApp, msg.sender); } // External access to default app id and namespace constants to mimic default getters for constants diff --git a/contracts/kill_switch/IKillSwitch.sol b/contracts/kill_switch/IKillSwitch.sol index 1a2bf3533..fae82833e 100644 --- a/contracts/kill_switch/IKillSwitch.sol +++ b/contracts/kill_switch/IKillSwitch.sol @@ -4,5 +4,5 @@ import "./IIssuesRegistry.sol"; contract IKillSwitch { - function shouldDenyCallingContract(address _contract) external returns (bool); + function shouldDenyCallingApp(bytes32 _appId, address _base, address _proxy) external returns (bool); } diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index 14f077cd9..f8e5b33c3 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -10,33 +10,35 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { /* * Hardcoded constants to save gas * bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); + * bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = keccak256("SET_ALLOWED_INSTANCES_ROLE"); + * bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = keccak256("SET_DENIED_BASE_IMPLS_ROLE"); * bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); - * bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); * bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); */ bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = 0xec32b556caaf18ff28362d6b89f3f678177fb74ae2c5c78bfbac6b1dedfa6b43; + bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = 0x98ff612ed29ae4d49b4e102b7554cfaba413a7f9c345ecd1c920f91df1eb22e8; + bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = 0x6ec1c2a4f70ec94acd884927a40806e8282a03b3a489ac3c5551aee638767a33; bytes32 constant public SET_ISSUES_REGISTRY_ROLE = 0xc347b194ad4bc72077d417e05508bb224b4be509950d86cc7756e39a78fb725b; - bytes32 constant public SET_CONTRACT_ACTION_ROLE = 0xc7e0b4d70cab2a2679fe330e7c518a6e245cc494b086c284bfeb5f5d03fbe3f6; bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = 0xca159ccee5d02309b609308bfc70aecedaf2d2023cd19f9c223d8e9875a256ba; string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; - enum ContractAction { Allow, Check, Deny } - - struct Settings { - ContractAction action; - IIssuesRegistry.Severity highestAllowedSeverity; + struct IssuesSettings { IIssuesRegistry issuesRegistry; + IIssuesRegistry.Severity highestAllowedSeverity; } IIssuesRegistry public defaultIssuesRegistry; - mapping (address => Settings) internal contractSettings; + mapping (address => bool) internal allowedInstances; + mapping (address => bool) internal deniedBaseImplementations; + mapping (bytes32 => IssuesSettings) internal appsIssuesSettings; event DefaultIssuesRegistrySet(address issuesRegistry); - event ContractActionSet(address indexed contractAddress, ContractAction action); - event IssuesRegistrySet(address indexed contractAddress, address issuesRegistry); - event HighestAllowedSeveritySet(address indexed contractAddress, IIssuesRegistry.Severity severity); + event AllowedInstanceSet(address indexed instance, bool allowed); + event DeniedBaseImplementationSet(address indexed base, bool denied); + event IssuesRegistrySet(bytes32 indexed appId, address issuesRegistry); + event HighestAllowedSeveritySet(bytes32 indexed appId, IIssuesRegistry.Severity severity); function initialize(IIssuesRegistry _defaultIssuesRegistry) external onlyInit { initialized(); @@ -50,44 +52,58 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { _setDefaultIssuesRegistry(_defaultIssuesRegistry); } - function setContractAction(address _contract, ContractAction _action) + function setAllowedInstance(address _instance, bool _allowed) external - authP(SET_CONTRACT_ACTION_ROLE, arr(_contract)) + authP(SET_ALLOWED_INSTANCES_ROLE, arr(_instance)) { - contractSettings[_contract].action = _action; - emit ContractActionSet(_contract, _action); + allowedInstances[_instance] = _allowed; + emit AllowedInstanceSet(_instance, _allowed); } - function setHighestAllowedSeverity(address _contract, IIssuesRegistry.Severity _severity) + function setDeniedBaseImplementation(address _base, bool _denied) external - authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_contract)) + authP(SET_DENIED_BASE_IMPLS_ROLE, arr(_base)) { - contractSettings[_contract].highestAllowedSeverity = _severity; - emit HighestAllowedSeveritySet(_contract, _severity); + deniedBaseImplementations[_base] = _denied; + emit DeniedBaseImplementationSet(_base, _denied); } - function setIssuesRegistry(address _contract, IIssuesRegistry _issuesRegistry) + function setIssuesRegistry(bytes32 _appId, IIssuesRegistry _issuesRegistry) external - authP(SET_ISSUES_REGISTRY_ROLE, arr(_contract)) + authP(SET_ISSUES_REGISTRY_ROLE, arr(_appId)) { require(isContract(_issuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); - contractSettings[_contract].issuesRegistry = _issuesRegistry; - emit IssuesRegistrySet(_contract, address(_issuesRegistry)); + appsIssuesSettings[_appId].issuesRegistry = _issuesRegistry; + emit IssuesRegistrySet(_appId, address(_issuesRegistry)); } - function shouldDenyCallingContract(address _contract) external returns (bool) { - // if the contract is denied, then deny given call - if (isContractDenied(_contract)) { - return true; - } + function setHighestAllowedSeverity(bytes32 _appId, IIssuesRegistry.Severity _severity) + external + authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_appId)) + { + appsIssuesSettings[_appId].highestAllowedSeverity = _severity; + emit HighestAllowedSeveritySet(_appId, _severity); + } - // if the contract is allowed, then allow given call - if (isContractAllowed(_contract)) { + /** + * @dev Note that we are not checking if the appId, base address and instance address are valid and if they correspond + * to each other in order to reduce extra calls. However, since this is only a query method, wrong input + * can only result in invalid output. Internally, this method is used from the Kernel to stop calls if needed, + * and we have several tests to make sure its usage is working as expected. + */ + function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external returns (bool) { + // if the instance is allowed, then allow given call + if (isInstanceAllowed(_instance)) { return false; } - // if the contract severity found is ignored, then allow given call - if (isSeverityIgnored(_contract)) { + // if the base implementation is denied, then deny given call + if (isBaseImplementationDenied(_base)) { + return true; + } + + // if the app severity found is ignored, then allow given call + if (isSeverityIgnored(_appId, _base)) { return false; } @@ -95,31 +111,27 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { return true; } - function getContractAction(address _contract) public view returns (ContractAction) { - return contractSettings[_contract].action; + function isInstanceAllowed(address _instance) public view returns (bool) { + return allowedInstances[_instance]; } - function getHighestAllowedSeverity(address _contract) public view returns (IIssuesRegistry.Severity) { - return contractSettings[_contract].highestAllowedSeverity; + function isBaseImplementationDenied(address _base) public view returns (bool) { + return deniedBaseImplementations[_base]; } - function getIssuesRegistry(address _contract) public view returns (IIssuesRegistry) { - IIssuesRegistry foundRegistry = contractSettings[_contract].issuesRegistry; - return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; - } - - function isContractAllowed(address _contract) public view returns (bool) { - return getContractAction(_contract) == ContractAction.Allow; + function isSeverityIgnored(bytes32 _appId, address _base) public view returns (bool) { + IIssuesRegistry.Severity severityFound = getIssuesRegistry(_appId).getSeverityFor(_base); + IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_appId); + return highestAllowedSeverity >= severityFound; } - function isContractDenied(address _contract) public view returns (bool) { - return getContractAction(_contract) == ContractAction.Deny; + function getIssuesRegistry(bytes32 _appId) public view returns (IIssuesRegistry) { + IIssuesRegistry foundRegistry = appsIssuesSettings[_appId].issuesRegistry; + return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; } - function isSeverityIgnored(address _contract) public view returns (bool) { - IIssuesRegistry.Severity severityFound = getIssuesRegistry(_contract).getSeverityFor(_contract); - IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_contract); - return highestAllowedSeverity >= severityFound; + function getHighestAllowedSeverity(bytes32 _appId) public view returns (IIssuesRegistry.Severity) { + return appsIssuesSettings[_appId].highestAllowedSeverity; } function _setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) internal { diff --git a/contracts/test/mocks/common/KeccakConstants.sol b/contracts/test/mocks/common/KeccakConstants.sol index d5df4dd8c..20a054ddc 100644 --- a/contracts/test/mocks/common/KeccakConstants.sol +++ b/contracts/test/mocks/common/KeccakConstants.sol @@ -30,8 +30,9 @@ contract KeccakConstants { // KillSwitch bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); + bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = keccak256("SET_ALLOWED_INSTANCES_ROLE"); + bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = keccak256("SET_DENIED_BASE_IMPLS_ROLE"); bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); - bytes32 constant public SET_CONTRACT_ACTION_ROLE = keccak256("SET_CONTRACT_ACTION_ROLE"); bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); // APMRegistry diff --git a/test/contracts/common/keccak_constants.js b/test/contracts/common/keccak_constants.js index 14bc6055a..44ef4d0ec 100644 --- a/test/contracts/common/keccak_constants.js +++ b/test/contracts/common/keccak_constants.js @@ -128,8 +128,9 @@ contract('Constants', () => { const killSwitch = await getContract('KillSwitch').new() assert.equal(await killSwitch.SET_DEFAULT_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_DEFAULT_ISSUES_REGISTRY_ROLE()) + assert.equal(await killSwitch.SET_ALLOWED_INSTANCES_ROLE(), await keccakConstants.SET_ALLOWED_INSTANCES_ROLE()) + assert.equal(await killSwitch.SET_DENIED_BASE_IMPLS_ROLE(), await keccakConstants.SET_DENIED_BASE_IMPLS_ROLE()) assert.equal(await killSwitch.SET_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_ISSUES_REGISTRY_ROLE()) - assert.equal(await killSwitch.SET_CONTRACT_ACTION_ROLE(), await keccakConstants.SET_CONTRACT_ACTION_ROLE()) assert.equal(await killSwitch.SET_HIGHEST_ALLOWED_SEVERITY_ROLE(), await keccakConstants.SET_HIGHEST_ALLOWED_SEVERITY_ROLE()) }) }) diff --git a/test/contracts/kill_switch/enums.js b/test/contracts/kill_switch/enums.js index dcdae8e07..26374b223 100644 --- a/test/contracts/kill_switch/enums.js +++ b/test/contracts/kill_switch/enums.js @@ -1,7 +1,5 @@ -const ACTION = { ALLOW: 0, CHECK: 1, DENY: 2 } const SEVERITY = { NONE: 0, LOW: 1, MID: 2, HIGH: 3, CRITICAL: 4 } module.exports = { - ACTION, SEVERITY } diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill_switch/kill_switch.test.js index 56bcc62fb..6faf606a4 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill_switch/kill_switch.test.js @@ -1,4 +1,4 @@ -const { ACTION, SEVERITY } = require('./enums') +const { SEVERITY } = require('./enums') const { skipCoverage } = require('../../helpers/coverage') const { assertRevert } = require('../../helpers/assertThrow') const { getEvents, getEvent, getEventArgument } = require('../../helpers/events') @@ -18,7 +18,7 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { let registryFactory, dao, acl, app let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory, oldKernelBase - let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, SET_SEVERITY_ROLE, SET_DEFAULT_ISSUES_REGISTRY_ROLE, SET_ISSUES_REGISTRY_ROLE, SET_CONTRACT_ACTION_ROLE, SET_HIGHEST_ALLOWED_SEVERITY_ROLE + let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, SET_SEVERITY_ROLE, SET_DEFAULT_ISSUES_REGISTRY_ROLE, SET_ISSUES_REGISTRY_ROLE, SET_ALLOWED_INSTANCES_ROLE, SET_DENIED_BASE_IMPLS_ROLE, SET_HIGHEST_ALLOWED_SEVERITY_ROLE before('deploy base implementations', async () => { kernelBase = await Kernel.new(true) // petrify immediately @@ -38,7 +38,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() SET_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_ISSUES_REGISTRY_ROLE() - SET_CONTRACT_ACTION_ROLE = await killSwitchBase.SET_CONTRACT_ACTION_ROLE() + SET_ALLOWED_INSTANCES_ROLE = await killSwitchBase.SET_ALLOWED_INSTANCES_ROLE() + SET_DENIED_BASE_IMPLS_ROLE = await killSwitchBase.SET_DENIED_BASE_IMPLS_ROLE() SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() }) @@ -109,6 +110,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the kernel was initialized with a kill-switch', async () => { let killSwitch, defaultIssuesRegistry, specificIssuesRegistry + const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' + beforeEach('create issues registries', async () => { const daoReceipt = await daoFactory.newDAO(root) const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) @@ -132,133 +135,157 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) killSwitch = KillSwitch.at(await dao.killSwitch()) - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_CONTRACT_ACTION_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_ALLOWED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_DENIED_BASE_IMPLS_ROLE, root, { from: root }) await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) await app.initialize(owner) }) - describe('isContractAllowed', function () { - context('when there was no action previously set', function () { - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + describe('isInstanceAllowed', function () { + context('when there was no instance allowed value set yet', function () { + it('returns false', async () => { + assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) }) }) - context('when there was an action set', function () { - context('when the contract is allowed', function () { - beforeEach('allow contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + context('when there was an allowed value already set', function () { + context('when it is allowed', function () { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from: owner }) }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractAllowed(appBase.address)) + it('returns true', async () => { + assert(await killSwitch.isInstanceAllowed(app.address)) }) }) - context('when the contract is being checked', function () { - beforeEach('check contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + context('when it is not allowed', function () { + beforeEach('do not allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from: owner }) }) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) }) }) + }) + }) + + describe('setAllowedInstance', function () { + context('when the sender is authorized', function () { + const from = owner - context('when the contract is denied', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + context('when there was no instance allowed yet', function () { + it('sets a new allowed value', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from }) + + assert(await killSwitch.isInstanceAllowed(app.address)) }) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractAllowed(appBase.address)) + it('emits an event', async () => { + const receipt = await killSwitch.setAllowedInstance(app.address, true, { from }) + + const events = getEvents(receipt, 'AllowedInstanceSet') + assert.equal(events.length, 1, 'number of AllowedInstanceSet events does not match') + + const event = getEvent(receipt, 'AllowedInstanceSet').args + assert.equal(event.allowed, true, 'allowed value does not match') + assert.equal(event.instance, app.address, 'instance address does not match') }) }) - }) - }) - describe('isContractDenied', function () { - context('when there was no action previously set', function () { - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + context('when there was a instance already allowed', function () { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from }) + }) + + it('changes the allowed value', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from }) + + assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) + }) }) }) - context('when there was an action set', function () { - context('when the contract is allowed', function () { - beforeEach('allow contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) - }) + context('when the sender is not authorized', function () { + const from = anyone - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) - }) + it('reverts', async () => { + await assertRevert(killSwitch.setAllowedInstance(app.address, true, { from })) + }) + }) + }) + + describe('isBaseImplementationDenied', function () { + context('when there was no denied value set yet', function () { + it('returns false', async () => { + assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) }) + }) - context('when the contract is being checked', function () { - beforeEach('check contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + context('when there was a denied value already set', function () { + context('when it is denied', function () { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) }) - it('returns false', async function () { - assert.isFalse(await killSwitch.isContractDenied(appBase.address)) + it('returns true', async () => { + assert.isTrue(await killSwitch.isBaseImplementationDenied(appBase.address)) }) }) - context('when the contract is denied', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + context('when it is not denied', function () { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) }) - it('returns true', async function () { - assert.isTrue(await killSwitch.isContractDenied(appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) }) }) }) }) - describe('setContractAction', function () { + describe('setDeniedBaseImplementation', function () { context('when the sender is authorized', function () { const from = owner - context('when there was no action set yet', function () { - it('sets a new action', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + context('when there was no base implementation denied yet', function () { + it('sets a new denied value', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + assert(await killSwitch.isBaseImplementationDenied(appBase.address)) }) it('emits an event', async () => { - const receipt = await await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) + const receipt = await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) - const events = getEvents(receipt, 'ContractActionSet') - assert.equal(events.length, 1, 'number of ContractActionSet events does not match') + const events = getEvents(receipt, 'DeniedBaseImplementationSet') + assert.equal(events.length, 1, 'number of DeniedBaseImplementationSet events does not match') - const event = getEvent(receipt, 'ContractActionSet').args - assert.equal(event.action, ACTION.DENY, 'action does not match') - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + const event = getEvent(receipt, 'DeniedBaseImplementationSet').args + assert.equal(event.base, appBase.address, 'base address does not match') + assert.equal(event.denied, true, 'denied value does not match') }) }) - context('when there was an action already set', function () { - beforeEach('deny contract', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.DENY) + context('when there was a base implementation already denied', function () { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) }) - it('changes the contract action', async function () { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from }) + it('changes the denied value', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from }) - assert.equal(await killSwitch.getContractAction(appBase.address), ACTION.ALLOW) + assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) }) }) }) @@ -266,8 +293,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the sender is not authorized', function () { const from = anyone - it('reverts', async function () { - await assertRevert(killSwitch.setContractAction(appBase.address, ACTION.DENY, { from })) + it('reverts', async () => { + await assertRevert(killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }), 'APP_AUTH_FAILED') }) }) }) @@ -275,17 +302,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { describe('getIssuesRegistry', function () { context('when there was no specific issues registry set', () => { it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) }) }) context('when there is a specific issues registry set', () => { beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from: owner }) + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from: owner }) }) it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) }) }) }) @@ -296,39 +323,39 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the given address is not a contract', () => { it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(appBase.address, ZERO_ADDRESS, { from })) + await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, ZERO_ADDRESS, { from })) }) }) context('when the given address is a contract', () => { context('when there was no specific issues registry set yet', function () { it('sets the given implementation', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), specificIssuesRegistry.address) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) }) it('emits an event', async () => { - const receipt = await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + const receipt = await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) const events = getEvents(receipt, 'IssuesRegistrySet') assert.equal(events.length, 1, 'number of IssuesRegistrySet events does not match') const event = getEvent(receipt, 'IssuesRegistrySet').args - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.appId, SAMPLE_APP_ID, 'app id does not match') assert.equal(event.issuesRegistry, specificIssuesRegistry.address, 'issues registry address does not match') }) }) context('when there was a specific issues registry set', function () { beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from }) + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) }) it('changes the issues registry', async () => { - await killSwitch.setIssuesRegistry(appBase.address, defaultIssuesRegistry.address, { from }) + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, defaultIssuesRegistry.address, { from }) - assert.equal(await killSwitch.getIssuesRegistry(appBase.address), defaultIssuesRegistry.address) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) }) }) }) @@ -338,7 +365,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const from = anyone it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(appBase.address, specificIssuesRegistry.address, { from })) + await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from })) }) }) }) @@ -400,17 +427,17 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is no bug registered', () => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) context('when there is a highest allowed severity set for the contract being called', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) }) @@ -422,38 +449,38 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isFalse(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) context('when there is a highest allowed severity set for the contract being called', () => { context('when the highest allowed severity is under the reported bug severity', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isFalse(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) context('when the highest allowed severity is equal to the reported bug severity', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) context('when the highest allowed severity is greater than the reported bug severity', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(appBase.address)) + assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) }) }) }) @@ -465,34 +492,34 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const from = owner context('when there was no severity set', function () { - it('sets the highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + it('sets the highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.HIGH) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.HIGH) }) it('emits an event', async () => { - const receipt = await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.HIGH, { from }) + const receipt = await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) const events = getEvents(receipt, 'HighestAllowedSeveritySet') assert.equal(events.length, 1, 'number of ContractActionSet events does not match') const event = getEvent(receipt, 'HighestAllowedSeveritySet').args - assert.equal(event.contractAddress, appBase.address, 'contract address does not match') + assert.equal(event.appId, SAMPLE_APP_ID, 'app id does not match') assert.equal(event.severity, SEVERITY.HIGH, 'highest severity does not match') }) }) context('when there was a previous severity set', function () { - beforeEach('set highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.LOW) + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from }) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.LOW) }) - it('changes the highest allowed severity', async function () { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from }) + it('changes the highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from }) - assert.equal(await killSwitch.getHighestAllowedSeverity(appBase.address), SEVERITY.MID) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.MID) }) }) }) @@ -500,8 +527,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the sender is not authorized', function () { const from = anyone - it('reverts', async function () { - await assertRevert(killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from })) + it('reverts', async () => { + await assertRevert(killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from })) }) }) }) @@ -516,39 +543,59 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) } - context('when the contract being called is denied', () => { - beforeEach('check calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, { from: owner }) + context('when the instance being called is allowed', () => { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from: owner }) }) - itExecutesTheCall() - }) + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) - context('when the contract being called is denied', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) + itExecutesTheCall() }) - itExecutesTheCall() + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) + context('when the instance being called is not marked as allowed', () => { + beforeEach('dot not allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from: owner }) }) - itExecutesTheCall() + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) }) } context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { + context('when there is no highest allowed severity set for the app being called', () => { itExecutesTheCallEvenIfDenied() }) context('when there is a highest allowed severity set for the contract being called', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) itExecutesTheCallEvenIfDenied() @@ -566,20 +613,24 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is a highest allowed severity set for the contract being called', () => { context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity bellow the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) + itExecutesTheCallEvenIfDenied() }) context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) itExecutesTheCallEvenIfDenied() }) context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from: owner }) }) itExecutesTheCallEvenIfDenied() @@ -602,31 +653,91 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) } - const itExecutesTheCallWhenNotDenied = () => { - context('when there was no action previously set', () => { - itExecutesTheCall() + const itExecutesTheCallOnlyWhenAllowed = () => { + context('when the instance being called is allowed', () => { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) }) - context('when there was an action set', () => { - context('when the contract being called is being checked', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) + context('when the instance being called is not marked as allowed', () => { + beforeEach('dot not allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + }) + } + + const itExecutesTheCallUnlessDisallowedAndDenied = () => { + context('when the instance being called is allowed', () => { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the contract being called is allowed', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, {from: owner}) + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() }) + }) - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, {from: owner}) + context('when the instance being called is not marked as allowed', () => { + beforeEach('dot not allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) }) itDoesNotExecuteTheCall() @@ -636,15 +747,15 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is no bug registered', () => { context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when there is a highest allowed severity set for the contract being called', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) }) @@ -655,88 +766,32 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the bug was not fixed yet', () => { context('when there is no highest allowed severity set for the contract being called', () => { - context('when there was no action previously set', () => { - itExecutesTheCall() - }) - - context('when there was an action set', () => { - context('when the contract being called is allowed', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is being checked', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) - }) - - itDoesNotExecuteTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) + itExecutesTheCallOnlyWhenAllowed() }) context('when there is a highest allowed severity set for the contract being called', () => { context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) - }) - - context('when there was no action previously set', () => { - itExecutesTheCall() + beforeEach('set highest allowed severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - context('when there was an action set', () => { - context('when the contract being called is allowed', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.ALLOW, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the contract being called is being checked', () => { - beforeEach('allow calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.CHECK, {from: owner}) - }) - - itDoesNotExecuteTheCall() - }) - - context('when the contract being called is denied', () => { - beforeEach('deny calling contract', async () => { - await killSwitch.setContractAction(appBase.address, ACTION.DENY, { from: owner }) - }) - - itDoesNotExecuteTheCall() - }) - }) + itExecutesTheCallOnlyWhenAllowed() }) context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) }) }) @@ -747,32 +802,32 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when there is a highest allowed severity set for the contract being called', () => { context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.LOW, { from: owner }) + beforeEach('set highest allowed severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.CRITICAL, { from: owner }) + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - itExecutesTheCallWhenNotDenied() + itExecutesTheCallUnlessDisallowedAndDenied() }) }) }) @@ -782,16 +837,16 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { describe('gas costs', () => { beforeEach('set an allowed severity issue', async () => { - await killSwitch.setHighestAllowedSeverity(appBase.address, SEVERITY.MID, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) - it('kill switch should overload ~16k of gas to a function', skipCoverage(async () => { + it('kill switch should overload ~27k of gas to a function', skipCoverage(async () => { const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch - assert(killSwitchCost <= 16000, 'kill switch should overload ~16k of gas') + assert(killSwitchCost <= 27000, 'kill switch should overload ~27k of gas') })) }) }) From 0f288ce0d8aa5052a4592a01f50aad310cdbc2f2 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 8 May 2019 01:03:24 -0300 Subject: [PATCH 27/37] kill-switch: add `DAOFactory` tests --- test/contracts/factory/dao_factory.js | 178 ++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 test/contracts/factory/dao_factory.js diff --git a/test/contracts/factory/dao_factory.js b/test/contracts/factory/dao_factory.js new file mode 100644 index 000000000..5caa7fb47 --- /dev/null +++ b/test/contracts/factory/dao_factory.js @@ -0,0 +1,178 @@ +const { getEventArgument } = require('../../helpers/events') + +const DAOFactory = artifacts.require('DAOFactory') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const KillSwitch = artifacts.require('KillSwitch') +const IssuesRegistry = artifacts.require('IssuesRegistry') +const EVMScriptRegistry = artifacts.require('EVMScriptRegistry') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') +const EVMScriptRegistryConstants = artifacts.require('EVMScriptRegistryConstantsMock') + +const ZERO_ADDR = '0x0000000000000000000000000000000000000000' + +contract('DAO Factory', ([_, root]) => { + let daoFactory, dao, acl, receipt + + let CORE_NAMESPACE, APP_ADDR_NAMESPACE, APP_BASES_NAMESPACE + let APP_MANAGER_ROLE, CREATE_PERMISSIONS_ROLE, REGISTRY_ADD_EXECUTOR_ROLE + let ACL_APP_ID, KERNEL_APP_ID, KILL_SWITCH_APP_ID, EVM_SCRIPT_REGISTRY_APP_ID + let kernelBase, aclBase, killSwitchBase, issuesRegistry, scriptsRegistryFactory, scriptsRegistryBase, scriptsRegistryConstants + + before('deploy base implementations', async () => { + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + killSwitchBase = await KillSwitch.new() + issuesRegistry = await IssuesRegistry.new() + scriptsRegistryFactory = await EVMScriptRegistryFactory.new() + scriptsRegistryConstants = await EVMScriptRegistryConstants.new() + scriptsRegistryBase = EVMScriptRegistry.at(await scriptsRegistryFactory.baseReg()) + }) + + before('load roles and constants', async () => { + ACL_APP_ID = await kernelBase.DEFAULT_ACL_APP_ID() + KERNEL_APP_ID = await kernelBase.KERNEL_APP_ID() + KILL_SWITCH_APP_ID = await kernelBase.DEFAULT_KILL_SWITCH_APP_ID() + EVM_SCRIPT_REGISTRY_APP_ID = await scriptsRegistryConstants.getEVMScriptRegistryAppId() + + CORE_NAMESPACE = await kernelBase.CORE_NAMESPACE() + APP_ADDR_NAMESPACE = await kernelBase.APP_ADDR_NAMESPACE() + APP_BASES_NAMESPACE = await kernelBase.APP_BASES_NAMESPACE() + + APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + CREATE_PERMISSIONS_ROLE = await aclBase.CREATE_PERMISSIONS_ROLE() + REGISTRY_ADD_EXECUTOR_ROLE = await scriptsRegistryBase.REGISTRY_ADD_EXECUTOR_ROLE() + }) + + const itCreatesADao = () => { + it('creates a new DAO', async () => { + assert(await dao.hasInitialized(), 'DAO should be initialized') + assert.equal(await dao.getApp(CORE_NAMESPACE, KERNEL_APP_ID), kernelBase.address) + assert.equal(await dao.getApp(APP_BASES_NAMESPACE, ACL_APP_ID), aclBase.address) + assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, ACL_APP_ID), acl.address) + }) + + it('sets the given root address as the permissions creator of the DAO', async () => { + assert(await acl.hasInitialized(), 'ACL should be initialized') + assert.equal(await acl.getPermissionManager(acl.address, CREATE_PERMISSIONS_ROLE), root) + assert.isTrue(await acl.hasPermission(root, acl.address, CREATE_PERMISSIONS_ROLE)) + assert.isFalse(await acl.hasPermission(daoFactory.address, acl.address, CREATE_PERMISSIONS_ROLE)) + }) + + it('does not create or grant app manager to the root address of the DAO', async () => { + assert.equal(await acl.getPermissionManager(dao.address, APP_MANAGER_ROLE), ZERO_ADDR) + assert.isFalse(await acl.hasPermission(root, dao.address, APP_MANAGER_ROLE)) + assert.isFalse(await acl.hasPermission(daoFactory.address, dao.address, APP_MANAGER_ROLE)) + }) + } + + const itDoesCreateAnEVMScriptsRegistry = () => { + it('deploys an EVM script registry with a script executor', async () => { + const scriptsRegistry = EVMScriptRegistry.at(getEventArgument(receipt, 'DeployEVMScriptRegistry', 'registry')) + + assert(await scriptsRegistry.hasInitialized(), 'EVM scripts registry should be initialized') + assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, EVM_SCRIPT_REGISTRY_APP_ID), scriptsRegistry.address) + assert.equal(await dao.getApp(APP_BASES_NAMESPACE, EVM_SCRIPT_REGISTRY_APP_ID), scriptsRegistryBase.address) + + const [executor] = await scriptsRegistry.executors(1) + assert.equal(executor, await scriptsRegistryFactory.baseCallScript()) + + assert.equal(await acl.getPermissionManager(scriptsRegistry.address, REGISTRY_ADD_EXECUTOR_ROLE), ZERO_ADDR) + assert.isFalse(await acl.hasPermission(root, scriptsRegistry.address, REGISTRY_ADD_EXECUTOR_ROLE)) + assert.isFalse(await acl.hasPermission(scriptsRegistryFactory.address, scriptsRegistry.address, REGISTRY_ADD_EXECUTOR_ROLE)) + }) + } + + const itDoesNotCreateAnEVMScriptsRegistry = () => { + it('does not deploy an EVM script registry with a script executor', async () => { + assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, EVM_SCRIPT_REGISTRY_APP_ID), ZERO_ADDR) + assert.equal(await dao.getApp(APP_BASES_NAMESPACE, EVM_SCRIPT_REGISTRY_APP_ID), ZERO_ADDR) + }) + } + + const itDoesCreateAKillSwitch = () => { + it('does install a kill switch instance', async () => { + const killSwitch = KillSwitch.at(await dao.killSwitch()) + + assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, KILL_SWITCH_APP_ID), killSwitch.address) + assert.equal(await dao.getApp(APP_BASES_NAMESPACE, KILL_SWITCH_APP_ID), killSwitchBase.address) + }) + } + + const itDoesNotCreateAKillSwitch = () => { + it('does not have a kill switch installed', async () => { + assert.equal(await dao.killSwitch(), ZERO_ADDR) + assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, KILL_SWITCH_APP_ID), ZERO_ADDR) + assert.equal(await dao.getApp(APP_BASES_NAMESPACE, KILL_SWITCH_APP_ID), ZERO_ADDR) + }) + } + + describe('newDAO', () => { + context('when it was created with an EVM scripts registry factory', () => { + before('create factory with an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, scriptsRegistryFactory.address) + }) + + before('create a DAO', async () => { + receipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) + + itCreatesADao() + itDoesCreateAnEVMScriptsRegistry() + itDoesNotCreateAKillSwitch() + }) + + context('when it was created without an EVM scripts registry factory', () => { + before('create factory without an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) + }) + + before('create a DAO', async () => { + receipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) + + itCreatesADao() + itDoesNotCreateAnEVMScriptsRegistry() + itDoesNotCreateAKillSwitch() + }) + }) + + describe('newDAOWithKillSwitch', () => { + context('when it was created with an EVM scripts registry factory', () => { + before('create factory with an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, scriptsRegistryFactory.address) + }) + + before('create a DAO', async () => { + receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) + + itCreatesADao() + itDoesCreateAnEVMScriptsRegistry() + itDoesCreateAKillSwitch() + }) + + context('when it was created without an EVM scripts registry factory', () => { + before('create factory without an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) + }) + + before('create a DAO', async () => { + receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) + + itCreatesADao() + itDoesNotCreateAnEVMScriptsRegistry() + itDoesCreateAKillSwitch() + }) + }) +}) From a5f6bfebdddc24ef5894803d595fbe56237c675c Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 8 May 2019 11:19:05 -0300 Subject: [PATCH 28/37] kill-switch: test non compliant kernel versions --- contracts/factory/DAOFactory.sol | 8 +- .../kill_switch/FailingKillSwitchMock.sol | 12 + .../KernelWithNonCompliantKillSwitchMock.sol | 18 ++ test/contracts/factory/dao_factory.js | 73 ++++-- .../contracts/kill_switch/kill_switch.test.js | 223 ++++++++++++++++-- 5 files changed, 293 insertions(+), 41 deletions(-) create mode 100644 contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol create mode 100644 contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index 48228ec7b..99f3ffe60 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -13,6 +13,8 @@ import "./EVMScriptRegistryFactory.sol"; contract DAOFactory { + string private constant ERROR_MISSING_BASE_KILL_SWITCH = "DF_MISSING_BASE_KILL_SWITCH"; + IKernel public baseKernel; IACL public baseACL; KillSwitch public baseKillSwitch; @@ -39,10 +41,12 @@ contract DAOFactory { if (address(_scriptsRegistryFactory) != address(0)) { scriptsRegistryFactory = _scriptsRegistryFactory; } + if (address(_baseKillSwitch) != address(0)) { + baseKillSwitch = _baseKillSwitch; + } baseKernel = _baseKernel; baseACL = _baseACL; - baseKillSwitch = _baseKillSwitch; } /** @@ -67,6 +71,8 @@ contract DAOFactory { * @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)); _createKillSwitch(dao, _issuesRegistry); diff --git a/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol b/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol new file mode 100644 index 000000000..360dfd48f --- /dev/null +++ b/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol @@ -0,0 +1,12 @@ +pragma solidity 0.4.24; + +import "../../../kill_switch/KillSwitch.sol"; + + +contract FailingKillSwitchMock is KillSwitch { + string private constant ERROR_FAIL = "KILL_SWITCH_FAIL!"; + + function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external returns (bool) { + revert(ERROR_FAIL); + } +} diff --git a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol new file mode 100644 index 000000000..5e903b759 --- /dev/null +++ b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol @@ -0,0 +1,18 @@ +pragma solidity 0.4.24; + +import "../../../kernel/Kernel.sol"; + + +/** + * @title KernelWithNonCompliantKillSwitchMock + * @dev This mock mimics a situation where the kernel returns an unexpected result for a kill-switch check + */ +contract KernelWithNonCompliantKillSwitchMock is Kernel { + constructor() Kernel(true) public {} + + function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + assembly { + return(0, 0x40) // returning 2 words instead of one + } + } +} diff --git a/test/contracts/factory/dao_factory.js b/test/contracts/factory/dao_factory.js index 5caa7fb47..4f4db1134 100644 --- a/test/contracts/factory/dao_factory.js +++ b/test/contracts/factory/dao_factory.js @@ -1,3 +1,4 @@ +const { assertRevert } = require('../../helpers/assertThrow') const { getEventArgument } = require('../../helpers/events') const DAOFactory = artifacts.require('DAOFactory') @@ -111,7 +112,7 @@ contract('DAO Factory', ([_, root]) => { describe('newDAO', () => { context('when it was created with an EVM scripts registry factory', () => { before('create factory with an EVM scripts registry factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, scriptsRegistryFactory.address) + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR, scriptsRegistryFactory.address) }) before('create a DAO', async () => { @@ -127,7 +128,7 @@ contract('DAO Factory', ([_, root]) => { context('when it was created without an EVM scripts registry factory', () => { before('create factory without an EVM scripts registry factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR, ZERO_ADDR) }) before('create a DAO', async () => { @@ -143,36 +144,60 @@ contract('DAO Factory', ([_, root]) => { }) describe('newDAOWithKillSwitch', () => { - context('when it was created with an EVM scripts registry factory', () => { - before('create factory with an EVM scripts registry factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, scriptsRegistryFactory.address) + context('when it was created with a base kill switch', () => { + context('when it was created with an EVM scripts registry factory', () => { + before('create factory with an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, scriptsRegistryFactory.address) + }) + + before('create a DAO', async () => { + receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) + + itCreatesADao() + itDoesCreateAnEVMScriptsRegistry() + itDoesCreateAKillSwitch() }) - before('create a DAO', async () => { - receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - }) + context('when it was created without an EVM scripts registry factory', () => { + before('create factory without an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) + }) - itCreatesADao() - itDoesCreateAnEVMScriptsRegistry() - itDoesCreateAKillSwitch() - }) + before('create a DAO', async () => { + receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + }) - context('when it was created without an EVM scripts registry factory', () => { - before('create factory without an EVM scripts registry factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, ZERO_ADDR) + itCreatesADao() + itDoesNotCreateAnEVMScriptsRegistry() + itDoesCreateAKillSwitch() }) + }) - before('create a DAO', async () => { - receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) + context('when it was created without a base kill switch', () => { + context('when it was created with an EVM scripts registry factory', () => { + before('create factory with an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR, scriptsRegistryFactory.address) + }) + + it('reverts', async () => { + await assertRevert(daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address), 'DF_MISSING_BASE_KILL_SWITCH') + }) }) - itCreatesADao() - itDoesNotCreateAnEVMScriptsRegistry() - itDoesCreateAKillSwitch() + context('when it was created without an EVM scripts registry factory', () => { + before('create factory without an EVM scripts registry factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, ZERO_ADDR, ZERO_ADDR) + }) + + it('reverts', async () => { + await assertRevert(daoFactory.newDAOWithKillSwitch(root, issuesRegistry.address), 'DF_MISSING_BASE_KILL_SWITCH') + }) + }) }) }) }) diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill_switch/kill_switch.test.js index 6faf606a4..1732f3ed0 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill_switch/kill_switch.test.js @@ -3,32 +3,39 @@ const { skipCoverage } = require('../../helpers/coverage') const { assertRevert } = require('../../helpers/assertThrow') const { getEvents, getEvent, getEventArgument } = require('../../helpers/events') -const KillSwitch = artifacts.require('KillSwitch') -const IssuesRegistry = artifacts.require('IssuesRegistry') -const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') -const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') - const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') const DAOFactory = artifacts.require('DAOFactory') +const KillSwitch = artifacts.require('KillSwitch') +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') +const FailingKillSwitchMock = artifacts.require('FailingKillSwitchMock') +const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') +const KernelWithNonCompliantKillSwitchMock = artifacts.require('KernelWithNonCompliantKillSwitchMock') + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let registryFactory, dao, acl, app - let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory, oldKernelBase + let dao, acl, app, registryFactory + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory + let kernelWithoutKillSwitchBase, kernelWithNonCompliantKillSwitchBase, failingKillSwitchBase let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, SET_SEVERITY_ROLE, SET_DEFAULT_ISSUES_REGISTRY_ROLE, SET_ISSUES_REGISTRY_ROLE, SET_ALLOWED_INSTANCES_ROLE, SET_DENIED_BASE_IMPLS_ROLE, SET_HIGHEST_ALLOWED_SEVERITY_ROLE before('deploy base implementations', async () => { + // real kernelBase = await Kernel.new(true) // petrify immediately aclBase = await ACL.new() registryFactory = await EVMScriptRegistryFactory.new() killSwitchBase = await KillSwitch.new() issuesRegistryBase = await IssuesRegistry.new() + + // mocks appBase = await KillSwitchedApp.new() - oldKernelBase = await KernelWithoutKillSwitchMock.new() - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + failingKillSwitchBase = await FailingKillSwitchMock.new() + kernelWithoutKillSwitchBase = await KernelWithoutKillSwitchMock.new() + kernelWithNonCompliantKillSwitchBase = await KernelWithNonCompliantKillSwitchMock.new() }) before('load constants and roles', async () => { @@ -44,14 +51,15 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when the kernel version does not support kill-switch logic', async () => { - beforeEach('deploy DAO with a kernel version not supporting kill-switch logic', async () => { + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, ZERO_ADDRESS, registryFactory.address) + }) + + beforeEach('deploy DAO without a kill switch', async () => { const receipt = await daoFactory.newDAO(root) dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - - // update the kernel to a mock version that doesn't supports kill-switch logic to mimic already deployed ones - await dao.setApp(CORE_NAMESPACE, KERNEL_APP_ID, oldKernelBase.address, { from: root }) }) beforeEach('create kill switched app', async () => { @@ -77,7 +85,13 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when the kernel version does support kill-switch logic', async () => { + const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' + context('when the kernel was not initialized with a kill-switch', async () => { + before('create DAO factory using a kernel that supports kill-switch logic', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + beforeEach('deploy DAO without a kill switch', async () => { const receipt = await daoFactory.newDAO(root) dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) @@ -107,11 +121,188 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - context('when the kernel was initialized with a kill-switch', async () => { + context('when the kernel is initialized with a non-compliant kill-switch implementation', async () => { + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelWithNonCompliantKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + + beforeEach('deploy DAO with a kill switch', async () => { + const receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistryBase.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) + }) + + describe('integration', () => { + context('when the function being called is not tagged', () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + }) + + context('when the function being called is tagged', () => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from: owner }), 'APP_UNEXPECTED_KERNEL_RESPONSE') + }) + }) + }) + }) + + context('when the kernel is initialized with a failing kill-switch implementation', async () => { + let killSwitch, defaultIssuesRegistry + + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, failingKillSwitchBase.address, registryFactory.address) + }) + + beforeEach('create issues registry', async () => { + const daoReceipt = await daoFactory.newDAO(root) + const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) + + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) + + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getEventArgument(defaultRegistryReceipt, 'NewAppProxy', 'proxy')) + await defaultIssuesRegistry.initialize() + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with a kill switch', async () => { + const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + killSwitch = KillSwitch.at(await dao.killSwitch()) + + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_ALLOWED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_DENIED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + await app.initialize(owner) + }) + + describe('integration', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + } + + context('when the function being called is not tagged', () => { + itExecutesTheCall() + }) + + context('when the function being called is tagged', () => { + const itAlwaysExecutesTheCall = () => { + context('when the instance being called is allowed', () => { + beforeEach('allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when the instance being called is not marked as allowed', () => { + beforeEach('dot not allow instance', async () => { + await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not denied', () => { + beforeEach('do not deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is denied', () => { + beforeEach('deny base implementation', async () => { + await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + } + + context('when there is no bug registered', () => { + itAlwaysExecutesTheCall() + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when there is no highest allowed severity set for the contract being called', () => { + itAlwaysExecutesTheCall() + }) + + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + }) + }) + }) + }) + }) + + context('when the kernel is initialized with a safe kill-switch implementation', async () => { let killSwitch, defaultIssuesRegistry, specificIssuesRegistry - const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' - + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + beforeEach('create issues registries', async () => { const daoReceipt = await daoFactory.newDAO(root) const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) From 7f73ac36677f364bf1c22aab84bfce9f3fb5866e Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 8 May 2019 11:23:16 -0300 Subject: [PATCH 29/37] kill-switch: allow core instances by default --- contracts/factory/DAOFactory.sol | 65 +++++++++++++++++---------- test/contracts/factory/dao_factory.js | 8 ++++ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index 99f3ffe60..001cb32ad 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -1,14 +1,12 @@ pragma solidity 0.4.24; +import "../acl/IACL.sol"; +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 "../acl/IACL.sol"; -import "../acl/ACL.sol"; - import "./EVMScriptRegistryFactory.sol"; @@ -35,7 +33,7 @@ contract DAOFactory { KillSwitch _baseKillSwitch, EVMScriptRegistryFactory _scriptsRegistryFactory ) - public + public { // No need to init as it cannot be killed by devops199 if (address(_scriptsRegistryFactory) != address(0)) { @@ -77,7 +75,7 @@ contract DAOFactory { _createKillSwitch(dao, _issuesRegistry); if (address(scriptsRegistryFactory) == address(0)) { - _transferCreatePermissionsRole(dao, _root); + _transferCreatePermissionsRole(dao, address(this), _root); } else { _setupNewDaoPermissions(dao, _root); } @@ -94,39 +92,54 @@ contract DAOFactory { function _createKillSwitch(Kernel _dao, IssuesRegistry _issuesRegistry) internal { // create app manager role for this - ACL acl = ACL(_dao.acl()); - bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); - acl.createPermission(address(this), _dao, appManagerRole, address(this)); + _createAppManagerRole(_dao, 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); + bytes memory initializeData = abi.encodeWithSelector(baseKillSwitch.initialize.selector, _issuesRegistry); + _dao.newAppInstance(killSwitchAppID, baseKillSwitch, initializeData, true); + _allowKillSwitchCoreInstances(_dao); // remove app manager role permissions from this _removeAppManagerRole(_dao, address(this)); } - function _setupNewDaoPermissions(Kernel _dao, address _root) internal { + function _allowKillSwitchCoreInstances(Kernel _dao) internal { + KillSwitch killSwitch = KillSwitch(_dao.killSwitch()); + + // create allow instances role for this ACL acl = ACL(_dao.acl()); + bytes32 setAllowedInstancesRole = killSwitch.SET_ALLOWED_INSTANCES_ROLE(); + acl.createPermission(address(this), killSwitch, setAllowedInstancesRole, address(this)); - // grant create permissions role to the scripts registry factory - bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); - acl.grantPermission(scriptsRegistryFactory, acl, createPermissionsRole); + // 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); - // create app manager role to scripts registry factory and call + // remove allow instances role from this + acl.revokePermission(address(this), killSwitch, setAllowedInstancesRole); + acl.removePermissionManager(killSwitch, setAllowedInstancesRole); + } + + function _setupNewDaoPermissions(Kernel _dao, address _root) internal { + // grant permissions to script registry factory + _grantCreatePermissionsRole(_dao, scriptsRegistryFactory); _createAppManagerRole(_dao, scriptsRegistryFactory); + + // create evm scripts registry EVMScriptRegistry scriptsRegistry = scriptsRegistryFactory.newEVMScriptRegistry(_dao); emit DeployEVMScriptRegistry(address(scriptsRegistry)); - // remove app manager role permissions from the script registry factory + // remove permissions from scripts registry factory and transfer to root address _removeAppManagerRole(_dao, scriptsRegistryFactory); + _transferCreatePermissionsRole(_dao, scriptsRegistryFactory, _root); + } - // revoke create permissions role to the scripts registry factory - acl.revokePermission(scriptsRegistryFactory, acl, createPermissionsRole); - - // transfer create permissions role from this to the root address - _transferCreatePermissionsRole(_dao, _root); + function _grantCreatePermissionsRole(Kernel _dao, address _to) internal { + ACL acl = ACL(_dao.acl()); + bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); + acl.grantPermission(_to, acl, createPermissionsRole); } function _createAppManagerRole(Kernel _dao, address _to) internal { @@ -142,10 +155,14 @@ contract DAOFactory { acl.removePermissionManager(_dao, appManagerRole); } - function _transferCreatePermissionsRole(Kernel _dao, address _to) internal { + function _transferCreatePermissionsRole(Kernel _dao, address _from, address _to) internal { ACL acl = ACL(_dao.acl()); bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); - acl.revokePermission(address(this), acl, createPermissionsRole); + acl.revokePermission(_from, acl, createPermissionsRole); + if (_from != address(this)) { + acl.revokePermission(address(this), acl, createPermissionsRole); + } + acl.grantPermission(_to, acl, createPermissionsRole); acl.setPermissionManager(_to, acl, createPermissionsRole); } diff --git a/test/contracts/factory/dao_factory.js b/test/contracts/factory/dao_factory.js index 4f4db1134..5c292dd92 100644 --- a/test/contracts/factory/dao_factory.js +++ b/test/contracts/factory/dao_factory.js @@ -99,6 +99,14 @@ contract('DAO Factory', ([_, root]) => { assert.equal(await dao.getApp(APP_ADDR_NAMESPACE, KILL_SWITCH_APP_ID), killSwitch.address) assert.equal(await dao.getApp(APP_BASES_NAMESPACE, KILL_SWITCH_APP_ID), killSwitchBase.address) }) + + it('allows the kernel, acl and kill switch instances by default', async () => { + const killSwitch = KillSwitch.at(await dao.killSwitch()) + + assert(await killSwitch.isInstanceAllowed(dao.address), 'Kernel instance should be allowed') + assert(await killSwitch.isInstanceAllowed(acl.address), 'ACL instance should be allowed') + assert(await killSwitch.isInstanceAllowed(killSwitch.address), 'kill switch instance should be allowed') + }) } const itDoesNotCreateAKillSwitch = () => { From 5230a86113358e2c323210dc6eda6a55b8d2db21 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Wed, 8 May 2019 15:47:22 -0300 Subject: [PATCH 30/37] kill-switch: optimize tests --- .../contracts/kill_switch/kill_switch.test.js | 104 ++++-------------- 1 file changed, 20 insertions(+), 84 deletions(-) diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill_switch/kill_switch.test.js index 1732f3ed0..01c15d3f9 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill_switch/kill_switch.test.js @@ -55,32 +55,20 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, ZERO_ADDRESS, registryFactory.address) }) - beforeEach('deploy DAO without a kill switch', async () => { - const receipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + beforeEach('deploy DAO without a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) await app.initialize(owner) }) - describe('integration', () => { - context('when the function being called is not tagged', () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - }) - - context('when the function being called is tagged', () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - }) + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) }) }) @@ -92,16 +80,14 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) }) - beforeEach('deploy DAO without a kill switch', async () => { - const receipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + before('deploy DAO without a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) + const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + app = KillSwitchedApp.at(getEventArgument(appReceipt, 'NewAppProxy', 'proxy')) await app.initialize(owner) }) @@ -126,14 +112,12 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { daoFactory = await DAOFactory.new(kernelWithNonCompliantKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) }) - beforeEach('deploy DAO with a kill switch', async () => { - const receipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistryBase.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + before('deploy DAO with a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistryBase.address) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - }) - beforeEach('create kill switched app', async () => { const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getEventArgument(receipt, 'NewAppProxy', 'proxy')) await app.initialize(owner) @@ -161,7 +145,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, failingKillSwitchBase.address, registryFactory.address) }) - beforeEach('create issues registry', async () => { + before('create issues registry', async () => { const daoReceipt = await daoFactory.newDAO(root) const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) @@ -780,17 +764,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { } context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the app being called', () => { - itExecutesTheCallEvenIfDenied() - }) - - context('when there is a highest allowed severity set for the contract being called', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) + itExecutesTheCallEvenIfDenied() }) context('when there is a bug registered', () => { @@ -798,35 +772,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallEvenIfDenied() - }) - - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity bellow the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity equal to the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity above the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from: owner }) - }) - - itExecutesTheCallEvenIfDenied() - }) - }) + itExecutesTheCallEvenIfDenied() }) }) @@ -937,17 +883,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { } context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessDisallowedAndDenied() - }) - - context('when there is a highest allowed severity set for the contract being called', () => { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallUnlessDisallowedAndDenied() - }) + itExecutesTheCallUnlessDisallowedAndDenied() }) context('when there is a bug registered', () => { From fd6348c1c8dca35f744b041dbc350bef9649506f Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Thu, 9 May 2019 08:27:04 -0300 Subject: [PATCH 31/37] kill-switch: parameterize instance being called in kernel --- contracts/apps/AragonApp.sol | 2 +- contracts/kernel/IKernel.sol | 2 +- contracts/kernel/Kernel.sol | 4 ++-- .../kill_switch/KernelWithNonCompliantKillSwitchMock.sol | 2 +- .../test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index ddc306058..a3bd823d1 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -36,7 +36,7 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua modifier killSwitchProtected { IKernel _kernel = kernel(); bytes4 selector = _kernel.shouldDenyCallingContract.selector; - bytes memory callData = abi.encodeWithSelector(selector, appId()); + bytes memory callData = abi.encodeWithSelector(selector, appId(), address(this)); bool success = address(_kernel).call(callData); // perform a check only if kernel supports "shouldDenyCallingApp" method diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index 4be90a018..609151a3f 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -21,5 +21,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) public returns (bool); + function shouldDenyCallingContract(bytes32 appId, address _instance) public returns (bool); } diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 889056bde..5e2c2731f 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -169,14 +169,14 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant * @param _appId Identifier for app to be checked * @return True is the given call should be denied, false otherwise */ - function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) { IKillSwitch _killSwitch = killSwitch(); if (address(_killSwitch) == address(0)) { return false; } address _baseApp = getApp(KERNEL_APP_BASES_NAMESPACE, _appId); - return _killSwitch.shouldDenyCallingApp(_appId, _baseApp, msg.sender); + return _killSwitch.shouldDenyCallingApp(_appId, _baseApp, _instance); } // External access to default app id and namespace constants to mimic default getters for constants diff --git a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol index 5e903b759..7f2b66a02 100644 --- a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol +++ b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol @@ -10,7 +10,7 @@ import "../../../kernel/Kernel.sol"; contract KernelWithNonCompliantKillSwitchMock is Kernel { constructor() Kernel(true) public {} - function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) { assembly { return(0, 0x40) // returning 2 words instead of one } diff --git a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol index ed9c888bb..e8e2910ef 100644 --- a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol +++ b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol @@ -16,7 +16,7 @@ contract KernelWithoutKillSwitchMock is Kernel { revert(ERROR_METHOD_NOT_FOUND); } - function shouldDenyCallingContract(bytes32 _appId) public returns (bool) { + function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) { revert(ERROR_METHOD_NOT_FOUND); } } From ae617f61f3410ddf12c6b5cdbdc37554226ca017 Mon Sep 17 00:00:00 2001 From: Facu Spagnuolo Date: Fri, 17 May 2019 13:01:56 -0300 Subject: [PATCH 32/37] kill-switch: apply suggestions from @izqui Co-Authored-By: Jorge Izquierdo --- contracts/apps/AragonApp.sol | 7 +- contracts/factory/DAOFactory.sol | 73 +++++++++---------- contracts/kernel/IKernel.sol | 1 - contracts/kernel/Kernel.sol | 1 - contracts/kill_switch/KillSwitch.sol | 10 +-- .../contracts/kill_switch/kill_switch.test.js | 14 ++-- 6 files changed, 51 insertions(+), 55 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index a3bd823d1..9b10397be 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -39,7 +39,8 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua bytes memory callData = abi.encodeWithSelector(selector, appId(), address(this)); bool success = address(_kernel).call(callData); - // perform a check only if kernel supports "shouldDenyCallingApp" method + // if the call to `kernel.shouldDenyCallingApp` reverts (using an old Kernel) we consider that + // there is no kill switch and the call should be allowed to continue if (success) { uint256 _outputLength; assembly { _outputLength := returndatasize } @@ -49,8 +50,8 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua bool _shouldDenyCall; assembly { let ptr := mload(0x40) // get next free memory pointer - mstore(0x40, add(ptr, returndatasize)) // set next free memory pointer - returndatacopy(ptr, 0, returndatasize) // copy call return value + mstore(0x40, add(ptr, 0x20)) // set next free memory pointer + returndatacopy(ptr, 0, 0x20) // copy call return value _shouldDenyCall := mload(ptr) // read data } require(!_shouldDenyCall, ERROR_CONTRACT_CALL_NOT_ALLOWED); diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index 001cb32ad..17a78f4b9 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -33,7 +33,7 @@ contract DAOFactory { KillSwitch _baseKillSwitch, EVMScriptRegistryFactory _scriptsRegistryFactory ) - public + public { // No need to init as it cannot be killed by devops199 if (address(_scriptsRegistryFactory) != address(0)) { @@ -58,7 +58,8 @@ contract DAOFactory { } Kernel dao = _createDAO(address(this)); - _setupNewDaoPermissions(dao, _root); + ACL acl = ACL(dao.acl()); + _setupEVMScriptRegistry(dao, acl, _root); return dao; } @@ -72,12 +73,13 @@ contract DAOFactory { require(address(baseKillSwitch) != address(0), ERROR_MISSING_BASE_KILL_SWITCH); Kernel dao = _createDAO(address(this)); - _createKillSwitch(dao, _issuesRegistry); + ACL acl = ACL(dao.acl()); + _createKillSwitch(dao, acl, _issuesRegistry); if (address(scriptsRegistryFactory) == address(0)) { - _transferCreatePermissionsRole(dao, address(this), _root); + _transferCreatePermissionsRole(dao, acl, address(this), _root); } else { - _setupNewDaoPermissions(dao, _root); + _setupEVMScriptRegistry(dao, acl, _root); } return dao; @@ -90,80 +92,75 @@ contract DAOFactory { return dao; } - function _createKillSwitch(Kernel _dao, IssuesRegistry _issuesRegistry) internal { + function _createKillSwitch(Kernel _dao, ACL _acl, IssuesRegistry _issuesRegistry) internal { // create app manager role for this - _createAppManagerRole(_dao, address(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); + _allowKillSwitchCoreInstances(_dao, _acl); // remove app manager role permissions from this - _removeAppManagerRole(_dao, address(this)); + _removeAppManagerRole(_dao, _acl, address(this)); } - function _allowKillSwitchCoreInstances(Kernel _dao) internal { + function _allowKillSwitchCoreInstances(Kernel _dao, ACL _acl) internal { KillSwitch killSwitch = KillSwitch(_dao.killSwitch()); // create allow instances role for this - ACL acl = ACL(_dao.acl()); bytes32 setAllowedInstancesRole = killSwitch.SET_ALLOWED_INSTANCES_ROLE(); - acl.createPermission(address(this), killSwitch, setAllowedInstancesRole, address(this)); + _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(_acl), true); killSwitch.setAllowedInstance(address(killSwitch), true); // remove allow instances role from this - acl.revokePermission(address(this), killSwitch, setAllowedInstancesRole); - acl.removePermissionManager(killSwitch, setAllowedInstancesRole); + _acl.revokePermission(address(this), killSwitch, setAllowedInstancesRole); + _acl.removePermissionManager(killSwitch, setAllowedInstancesRole); } - function _setupNewDaoPermissions(Kernel _dao, address _root) internal { + function _setupEVMScriptRegistry(Kernel _dao, ACL _acl, address _root) internal { // grant permissions to script registry factory - _grantCreatePermissionsRole(_dao, scriptsRegistryFactory); - _createAppManagerRole(_dao, scriptsRegistryFactory); + _grantCreatePermissionsRole(_dao, _acl, scriptsRegistryFactory); + _createAppManagerRole(_dao, _acl, scriptsRegistryFactory); // create evm scripts registry EVMScriptRegistry scriptsRegistry = scriptsRegistryFactory.newEVMScriptRegistry(_dao); emit DeployEVMScriptRegistry(address(scriptsRegistry)); // remove permissions from scripts registry factory and transfer to root address - _removeAppManagerRole(_dao, scriptsRegistryFactory); - _transferCreatePermissionsRole(_dao, scriptsRegistryFactory, _root); + _removeAppManagerRole(_dao, _acl, scriptsRegistryFactory); + _transferCreatePermissionsRole(_dao, _acl, scriptsRegistryFactory, _root); } - function _grantCreatePermissionsRole(Kernel _dao, address _to) internal { - ACL acl = ACL(_dao.acl()); - bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); - acl.grantPermission(_to, acl, createPermissionsRole); + function _grantCreatePermissionsRole(Kernel _dao, ACL _acl, address _to) internal { + bytes32 createPermissionsRole = _acl.CREATE_PERMISSIONS_ROLE(); + _acl.grantPermission(_to, _acl, createPermissionsRole); } - function _createAppManagerRole(Kernel _dao, address _to) internal { - ACL acl = ACL(_dao.acl()); + function _createAppManagerRole(Kernel _dao, ACL _acl, address _to) internal { bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); - acl.createPermission(_to, _dao, appManagerRole, address(this)); + _acl.createPermission(_to, _dao, appManagerRole, address(this)); } - function _removeAppManagerRole(Kernel _dao, address _from) internal { - ACL acl = ACL(_dao.acl()); + function _removeAppManagerRole(Kernel _dao, ACL _acl, address _from) internal { bytes32 appManagerRole = _dao.APP_MANAGER_ROLE(); - acl.revokePermission(_from, _dao, appManagerRole); - acl.removePermissionManager(_dao, appManagerRole); + _acl.revokePermission(_from, _dao, appManagerRole); + _acl.removePermissionManager(_dao, appManagerRole); } - function _transferCreatePermissionsRole(Kernel _dao, address _from, address _to) internal { - ACL acl = ACL(_dao.acl()); - bytes32 createPermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); - acl.revokePermission(_from, acl, createPermissionsRole); + 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); + _acl.revokePermission(address(this), _acl, createPermissionsRole); } - acl.grantPermission(_to, acl, createPermissionsRole); - acl.setPermissionManager(_to, acl, createPermissionsRole); + _acl.grantPermission(_to, _acl, createPermissionsRole); + _acl.setPermissionManager(_to, _acl, createPermissionsRole); } } diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index 609151a3f..c97207eee 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -5,7 +5,6 @@ pragma solidity ^0.4.24; import "../acl/IACL.sol"; -import "../kill_switch/IKillSwitch.sol"; import "../common/IVaultRecoverable.sol"; diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 5e2c2731f..f5c823a7f 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -11,7 +11,6 @@ import "../common/Petrifiable.sol"; import "../common/VaultRecoverable.sol"; import "../factory/AppProxyFactory.sol"; import "../kill_switch/IKillSwitch.sol"; -import "../kill_switch/IIssuesRegistry.sol"; import "../lib/misc/ERCProxy.sol"; diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol index f8e5b33c3..94a6a43cc 100644 --- a/contracts/kill_switch/KillSwitch.sol +++ b/contracts/kill_switch/KillSwitch.sol @@ -15,7 +15,6 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { * bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); * bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); */ - bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = 0xec32b556caaf18ff28362d6b89f3f678177fb74ae2c5c78bfbac6b1dedfa6b43; bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = 0x98ff612ed29ae4d49b4e102b7554cfaba413a7f9c345ecd1c920f91df1eb22e8; bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = 0x6ec1c2a4f70ec94acd884927a40806e8282a03b3a489ac3c5551aee638767a33; @@ -34,7 +33,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { mapping (address => bool) internal deniedBaseImplementations; mapping (bytes32 => IssuesSettings) internal appsIssuesSettings; - event DefaultIssuesRegistrySet(address issuesRegistry); + event DefaultIssuesRegistrySet(address indexed issuesRegistry); event AllowedInstanceSet(address indexed instance, bool allowed); event DeniedBaseImplementationSet(address indexed base, bool denied); event IssuesRegistrySet(bytes32 indexed appId, address issuesRegistry); @@ -102,8 +101,9 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { return true; } - // if the app severity found is ignored, then allow given call - if (isSeverityIgnored(_appId, _base)) { + // Check if there is a severity issue reported in the corresponding issue registry. If there is actually a + // severity issue, check against the kill switch settings if we allow such severity level or not. + if (checkSeverityIssuesFor(_appId, _base)) { return false; } @@ -119,7 +119,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { return deniedBaseImplementations[_base]; } - function isSeverityIgnored(bytes32 _appId, address _base) public view returns (bool) { + function checkSeverityIssuesFor(bytes32 _appId, address _base) public view returns (bool) { IIssuesRegistry.Severity severityFound = getIssuesRegistry(_appId).getSeverityFor(_base); IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_appId); return highestAllowedSeverity >= severityFound; diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill_switch/kill_switch.test.js index a79465107..ac24a55f9 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill_switch/kill_switch.test.js @@ -584,11 +584,11 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - describe('isSeverityIgnored', function () { + describe('checkSeverityIssuesFor', function () { context('when there is no bug registered', () => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) @@ -598,7 +598,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) }) @@ -610,7 +610,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isFalse(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) @@ -621,7 +621,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns false', async () => { - assert.isFalse(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isFalse(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) @@ -631,7 +631,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) @@ -641,7 +641,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.isSeverityIgnored(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) }) }) }) From ce7a29bf9d359abeab95c9a6b9075fe90ed8722d Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Fri, 17 May 2019 15:59:56 -0300 Subject: [PATCH 33/37] kill-switch: extract `killSwitchProtected` modifier to function --- contracts/apps/AragonApp.sol | 53 +++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index 9b10397be..35f67aa4b 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -34,28 +34,7 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua } modifier killSwitchProtected { - IKernel _kernel = kernel(); - bytes4 selector = _kernel.shouldDenyCallingContract.selector; - bytes memory callData = abi.encodeWithSelector(selector, appId(), address(this)); - bool success = address(_kernel).call(callData); - - // if the call to `kernel.shouldDenyCallingApp` reverts (using an old Kernel) we consider that - // there is no kill switch and the call should be allowed to continue - if (success) { - uint256 _outputLength; - assembly { _outputLength := returndatasize } - // we expect 32 bytes length returned value here - require(_outputLength == 32, ERROR_UNEXPECTED_KERNEL_RESPONSE); - - bool _shouldDenyCall; - assembly { - let ptr := mload(0x40) // get next free memory pointer - mstore(0x40, add(ptr, 0x20)) // set next free memory pointer - returndatacopy(ptr, 0, 0x20) // copy call return value - _shouldDenyCall := mload(ptr) // read data - } - require(!_shouldDenyCall, ERROR_CONTRACT_CALL_NOT_ALLOWED); - } + require(canExecuteCall(), ERROR_CONTRACT_CALL_NOT_ALLOWED); _; } @@ -85,6 +64,36 @@ 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) { + IKernel _kernel = kernel(); + bytes4 selector = _kernel.shouldDenyCallingContract.selector; + bytes memory callData = abi.encodeWithSelector(selector, appId(), address(this)); + bool success = address(_kernel).call(callData); + + // if the call to `kernel.shouldDenyCallingApp` reverts (using an old Kernel) we consider that + // there is no kill switch and the call can be executed be allowed to continue + if (!success) return true; + + // if not, first ensure the returned value is 32 bytes length + uint256 _outputLength; + assembly { _outputLength := returndatasize } + require(_outputLength == 32, ERROR_UNEXPECTED_KERNEL_RESPONSE); + + // forward returned value + bool _shouldDenyCall; + assembly { + let ptr := mload(0x40) // get next free memory pointer + mstore(0x40, add(ptr, 0x20)) // set next free memory pointer + returndatacopy(ptr, 0, 0x20) // copy call return value + _shouldDenyCall := mload(ptr) // read data + } + return !_shouldDenyCall; + } + /** * @dev Get the recovery vault for the app * @return Recovery vault address for the app From 08f1a8bb66b0d99fd4105a1d0396f40455ebdd07 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Mon, 20 May 2019 18:54:31 -0300 Subject: [PATCH 34/37] kill-switch: address feedback from @sohkai --- contracts/acl/ACLSyntaxSugar.sol | 36 +- contracts/apps/AragonApp.sol | 29 +- contracts/factory/DAOFactory.sol | 146 +++---- contracts/kernel/IKernel.sol | 2 +- contracts/kernel/Kernel.sol | 14 +- .../IIssuesRegistry.sol | 2 +- .../IKillSwitch.sol | 2 +- .../IssuesRegistry.sol | 6 +- contracts/kill-switch/KillSwitch.sol | 142 +++++++ contracts/kill_switch/KillSwitch.sol | 142 ------- .../test/mocks/common/KeccakConstants.sol | 10 +- .../kill_switch/FailingKillSwitchMock.sol | 12 - .../KernelWithNonCompliantKillSwitchMock.sol | 2 +- .../KernelWithoutKillSwitchMock.sol | 2 +- .../mocks/kill_switch/KillSwitchedAppMock.sol | 25 +- .../kill_switch/RevertingKillSwitchMock.sol | 12 + test/contracts/common/keccak_constants.js | 10 +- test/contracts/factory/dao_factory.js | 16 +- .../{kill_switch => kill-switch}/enums.js | 0 .../issues_registry.js} | 14 +- .../kill_switch.js} | 397 +++++++++--------- 21 files changed, 542 insertions(+), 479 deletions(-) rename contracts/{kill_switch => kill-switch}/IIssuesRegistry.sol (77%) rename contracts/{kill_switch => kill-switch}/IKillSwitch.sol (75%) rename contracts/{kill_switch => kill-switch}/IssuesRegistry.sol (73%) create mode 100644 contracts/kill-switch/KillSwitch.sol delete mode 100644 contracts/kill_switch/KillSwitch.sol delete mode 100644 contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol create mode 100644 contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol rename test/contracts/{kill_switch => kill-switch}/enums.js (100%) rename test/contracts/{kill_switch/issues_registry.test.js => kill-switch/issues_registry.js} (90%) rename test/contracts/{kill_switch/kill_switch.test.js => kill-switch/kill_switch.js} (62%) diff --git a/contracts/acl/ACLSyntaxSugar.sol b/contracts/acl/ACLSyntaxSugar.sol index f5683c0bf..e4aa57ed5 100644 --- a/contracts/acl/ACLSyntaxSugar.sol +++ b/contracts/acl/ACLSyntaxSugar.sol @@ -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); } @@ -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); } @@ -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); + } } diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index 35f67aa4b..6de106bd6 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -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) { @@ -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 @@ -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; } @@ -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; diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index 17a78f4b9..6511412fd 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -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"; @@ -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( @@ -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; @@ -53,20 +55,37 @@ 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) { @@ -74,14 +93,31 @@ contract DAOFactory { 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; } @@ -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); } } diff --git a/contracts/kernel/IKernel.sol b/contracts/kernel/IKernel.sol index c97207eee..dc5de6558 100644 --- a/contracts/kernel/IKernel.sol +++ b/contracts/kernel/IKernel.sol @@ -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); } diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index f5c823a7f..9b8900674 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -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"; @@ -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; @@ -207,7 +209,7 @@ 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) { @@ -215,7 +217,7 @@ contract Kernel is IKernel, KernelStorage, KernelAppIds, KernelNamespaceConstant } /** - * @dev Get the installed KillSwitch app + * @dev Get the default KillSwitch app * @return KillSwitch app */ function killSwitch() public view returns (IKillSwitch) { diff --git a/contracts/kill_switch/IIssuesRegistry.sol b/contracts/kill-switch/IIssuesRegistry.sol similarity index 77% rename from contracts/kill_switch/IIssuesRegistry.sol rename to contracts/kill-switch/IIssuesRegistry.sol index a807a15dc..97c927a63 100644 --- a/contracts/kill_switch/IIssuesRegistry.sol +++ b/contracts/kill-switch/IIssuesRegistry.sol @@ -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; diff --git a/contracts/kill_switch/IKillSwitch.sol b/contracts/kill-switch/IKillSwitch.sol similarity index 75% rename from contracts/kill_switch/IKillSwitch.sol rename to contracts/kill-switch/IKillSwitch.sol index fae82833e..cacec5ff8 100644 --- a/contracts/kill_switch/IKillSwitch.sol +++ b/contracts/kill-switch/IKillSwitch.sol @@ -4,5 +4,5 @@ import "./IIssuesRegistry.sol"; contract IKillSwitch { - function shouldDenyCallingApp(bytes32 _appId, address _base, address _proxy) external returns (bool); + function shouldDenyCallingApp(bytes32 _appId, address _base, address _proxy) external view returns (bool); } diff --git a/contracts/kill_switch/IssuesRegistry.sol b/contracts/kill-switch/IssuesRegistry.sol similarity index 73% rename from contracts/kill_switch/IssuesRegistry.sol rename to contracts/kill-switch/IssuesRegistry.sol index dd8f730ad..892d80d47 100644 --- a/contracts/kill_switch/IssuesRegistry.sol +++ b/contracts/kill-switch/IssuesRegistry.sol @@ -5,7 +5,7 @@ import "./IIssuesRegistry.sol"; contract IssuesRegistry is IIssuesRegistry, AragonApp { - bytes32 constant public SET_SEVERITY_ROLE = keccak256("SET_SEVERITY_ROLE"); + bytes32 constant public CHANGE_SEVERITY_ROLE = keccak256("CHANGE_SEVERITY_ROLE"); mapping (address => Severity) internal issuesSeverity; @@ -15,10 +15,10 @@ contract IssuesRegistry is IIssuesRegistry, AragonApp { function setSeverityFor(address implementation, Severity severity) external - authP(SET_SEVERITY_ROLE, arr(implementation, uint256(severity))) + authP(CHANGE_SEVERITY_ROLE, arr(implementation, uint256(issuesSeverity[implementation]), uint256(severity))) { issuesSeverity[implementation] = severity; - emit SeveritySet(implementation, severity, msg.sender); + emit ChangeSeverity(implementation, severity, msg.sender); } function hasSeverity(address implementation) public view isInitialized returns (bool) { diff --git a/contracts/kill-switch/KillSwitch.sol b/contracts/kill-switch/KillSwitch.sol new file mode 100644 index 000000000..a85c14000 --- /dev/null +++ b/contracts/kill-switch/KillSwitch.sol @@ -0,0 +1,142 @@ +pragma solidity 0.4.24; + +import "./IKillSwitch.sol"; +import "./IIssuesRegistry.sol"; +import "../apps/AragonApp.sol"; +import "../common/IsContract.sol"; + + +contract KillSwitch is IKillSwitch, IsContract, AragonApp { + /* + * Hardcoded constants to save gas + * bytes32 constant public CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE"); + * bytes32 constant public CHANGE_WHITELISTED_INSTANCES_ROLE = keccak256("CHANGE_WHITELISTED_INSTANCES_ROLE"); + * bytes32 constant public CHANGE_BLACKLISTED_BASE_IMPLS_ROLE = keccak256("CHANGE_BLACKLISTED_BASE_IMPLS_ROLE"); + * bytes32 constant public CHANGE_ISSUES_REGISTRY_ROLE = keccak256("CHANGE_ISSUES_REGISTRY_ROLE"); + * bytes32 constant public CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE"); + */ + bytes32 constant public CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = 0xdc8509ec9a919d33309806f4c91c281bcd27100bf2f895bcf78c5b42a0c39517; + bytes32 constant public CHANGE_WHITELISTED_INSTANCES_ROLE = 0x015a45e5f33fcae59ca7bd74eb36669dbf842f279d59011ea683d2867d05464a; + bytes32 constant public CHANGE_BLACKLISTED_BASE_IMPLS_ROLE = 0x05c71f33783f36a1b1a40c12d7308ff84c475600d0a4ff736122d42d72eafd4c; + bytes32 constant public CHANGE_ISSUES_REGISTRY_ROLE = 0x05b8a6bf0cdb51438256b73559daacd20b321e9c934d472dddb8f6cf12e6e048; + bytes32 constant public CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = 0x1aec2a88cc5515dccebf91f7653b986b872c1cea4b784dc2eb5d285a6ccb2998; + + string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; + + struct IssuesSettings { + IIssuesRegistry issuesRegistry; + IIssuesRegistry.Severity highestAllowedSeverity; + } + + IIssuesRegistry public defaultIssuesRegistry; + mapping (address => bool) internal whitelistedInstances; + mapping (address => bool) internal blacklistedBaseImplementations; + mapping (bytes32 => IssuesSettings) internal appsIssuesSettings; + + event ChangeDefaultIssuesRegistry(address indexed issuesRegistry); + event ChangeWhitelistedInstance(address indexed instance, bool whitelisted); + event ChangeBlacklistedBaseImplementation(address indexed base, bool blacklisted); + event ChangeIssuesRegistry(bytes32 indexed appId, address issuesRegistry); + event ChangeHighestAllowedSeverity(bytes32 indexed appId, IIssuesRegistry.Severity severity); + + function initialize(IIssuesRegistry _defaultIssuesRegistry) external onlyInit { + initialized(); + _setDefaultIssuesRegistry(_defaultIssuesRegistry); + } + + function setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) + external + auth(CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE) + { + _setDefaultIssuesRegistry(_defaultIssuesRegistry); + } + + function setWhitelistedInstance(address _instance, bool _allowed) + external + authP(CHANGE_WHITELISTED_INSTANCES_ROLE, arr(_instance, whitelistedInstances[_instance], _allowed)) + { + whitelistedInstances[_instance] = _allowed; + emit ChangeWhitelistedInstance(_instance, _allowed); + } + + function setBlacklistedBaseImplementation(address _base, bool _denied) + external + authP(CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, arr(_base, blacklistedBaseImplementations[_base], _denied)) + { + blacklistedBaseImplementations[_base] = _denied; + emit ChangeBlacklistedBaseImplementation(_base, _denied); + } + + function setIssuesRegistry(bytes32 _appId, IIssuesRegistry _issuesRegistry) + external + authP(CHANGE_ISSUES_REGISTRY_ROLE, arr(_appId, address(appsIssuesSettings[_appId].issuesRegistry), address(_issuesRegistry))) + { + require(isContract(_issuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); + appsIssuesSettings[_appId].issuesRegistry = _issuesRegistry; + emit ChangeIssuesRegistry(_appId, address(_issuesRegistry)); + } + + function setHighestAllowedSeverity(bytes32 _appId, IIssuesRegistry.Severity _severity) + external + authP(CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_appId, uint256(appsIssuesSettings[_appId].highestAllowedSeverity), uint256(_severity))) + { + appsIssuesSettings[_appId].highestAllowedSeverity = _severity; + emit ChangeHighestAllowedSeverity(_appId, _severity); + } + + /** + * @dev Note that we are not checking if the appId, base address and instance address are valid and if they correspond + * to each other in order to reduce extra calls. However, since this is only a query method, wrong input + * can only result in invalid output. Internally, this method is used from the Kernel to stop calls if needed, + * and we have several tests to make sure its usage is working as expected. + */ + function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external view returns (bool) { + // if the instance is whitelisted, then allow given call + if (isInstanceWhitelisted(_instance)) { + return false; + } + + // if the base implementation is blacklisted, then deny given call + if (isBaseImplementationBlacklisted(_base)) { + return true; + } + + // Check if there is a severity issue reported in the corresponding issue registry. If there is actually a + // severity issue, check against the kill switch settings if we allow such severity level or not. + if (hasExceededAllowedSeverity(_appId, _base)) { + return false; + } + + // if none of the conditions above were met, then deny given call + return true; + } + + function isInstanceWhitelisted(address _instance) public view returns (bool) { + return whitelistedInstances[_instance]; + } + + function isBaseImplementationBlacklisted(address _base) public view returns (bool) { + return blacklistedBaseImplementations[_base]; + } + + function hasExceededAllowedSeverity(bytes32 _appId, address _base) public view returns (bool) { + IIssuesRegistry.Severity severityFound = getIssuesRegistry(_appId).getSeverityFor(_base); + IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_appId); + return highestAllowedSeverity >= severityFound; + } + + function getIssuesRegistry(bytes32 _appId) public view returns (IIssuesRegistry) { + IIssuesRegistry foundRegistry = appsIssuesSettings[_appId].issuesRegistry; + return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; + } + + function getHighestAllowedSeverity(bytes32 _appId) public view returns (IIssuesRegistry.Severity) { + return appsIssuesSettings[_appId].highestAllowedSeverity; + } + + function _setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) internal { + require(isContract(_defaultIssuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); + defaultIssuesRegistry = _defaultIssuesRegistry; + emit ChangeDefaultIssuesRegistry(address(_defaultIssuesRegistry)); + } +} diff --git a/contracts/kill_switch/KillSwitch.sol b/contracts/kill_switch/KillSwitch.sol deleted file mode 100644 index 94a6a43cc..000000000 --- a/contracts/kill_switch/KillSwitch.sol +++ /dev/null @@ -1,142 +0,0 @@ -pragma solidity 0.4.24; - -import "./IKillSwitch.sol"; -import "./IIssuesRegistry.sol"; -import "../apps/AragonApp.sol"; -import "../common/IsContract.sol"; - - -contract KillSwitch is IKillSwitch, IsContract, AragonApp { - /* - * Hardcoded constants to save gas - * bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); - * bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = keccak256("SET_ALLOWED_INSTANCES_ROLE"); - * bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = keccak256("SET_DENIED_BASE_IMPLS_ROLE"); - * bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); - * bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); - */ - bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = 0xec32b556caaf18ff28362d6b89f3f678177fb74ae2c5c78bfbac6b1dedfa6b43; - bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = 0x98ff612ed29ae4d49b4e102b7554cfaba413a7f9c345ecd1c920f91df1eb22e8; - bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = 0x6ec1c2a4f70ec94acd884927a40806e8282a03b3a489ac3c5551aee638767a33; - bytes32 constant public SET_ISSUES_REGISTRY_ROLE = 0xc347b194ad4bc72077d417e05508bb224b4be509950d86cc7756e39a78fb725b; - bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = 0xca159ccee5d02309b609308bfc70aecedaf2d2023cd19f9c223d8e9875a256ba; - - string constant private ERROR_ISSUES_REGISTRY_NOT_CONTRACT = "KS_ISSUES_REGISTRY_NOT_CONTRACT"; - - struct IssuesSettings { - IIssuesRegistry issuesRegistry; - IIssuesRegistry.Severity highestAllowedSeverity; - } - - IIssuesRegistry public defaultIssuesRegistry; - mapping (address => bool) internal allowedInstances; - mapping (address => bool) internal deniedBaseImplementations; - mapping (bytes32 => IssuesSettings) internal appsIssuesSettings; - - event DefaultIssuesRegistrySet(address indexed issuesRegistry); - event AllowedInstanceSet(address indexed instance, bool allowed); - event DeniedBaseImplementationSet(address indexed base, bool denied); - event IssuesRegistrySet(bytes32 indexed appId, address issuesRegistry); - event HighestAllowedSeveritySet(bytes32 indexed appId, IIssuesRegistry.Severity severity); - - function initialize(IIssuesRegistry _defaultIssuesRegistry) external onlyInit { - initialized(); - _setDefaultIssuesRegistry(_defaultIssuesRegistry); - } - - function setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) - external - auth(SET_DEFAULT_ISSUES_REGISTRY_ROLE) - { - _setDefaultIssuesRegistry(_defaultIssuesRegistry); - } - - function setAllowedInstance(address _instance, bool _allowed) - external - authP(SET_ALLOWED_INSTANCES_ROLE, arr(_instance)) - { - allowedInstances[_instance] = _allowed; - emit AllowedInstanceSet(_instance, _allowed); - } - - function setDeniedBaseImplementation(address _base, bool _denied) - external - authP(SET_DENIED_BASE_IMPLS_ROLE, arr(_base)) - { - deniedBaseImplementations[_base] = _denied; - emit DeniedBaseImplementationSet(_base, _denied); - } - - function setIssuesRegistry(bytes32 _appId, IIssuesRegistry _issuesRegistry) - external - authP(SET_ISSUES_REGISTRY_ROLE, arr(_appId)) - { - require(isContract(_issuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); - appsIssuesSettings[_appId].issuesRegistry = _issuesRegistry; - emit IssuesRegistrySet(_appId, address(_issuesRegistry)); - } - - function setHighestAllowedSeverity(bytes32 _appId, IIssuesRegistry.Severity _severity) - external - authP(SET_HIGHEST_ALLOWED_SEVERITY_ROLE, arr(_appId)) - { - appsIssuesSettings[_appId].highestAllowedSeverity = _severity; - emit HighestAllowedSeveritySet(_appId, _severity); - } - - /** - * @dev Note that we are not checking if the appId, base address and instance address are valid and if they correspond - * to each other in order to reduce extra calls. However, since this is only a query method, wrong input - * can only result in invalid output. Internally, this method is used from the Kernel to stop calls if needed, - * and we have several tests to make sure its usage is working as expected. - */ - function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external returns (bool) { - // if the instance is allowed, then allow given call - if (isInstanceAllowed(_instance)) { - return false; - } - - // if the base implementation is denied, then deny given call - if (isBaseImplementationDenied(_base)) { - return true; - } - - // Check if there is a severity issue reported in the corresponding issue registry. If there is actually a - // severity issue, check against the kill switch settings if we allow such severity level or not. - if (checkSeverityIssuesFor(_appId, _base)) { - return false; - } - - // if none of the conditions above were met, then deny given call - return true; - } - - function isInstanceAllowed(address _instance) public view returns (bool) { - return allowedInstances[_instance]; - } - - function isBaseImplementationDenied(address _base) public view returns (bool) { - return deniedBaseImplementations[_base]; - } - - function checkSeverityIssuesFor(bytes32 _appId, address _base) public view returns (bool) { - IIssuesRegistry.Severity severityFound = getIssuesRegistry(_appId).getSeverityFor(_base); - IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_appId); - return highestAllowedSeverity >= severityFound; - } - - function getIssuesRegistry(bytes32 _appId) public view returns (IIssuesRegistry) { - IIssuesRegistry foundRegistry = appsIssuesSettings[_appId].issuesRegistry; - return foundRegistry == IIssuesRegistry(0) ? defaultIssuesRegistry : foundRegistry; - } - - function getHighestAllowedSeverity(bytes32 _appId) public view returns (IIssuesRegistry.Severity) { - return appsIssuesSettings[_appId].highestAllowedSeverity; - } - - function _setDefaultIssuesRegistry(IIssuesRegistry _defaultIssuesRegistry) internal { - require(isContract(_defaultIssuesRegistry), ERROR_ISSUES_REGISTRY_NOT_CONTRACT); - defaultIssuesRegistry = _defaultIssuesRegistry; - emit DefaultIssuesRegistrySet(address(_defaultIssuesRegistry)); - } -} diff --git a/contracts/test/mocks/common/KeccakConstants.sol b/contracts/test/mocks/common/KeccakConstants.sol index 20a054ddc..6ccd3adc6 100644 --- a/contracts/test/mocks/common/KeccakConstants.sol +++ b/contracts/test/mocks/common/KeccakConstants.sol @@ -29,11 +29,11 @@ contract KeccakConstants { bytes32 public constant EMPTY_PARAM_HASH = keccak256(abi.encodePacked(uint256(0))); // KillSwitch - bytes32 constant public SET_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("SET_DEFAULT_ISSUES_REGISTRY_ROLE"); - bytes32 constant public SET_ALLOWED_INSTANCES_ROLE = keccak256("SET_ALLOWED_INSTANCES_ROLE"); - bytes32 constant public SET_DENIED_BASE_IMPLS_ROLE = keccak256("SET_DENIED_BASE_IMPLS_ROLE"); - bytes32 constant public SET_ISSUES_REGISTRY_ROLE = keccak256("SET_ISSUES_REGISTRY_ROLE"); - bytes32 constant public SET_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("SET_HIGHEST_ALLOWED_SEVERITY_ROLE"); + bytes32 constant public CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = keccak256("CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE"); + bytes32 constant public CHANGE_WHITELISTED_INSTANCES_ROLE = keccak256("CHANGE_WHITELISTED_INSTANCES_ROLE"); + bytes32 constant public CHANGE_BLACKLISTED_BASE_IMPLS_ROLE = keccak256("CHANGE_BLACKLISTED_BASE_IMPLS_ROLE"); + bytes32 constant public CHANGE_ISSUES_REGISTRY_ROLE = keccak256("CHANGE_ISSUES_REGISTRY_ROLE"); + bytes32 constant public CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = keccak256("CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE"); // APMRegistry bytes32 public constant CREATE_REPO_ROLE = keccak256(abi.encodePacked("CREATE_REPO_ROLE")); diff --git a/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol b/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol deleted file mode 100644 index 360dfd48f..000000000 --- a/contracts/test/mocks/kill_switch/FailingKillSwitchMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity 0.4.24; - -import "../../../kill_switch/KillSwitch.sol"; - - -contract FailingKillSwitchMock is KillSwitch { - string private constant ERROR_FAIL = "KILL_SWITCH_FAIL!"; - - function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external returns (bool) { - revert(ERROR_FAIL); - } -} diff --git a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol index 7f2b66a02..997928138 100644 --- a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol +++ b/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol @@ -10,7 +10,7 @@ import "../../../kernel/Kernel.sol"; contract KernelWithNonCompliantKillSwitchMock is Kernel { constructor() Kernel(true) public {} - function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) { + function isAppDisabled(bytes32 _appId, address _instance) public view returns (bool) { assembly { return(0, 0x40) // returning 2 words instead of one } diff --git a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol index e8e2910ef..a5bc9d3f8 100644 --- a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol +++ b/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol @@ -16,7 +16,7 @@ contract KernelWithoutKillSwitchMock is Kernel { revert(ERROR_METHOD_NOT_FOUND); } - function shouldDenyCallingContract(bytes32 _appId, address _instance) public returns (bool) { + function isAppDisabled(bytes32 _appId, address _instance) public view returns (bool) { revert(ERROR_METHOD_NOT_FOUND); } } diff --git a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol index 863b3e315..aa0c4c075 100644 --- a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol +++ b/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol @@ -4,9 +4,17 @@ import "../../../apps/AragonApp.sol"; contract KillSwitchedAppMock is AragonApp { + bytes32 public constant WRITER_ROLE = keccak256("WRITER_ROLE"); + string private constant ERROR_AUTH_FAILED = "APP_AUTH_FAILED"; + address public owner; uint256 internal data; + modifier oldAuth(bytes32 _role) { + require(_oldCanPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); + _; + } + function initialize(address _owner) public onlyInit { initialized(); data = 42; @@ -17,15 +25,26 @@ contract KillSwitchedAppMock is AragonApp { return data; } - function write(uint256 _data) public killSwitchProtected { + function write(uint256 _data) public auth(WRITER_ROLE) { data = _data; } - function writeWithoutKillSwitch(uint256 _data) public { + function writeWithoutKillSwitch(uint256 _data) oldAuth(WRITER_ROLE) public { data = _data; } - function reset() public killSwitchProtected { + function reset() public auth(WRITER_ROLE) { data = 0; } + + function _oldCanPerform(address _sender, bytes32 _role, uint256[] _params) private view returns (bool) { + if (!hasInitialized()) { + return false; + } + IKernel _kernel = kernel(); + if (address(_kernel) == address(0)) { + return false; + } + return _kernel.hasPermission(_sender, address(this), _role, ConversionHelpers.dangerouslyCastUintArrayToBytes(_params)); + } } diff --git a/contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol b/contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol new file mode 100644 index 000000000..c2f5f003c --- /dev/null +++ b/contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol @@ -0,0 +1,12 @@ +pragma solidity 0.4.24; + +import "../../../kill-switch/KillSwitch.sol"; + + +contract RevertingKillSwitchMock is KillSwitch { + string private constant ERROR_MESSAGE = "KILL_SWITCH_REVERTED!"; + + function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external view returns (bool) { + revert(ERROR_MESSAGE); + } +} diff --git a/test/contracts/common/keccak_constants.js b/test/contracts/common/keccak_constants.js index 44ef4d0ec..8d460495b 100644 --- a/test/contracts/common/keccak_constants.js +++ b/test/contracts/common/keccak_constants.js @@ -127,10 +127,10 @@ contract('Constants', () => { it('checks KillSwitch constants', async () => { const killSwitch = await getContract('KillSwitch').new() - assert.equal(await killSwitch.SET_DEFAULT_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_DEFAULT_ISSUES_REGISTRY_ROLE()) - assert.equal(await killSwitch.SET_ALLOWED_INSTANCES_ROLE(), await keccakConstants.SET_ALLOWED_INSTANCES_ROLE()) - assert.equal(await killSwitch.SET_DENIED_BASE_IMPLS_ROLE(), await keccakConstants.SET_DENIED_BASE_IMPLS_ROLE()) - assert.equal(await killSwitch.SET_ISSUES_REGISTRY_ROLE(), await keccakConstants.SET_ISSUES_REGISTRY_ROLE()) - assert.equal(await killSwitch.SET_HIGHEST_ALLOWED_SEVERITY_ROLE(), await keccakConstants.SET_HIGHEST_ALLOWED_SEVERITY_ROLE()) + assert.equal(await killSwitch.CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE(), await keccakConstants.CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE()) + assert.equal(await killSwitch.CHANGE_WHITELISTED_INSTANCES_ROLE(), await keccakConstants.CHANGE_WHITELISTED_INSTANCES_ROLE()) + assert.equal(await killSwitch.CHANGE_BLACKLISTED_BASE_IMPLS_ROLE(), await keccakConstants.CHANGE_BLACKLISTED_BASE_IMPLS_ROLE()) + assert.equal(await killSwitch.CHANGE_ISSUES_REGISTRY_ROLE(), await keccakConstants.CHANGE_ISSUES_REGISTRY_ROLE()) + assert.equal(await killSwitch.CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE(), await keccakConstants.CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE()) }) }) diff --git a/test/contracts/factory/dao_factory.js b/test/contracts/factory/dao_factory.js index 5c292dd92..30d93f227 100644 --- a/test/contracts/factory/dao_factory.js +++ b/test/contracts/factory/dao_factory.js @@ -1,5 +1,6 @@ const { assertRevert } = require('../../helpers/assertThrow') const { getEventArgument } = require('../../helpers/events') +const { assertEvent, assertAmountOfEvents } = require('../../helpers/assertEvent')(web3) const DAOFactory = artifacts.require('DAOFactory') @@ -100,12 +101,19 @@ contract('DAO Factory', ([_, root]) => { assert.equal(await dao.getApp(APP_BASES_NAMESPACE, KILL_SWITCH_APP_ID), killSwitchBase.address) }) - it('allows the kernel, acl and kill switch instances by default', async () => { + it('whitelists the kernel, acl and kill switch instances by default', async () => { const killSwitch = KillSwitch.at(await dao.killSwitch()) - assert(await killSwitch.isInstanceAllowed(dao.address), 'Kernel instance should be allowed') - assert(await killSwitch.isInstanceAllowed(acl.address), 'ACL instance should be allowed') - assert(await killSwitch.isInstanceAllowed(killSwitch.address), 'kill switch instance should be allowed') + assert(await killSwitch.isInstanceWhitelisted(dao.address), 'Kernel instance should be whitelisted') + assert(await killSwitch.isInstanceWhitelisted(acl.address), 'ACL instance should be whitelisted') + assert(await killSwitch.isInstanceWhitelisted(killSwitch.address), 'KillSwitch instance should be whitelisted') + }) + + it('emits an event', async () => { + assertAmountOfEvents(receipt, 'DeployKillSwitch', 1) + + const killSwitch = KillSwitch.at(await dao.killSwitch()) + assertEvent(receipt, 'DeployKillSwitch', { killSwitch: killSwitch.address }) }) } diff --git a/test/contracts/kill_switch/enums.js b/test/contracts/kill-switch/enums.js similarity index 100% rename from test/contracts/kill_switch/enums.js rename to test/contracts/kill-switch/enums.js diff --git a/test/contracts/kill_switch/issues_registry.test.js b/test/contracts/kill-switch/issues_registry.js similarity index 90% rename from test/contracts/kill_switch/issues_registry.test.js rename to test/contracts/kill-switch/issues_registry.js index ee662b525..1aefe5788 100644 --- a/test/contracts/kill_switch/issues_registry.test.js +++ b/test/contracts/kill-switch/issues_registry.js @@ -34,8 +34,8 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { const receipt = await dao.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) issuesRegistry = IssuesRegistry.at(getNewProxyAddress(receipt)) await issuesRegistry.initialize() - const SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() - await acl.createPermission(owner, issuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + const CHANGE_SEVERITY_ROLE = await issuesRegistryBase.CHANGE_SEVERITY_ROLE() + await acl.createPermission(owner, issuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) }) describe('hasSeverity', () => { @@ -50,14 +50,14 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from: owner }) }) - context('when the issues was not fixed yet', () => { + context('when the issue was real', () => { it('returns true', async () => { assert.isTrue(await issuesRegistry.hasSeverity(implementation), 'did not expect severity for given implementation') }) }) - context('when the issues was already fixed', () => { - beforeEach('set medium severity', async () => { + context('when the issue was a false positive', () => { + beforeEach('roll back severity', async () => { await issuesRegistry.setSeverityFor(implementation, SEVERITY.NONE, { from: owner }) }) @@ -93,8 +93,8 @@ contract('IssuesRegistry', ([_, root, implementation, owner, anyone]) => { it('emits an event', async () => { const receipt = await issuesRegistry.setSeverityFor(implementation, SEVERITY.LOW, { from }) - assertAmountOfEvents(receipt, 'SeveritySet') - assertEvent(receipt, 'SeveritySet', { implementation, severity: SEVERITY.LOW, sender: owner }) + assertAmountOfEvents(receipt, 'ChangeSeverity') + assertEvent(receipt, 'ChangeSeverity', { implementation, severity: SEVERITY.LOW, sender: owner }) }) context('when there was no severity set before', () => { diff --git a/test/contracts/kill_switch/kill_switch.test.js b/test/contracts/kill-switch/kill_switch.js similarity index 62% rename from test/contracts/kill_switch/kill_switch.test.js rename to test/contracts/kill-switch/kill_switch.js index ac24a55f9..48899937c 100644 --- a/test/contracts/kill_switch/kill_switch.test.js +++ b/test/contracts/kill-switch/kill_switch.js @@ -12,17 +12,18 @@ const IssuesRegistry = artifacts.require('IssuesRegistry') const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') -const FailingKillSwitchMock = artifacts.require('FailingKillSwitchMock') +const RevertingKillSwitchMock = artifacts.require('RevertingKillSwitchMock') const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') const KernelWithNonCompliantKillSwitchMock = artifacts.require('KernelWithNonCompliantKillSwitchMock') const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { let dao, acl, app, registryFactory let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory let kernelWithoutKillSwitchBase, kernelWithNonCompliantKillSwitchBase, failingKillSwitchBase - let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, SET_SEVERITY_ROLE, SET_DEFAULT_ISSUES_REGISTRY_ROLE, SET_ISSUES_REGISTRY_ROLE, SET_ALLOWED_INSTANCES_ROLE, SET_DENIED_BASE_IMPLS_ROLE, SET_HIGHEST_ALLOWED_SEVERITY_ROLE + let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, CHANGE_SEVERITY_ROLE, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, CHANGE_ISSUES_REGISTRY_ROLE, CHANGE_WHITELISTED_INSTANCES_ROLE, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, WRITER_ROLE before('deploy base implementations', async () => { // real @@ -34,21 +35,22 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { // mocks appBase = await KillSwitchedApp.new() - failingKillSwitchBase = await FailingKillSwitchMock.new() + failingKillSwitchBase = await RevertingKillSwitchMock.new() kernelWithoutKillSwitchBase = await KernelWithoutKillSwitchMock.new() kernelWithNonCompliantKillSwitchBase = await KernelWithNonCompliantKillSwitchMock.new() }) before('load constants and roles', async () => { + WRITER_ROLE = await appBase.WRITER_ROLE() CORE_NAMESPACE = await kernelBase.CORE_NAMESPACE() KERNEL_APP_ID = await kernelBase.KERNEL_APP_ID() APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() - SET_SEVERITY_ROLE = await issuesRegistryBase.SET_SEVERITY_ROLE() - SET_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_DEFAULT_ISSUES_REGISTRY_ROLE() - SET_ISSUES_REGISTRY_ROLE = await killSwitchBase.SET_ISSUES_REGISTRY_ROLE() - SET_ALLOWED_INSTANCES_ROLE = await killSwitchBase.SET_ALLOWED_INSTANCES_ROLE() - SET_DENIED_BASE_IMPLS_ROLE = await killSwitchBase.SET_DENIED_BASE_IMPLS_ROLE() - SET_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.SET_HIGHEST_ALLOWED_SEVERITY_ROLE() + CHANGE_SEVERITY_ROLE = await issuesRegistryBase.CHANGE_SEVERITY_ROLE() + CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE() + CHANGE_ISSUES_REGISTRY_ROLE = await killSwitchBase.CHANGE_ISSUES_REGISTRY_ROLE() + CHANGE_WHITELISTED_INSTANCES_ROLE = await killSwitchBase.CHANGE_WHITELISTED_INSTANCES_ROLE() + CHANGE_BLACKLISTED_BASE_IMPLS_ROLE = await killSwitchBase.CHANGE_BLACKLISTED_BASE_IMPLS_ROLE() + CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE() }) context('when the kernel version does not support kill-switch logic', async () => { @@ -62,9 +64,10 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) await app.initialize(owner) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) }) it('executes the call', async () => { @@ -74,8 +77,6 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when the kernel version does support kill-switch logic', async () => { - const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' - context('when the kernel was not initialized with a kill-switch', async () => { before('create DAO factory using a kernel that supports kill-switch logic', async () => { daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) @@ -87,9 +88,10 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { acl = ACL.at(await dao.acl()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - const appReceipt = await dao.newAppInstance('0x1236', appBase.address, '0x', false, { from: root }) + const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) await app.initialize(owner) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) }) describe('integration', () => { @@ -110,7 +112,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the kernel is initialized with a non-compliant kill-switch implementation', async () => { before('create DAO factory', async () => { - daoFactory = await DAOFactory.new(kernelWithNonCompliantKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) }) before('deploy DAO with a kill switch and create kill-switched sample app', async () => { @@ -122,6 +124,10 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getNewProxyAddress(receipt)) await app.initialize(owner) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + + // upgrade kernel to non-compliant implementation + await dao.setApp(CORE_NAMESPACE, KERNEL_APP_ID, kernelWithNonCompliantKillSwitchBase.address, { from: root }) }) describe('integration', () => { @@ -156,7 +162,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) }) beforeEach('deploy DAO with a kill switch', async () => { @@ -166,17 +172,18 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { killSwitch = KillSwitch.at(await dao.killSwitch()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_ALLOWED_INSTANCES_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_DENIED_BASE_IMPLS_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getNewProxyAddress(receipt)) await app.initialize(owner) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) }) describe('integration', () => { @@ -193,44 +200,45 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when the function being called is tagged', () => { const itAlwaysExecutesTheCall = () => { - context('when the instance being called is allowed', () => { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) + // Note that whitelisting a single instance has higher precedence than blacklisting a base implementation itExecutesTheCall() }) }) - context('when the instance being called is not marked as allowed', () => { - beforeEach('dot not allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('do not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() @@ -247,29 +255,29 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when there is no highest allowed severity set for the contract being called', () => { + context('when there is no highest whitelisted severity set for the contract being called', () => { itAlwaysExecutesTheCall() }) - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity below the one reported', async () => { + context('when there is a highest whitelisted severity set for the contract being called', () => { + context('when the highest whitelisted severity is under the reported bug severity', () => { + beforeEach('set highest whitelisted severity below the one reported', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) itAlwaysExecutesTheCall() }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity equal to the one reported', async () => { + context('when the highest whitelisted severity is equal to the reported bug severity', () => { + beforeEach('set highest whitelisted severity equal to the one reported', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) itAlwaysExecutesTheCall() }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity above the one reported', async () => { + context('when the highest whitelisted severity is greater than the reported bug severity', () => { + beforeEach('set highest whitelisted severity above the one reported', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) @@ -298,12 +306,12 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) specificIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(specificRegistryReceipt)) await specificIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, SET_SEVERITY_ROLE, root, { from: root }) + await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) }) beforeEach('deploy DAO with a kill switch', async () => { @@ -312,77 +320,78 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { acl = ACL.at(await dao.acl()) killSwitch = KillSwitch.at(await dao.killSwitch()) await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_ALLOWED_INSTANCES_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_DENIED_BASE_IMPLS_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, SET_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) beforeEach('create kill switched app', async () => { const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) app = KillSwitchedApp.at(getNewProxyAddress(receipt)) await app.initialize(owner) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) }) - describe('isInstanceAllowed', function () { - context('when there was no instance allowed value set yet', function () { + describe('isInstanceWhitelisted', function () { + context('when there was no instance whitelisted value set yet', function () { it('returns false', async () => { - assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) }) - context('when there was an allowed value already set', function () { - context('when it is allowed', function () { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + context('when there was an whitelisted value already set', function () { + context('when it is whitelisted', function () { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) }) it('returns true', async () => { - assert(await killSwitch.isInstanceAllowed(app.address)) + assert(await killSwitch.isInstanceWhitelisted(app.address)) }) }) - context('when it is not allowed', function () { - beforeEach('do not allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + context('when it is not whitelisted', function () { + beforeEach('do not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) it('returns false', async () => { - assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) }) }) }) - describe('setAllowedInstance', function () { + describe('setWhitelistedInstance', function () { context('when the sender is authorized', function () { const from = owner - context('when there was no instance allowed yet', function () { - it('sets a new allowed value', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from }) + context('when there was no instance whitelisted yet', function () { + it('sets a new whitelisted value', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from }) - assert(await killSwitch.isInstanceAllowed(app.address)) + assert(await killSwitch.isInstanceWhitelisted(app.address)) }) it('emits an event', async () => { - const receipt = await killSwitch.setAllowedInstance(app.address, true, { from }) + const receipt = await killSwitch.setWhitelistedInstance(app.address, true, { from }) - assertAmountOfEvents(receipt, 'AllowedInstanceSet') - assertEvent(receipt, 'AllowedInstanceSet', { allowed: true, instance: app.address }) + assertAmountOfEvents(receipt, 'ChangeWhitelistedInstance') + assertEvent(receipt, 'ChangeWhitelistedInstance', { whitelisted: true, instance: app.address }) }) }) - context('when there was a instance already allowed', function () { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from }) + context('when there was a instance already whitelisted', function () { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from }) }) - it('changes the allowed value', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from }) + it('changes the whitelisted value', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from }) - assert.isFalse(await killSwitch.isInstanceAllowed(app.address)) + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) }) }) @@ -391,69 +400,69 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const from = anyone it('reverts', async () => { - await assertRevert(killSwitch.setAllowedInstance(app.address, true, { from })) + await assertRevert(killSwitch.setWhitelistedInstance(app.address, true, { from })) }) }) }) - describe('isBaseImplementationDenied', function () { - context('when there was no denied value set yet', function () { + describe('isBaseImplementationBlacklisted', function () { + context('when there was no blacklisted value set yet', function () { it('returns false', async () => { - assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) }) - context('when there was a denied value already set', function () { - context('when it is denied', function () { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when there was a blacklisted value already set', function () { + context('when it is blacklisted', function () { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) it('returns true', async () => { - assert.isTrue(await killSwitch.isBaseImplementationDenied(appBase.address)) + assert.isTrue(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) }) - context('when it is not denied', function () { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when it is not blacklisted', function () { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) it('returns false', async () => { - assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) }) }) }) - describe('setDeniedBaseImplementation', function () { + describe('setBlacklistedBaseImplementation', function () { context('when the sender is authorized', function () { const from = owner - context('when there was no base implementation denied yet', function () { - it('sets a new denied value', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) + context('when there was no base implementation blacklisted yet', function () { + it('sets a new blacklisted value', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - assert(await killSwitch.isBaseImplementationDenied(appBase.address)) + assert(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) it('emits an event', async () => { - const receipt = await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) + const receipt = await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - assertAmountOfEvents(receipt, 'DeniedBaseImplementationSet') - assertEvent(receipt, 'DeniedBaseImplementationSet', { base: appBase.address, denied: true }) + assertAmountOfEvents(receipt, 'ChangeBlacklistedBaseImplementation') + assertEvent(receipt, 'ChangeBlacklistedBaseImplementation', { base: appBase.address, blacklisted: true }) }) }) - context('when there was a base implementation already denied', function () { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }) + context('when there was a base implementation already blacklisted', function () { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) }) - it('changes the denied value', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from }) + it('changes the blacklisted value', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from }) - assert.isFalse(await killSwitch.isBaseImplementationDenied(appBase.address)) + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) }) }) @@ -462,7 +471,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const from = anyone it('reverts', async () => { - await assertRevert(killSwitch.setDeniedBaseImplementation(appBase.address, true, { from }), 'APP_AUTH_FAILED') + await assertRevert(killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }), 'APP_AUTH_FAILED') }) }) }) @@ -506,8 +515,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { it('emits an event', async () => { const receipt = await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - assertAmountOfEvents(receipt, 'IssuesRegistrySet') - assertEvent(receipt, 'IssuesRegistrySet', { appId: SAMPLE_APP_ID, issuesRegistry: specificIssuesRegistry.address }) + assertAmountOfEvents(receipt, 'ChangeIssuesRegistry') + assertEvent(receipt, 'ChangeIssuesRegistry', { appId: SAMPLE_APP_ID, issuesRegistry: specificIssuesRegistry.address }) }) }) @@ -555,8 +564,8 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { it('emits an event', async () => { const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - assertAmountOfEvents(receipt, 'DefaultIssuesRegistrySet') - assertEvent(receipt, 'DefaultIssuesRegistrySet', { issuesRegistry: specificIssuesRegistry.address }) + assertAmountOfEvents(receipt, 'ChangeDefaultIssuesRegistry') + assertEvent(receipt, 'ChangeDefaultIssuesRegistry', { issuesRegistry: specificIssuesRegistry.address }) }) }) @@ -584,11 +593,11 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - describe('checkSeverityIssuesFor', function () { + describe('hasExceededAllowedSeverity', function () { context('when there is no bug registered', () => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns true', async () => { - assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) @@ -598,7 +607,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) }) @@ -610,7 +619,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns false', async () => { - assert.isFalse(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) @@ -621,7 +630,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns false', async () => { - assert.isFalse(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) @@ -631,7 +640,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) @@ -641,7 +650,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) it('returns true', async () => { - assert.isTrue(await killSwitch.checkSeverityIssuesFor(SAMPLE_APP_ID, appBase.address)) + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) }) @@ -662,13 +671,13 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { it('emits an event', async () => { const receipt = await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - assertAmountOfEvents(receipt, 'HighestAllowedSeveritySet') - assertEvent(receipt, 'HighestAllowedSeveritySet', { appId: SAMPLE_APP_ID, severity: SEVERITY.HIGH }) + assertAmountOfEvents(receipt, 'ChangeHighestAllowedSeverity') + assertEvent(receipt, 'ChangeHighestAllowedSeverity', { appId: SAMPLE_APP_ID, severity: SEVERITY.HIGH }) }) }) context('when there was a previous severity set', function () { - beforeEach('set highest allowed severity', async () => { + beforeEach('set highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from }) assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.LOW) }) @@ -693,51 +702,51 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { describe('integration', () => { context('when the function being called is not tagged', () => { - const itExecutesTheCallEvenIfDenied = () => { + const itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted = () => { const itExecutesTheCall = () => { it('executes the call', async () => { assert.equal(await app.read(), 42) }) } - context('when the instance being called is allowed', () => { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() }) }) - context('when the instance being called is not marked as allowed', () => { - beforeEach('dot not allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() @@ -746,7 +755,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { } context('when there is no bug registered', () => { - itExecutesTheCallEvenIfDenied() + itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() }) context('when there is a bug registered', () => { @@ -754,7 +763,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - itExecutesTheCallEvenIfDenied() + itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() }) }) @@ -768,49 +777,49 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { const itDoesNotExecuteTheCall = () => { it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_CONTRACT_CALL_NOT_ALLOWED') + await assertRevert(app.write(10, { from: owner }), 'APP_AUTH_FAILED') }) } - const itExecutesTheCallOnlyWhenAllowed = () => { - context('when the instance being called is allowed', () => { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + const itExecutesTheCallOnlyWhenWhitelisted = () => { + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() }) }) - context('when the instance being called is not marked as allowed', () => { - beforeEach('dot not allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itDoesNotExecuteTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itDoesNotExecuteTheCall() @@ -818,45 +827,45 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) } - const itExecutesTheCallUnlessDisallowedAndDenied = () => { - context('when the instance being called is allowed', () => { - beforeEach('allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, true, { from: owner }) + const itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted = () => { + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itExecutesTheCall() }) }) - context('when the instance being called is not marked as allowed', () => { - beforeEach('dot not allow instance', async () => { - await killSwitch.setAllowedInstance(app.address, false, { from: owner }) + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) - context('when the base implementation is not denied', () => { - beforeEach('do not deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, false, { from: owner }) + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) itExecutesTheCall() }) - context('when the base implementation is denied', () => { - beforeEach('deny base implementation', async () => { - await killSwitch.setDeniedBaseImplementation(appBase.address, true, { from: owner }) + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) }) itDoesNotExecuteTheCall() @@ -865,7 +874,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { } context('when there is no bug registered', () => { - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) context('when there is a bug registered', () => { @@ -873,9 +882,9 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) }) - context('when the bug was not fixed yet', () => { + context('when the bug was real', () => { context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallOnlyWhenAllowed() + itExecutesTheCallOnlyWhenWhitelisted() }) context('when there is a highest allowed severity set for the contract being called', () => { @@ -884,7 +893,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCallOnlyWhenAllowed() + itExecutesTheCallOnlyWhenWhitelisted() }) context('when the highest allowed severity is equal to the reported bug severity', () => { @@ -892,7 +901,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) context('when the highest allowed severity is greater than the reported bug severity', () => { @@ -900,18 +909,18 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) }) }) - context('when the bug was already fixed', () => { - beforeEach('fix bug', async () => { + context('when the bug was a false positive', () => { + beforeEach('roll back reported bug', async () => { await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) }) context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) context('when there is a highest allowed severity set for the contract being called', () => { @@ -920,7 +929,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) context('when the highest allowed severity is equal to the reported bug severity', () => { @@ -928,7 +937,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) context('when the highest allowed severity is greater than the reported bug severity', () => { @@ -936,7 +945,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - itExecutesTheCallUnlessDisallowedAndDenied() + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) }) }) @@ -945,7 +954,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) describe('gas costs', () => { - beforeEach('set an allowed severity issue', async () => { + beforeEach('set a highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) }) From 58176617f5db167cb9a03787f120d56a60a1a603 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 21 May 2019 16:36:02 -0300 Subject: [PATCH 35/37] kill-switch: small tweaks and optimizations --- contracts/apps/AragonApp.sol | 13 +++++-------- contracts/factory/DAOFactory.sol | 4 ++-- contracts/kernel/Kernel.sol | 3 +-- contracts/kill-switch/KillSwitch.sol | 16 ++++++++-------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index 6de106bd6..1755edaa2 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -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_UNEXPECTED_KERNEL_RESPONSE = "APP_UNEXPECTED_KERNEL_RESPONSE"; modifier auth(bytes32 _role) { require(canPerform(msg.sender, _role, new uint256[](0)), ERROR_AUTH_FAILED); @@ -41,12 +40,8 @@ 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 (!isCallEnabled()) { - return false; - } - IKernel linkedKernel = kernel(); - if (address(linkedKernel) == address(0)) { + if (address(linkedKernel) == address(0) || !isCallEnabled()) { return false; } @@ -81,10 +76,12 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua return true; } - // if not, first ensure the returned value is 32 bytes length + // if not, check returned value is 32 bytes length, otherwise return false uint256 _outputLength; assembly { _outputLength := returndatasize } - require(_outputLength == 32, ERROR_UNEXPECTED_KERNEL_RESPONSE); + if (_outputLength != 32) { + return false; + } // forward returned value bool _shouldDenyCall; diff --git a/contracts/factory/DAOFactory.sol b/contracts/factory/DAOFactory.sol index 6511412fd..161869714 100644 --- a/contracts/factory/DAOFactory.sol +++ b/contracts/factory/DAOFactory.sol @@ -100,7 +100,7 @@ contract DAOFactory { // grant app manager permissions to this and deploy kill switch acl.createPermission(address(this), dao, appManagerRole, address(this)); - _createKillSwitch(dao, acl, _issuesRegistry, appManagerRole); + _createKillSwitch(dao, acl, _issuesRegistry); // deploy EVM scripts registry if required if (address(regFactory) != address(0)) { @@ -135,7 +135,7 @@ contract DAOFactory { _acl.revokePermission(regFactory, _acl, _createPermissionsRole); } - function _createKillSwitch(Kernel _dao, ACL _acl, IssuesRegistry _issuesRegistry, bytes32 _appManagerRole) internal { + function _createKillSwitch(Kernel _dao, ACL _acl, IssuesRegistry _issuesRegistry) 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)); diff --git a/contracts/kernel/Kernel.sol b/contracts/kernel/Kernel.sol index 9b8900674..bcbd47436 100644 --- a/contracts/kernel/Kernel.sol +++ b/contracts/kernel/Kernel.sol @@ -165,8 +165,7 @@ 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. - * Note that we don't need to perform an initialization check since having or not a kill-switch installed - * implicitly means that. + * Initialization check is implicitly provided by the KillSwitch's existence, as apps can only be installed after initialization. * @param _appId Identifier for app to be checked * @return True if the given call should be denied, false otherwise */ diff --git a/contracts/kill-switch/KillSwitch.sol b/contracts/kill-switch/KillSwitch.sol index a85c14000..6e6a3b21a 100644 --- a/contracts/kill-switch/KillSwitch.sol +++ b/contracts/kill-switch/KillSwitch.sol @@ -91,6 +91,11 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { * and we have several tests to make sure its usage is working as expected. */ function shouldDenyCallingApp(bytes32 _appId, address _base, address _instance) external view returns (bool) { + // if the instance is the kill switch itself, then allow given call + if (_instance == address(this)) { + return false; + } + // if the instance is whitelisted, then allow given call if (isInstanceWhitelisted(_instance)) { return false; @@ -102,13 +107,8 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { } // Check if there is a severity issue reported in the corresponding issue registry. If there is actually a - // severity issue, check against the kill switch settings if we allow such severity level or not. - if (hasExceededAllowedSeverity(_appId, _base)) { - return false; - } - - // if none of the conditions above were met, then deny given call - return true; + // severity issue, check if it has exceeded the highest allowed severity level or not. + return hasExceededAllowedSeverity(_appId, _base); } function isInstanceWhitelisted(address _instance) public view returns (bool) { @@ -122,7 +122,7 @@ contract KillSwitch is IKillSwitch, IsContract, AragonApp { function hasExceededAllowedSeverity(bytes32 _appId, address _base) public view returns (bool) { IIssuesRegistry.Severity severityFound = getIssuesRegistry(_appId).getSeverityFor(_base); IIssuesRegistry.Severity highestAllowedSeverity = getHighestAllowedSeverity(_appId); - return highestAllowedSeverity >= severityFound; + return highestAllowedSeverity < severityFound; } function getIssuesRegistry(bytes32 _appId) public view returns (IIssuesRegistry) { From 3113233b92cc3cb7b266955922ee2cdd9970d578 Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Tue, 21 May 2019 16:36:06 -0300 Subject: [PATCH 36/37] kill-switch: split unit and integration tests --- test/contracts/kill-switch/kill_switch.js | 1082 +++++------------ .../kill-switch/kill_switch_kernel.js | 576 +++++++++ 2 files changed, 911 insertions(+), 747 deletions(-) create mode 100644 test/contracts/kill-switch/kill_switch_kernel.js diff --git a/test/contracts/kill-switch/kill_switch.js b/test/contracts/kill-switch/kill_switch.js index 48899937c..ffc5dc8e6 100644 --- a/test/contracts/kill-switch/kill_switch.js +++ b/test/contracts/kill-switch/kill_switch.js @@ -1,49 +1,40 @@ const { SEVERITY } = require('./enums') const { skipCoverage } = require('../../helpers/coverage') const { assertRevert } = require('../../helpers/assertThrow') +const { getNewProxyAddress } = require('../../helpers/events') const { assertEvent, assertAmountOfEvents } = require('../../helpers/assertEvent')(web3) -const { getNewProxyAddress, getEventArgument } = require('../../helpers/events') const ACL = artifacts.require('ACL') const Kernel = artifacts.require('Kernel') -const DAOFactory = artifacts.require('DAOFactory') const KillSwitch = artifacts.require('KillSwitch') const IssuesRegistry = artifacts.require('IssuesRegistry') const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') -const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') - -const RevertingKillSwitchMock = artifacts.require('RevertingKillSwitchMock') -const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') -const KernelWithNonCompliantKillSwitchMock = artifacts.require('KernelWithNonCompliantKillSwitchMock') const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { - let dao, acl, app, registryFactory - let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory - let kernelWithoutKillSwitchBase, kernelWithNonCompliantKillSwitchBase, failingKillSwitchBase - let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, CHANGE_SEVERITY_ROLE, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, CHANGE_ISSUES_REGISTRY_ROLE, CHANGE_WHITELISTED_INSTANCES_ROLE, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, WRITER_ROLE + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase + let dao, acl, app, killSwitch, defaultIssuesRegistry, specificIssuesRegistry + let CORE_NAMESPACE, KERNEL_APP_ID, KILL_SWITCH_APP_ID + let APP_MANAGER_ROLE, CHANGE_SEVERITY_ROLE, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, CHANGE_ISSUES_REGISTRY_ROLE, CHANGE_WHITELISTED_INSTANCES_ROLE, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, WRITER_ROLE before('deploy base implementations', async () => { - // real kernelBase = await Kernel.new(true) // petrify immediately aclBase = await ACL.new() - registryFactory = await EVMScriptRegistryFactory.new() killSwitchBase = await KillSwitch.new() issuesRegistryBase = await IssuesRegistry.new() - - // mocks appBase = await KillSwitchedApp.new() - failingKillSwitchBase = await RevertingKillSwitchMock.new() - kernelWithoutKillSwitchBase = await KernelWithoutKillSwitchMock.new() - kernelWithNonCompliantKillSwitchBase = await KernelWithNonCompliantKillSwitchMock.new() }) - before('load constants and roles', async () => { - WRITER_ROLE = await appBase.WRITER_ROLE() + before('load constants', async () => { CORE_NAMESPACE = await kernelBase.CORE_NAMESPACE() KERNEL_APP_ID = await kernelBase.KERNEL_APP_ID() + KILL_SWITCH_APP_ID = await kernelBase.DEFAULT_KILL_SWITCH_APP_ID() + }) + + before('load roles', async () => { + WRITER_ROLE = await appBase.WRITER_ROLE() APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() CHANGE_SEVERITY_ROLE = await issuesRegistryBase.CHANGE_SEVERITY_ROLE() CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE() @@ -53,570 +44,377 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE() }) - context('when the kernel version does not support kill-switch logic', async () => { - before('create DAO factory', async () => { - daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, ZERO_ADDRESS, registryFactory.address) - }) + beforeEach('create issues registries', async () => { + const issuesRegistryDAO = await Kernel.new(false) + await issuesRegistryDAO.initialize(aclBase.address, root) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - beforeEach('deploy DAO without a kill switch and create kill-switched sample app', async () => { - const daoFactoryReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + const initializeData = issuesRegistryBase.contract.initialize.getData() - const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) - await app.initialize(owner) - await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) - }) + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, initializeData, false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) + const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, initializeData, false, { from: root }) + specificIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(specificRegistryReceipt)) + await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with a kill switch', async () => { + dao = await Kernel.new(false) + await dao.initialize(aclBase.address, root) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = killSwitchBase.contract.initialize.getData(defaultIssuesRegistry.address) + const receipt = await dao.newAppInstance(KILL_SWITCH_APP_ID, killSwitchBase.address, initializeData, true, { from: root }) + killSwitch = KillSwitch.at(getNewProxyAddress(receipt)) + + await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) }) - context('when the kernel version does support kill-switch logic', async () => { - context('when the kernel was not initialized with a kill-switch', async () => { - before('create DAO factory using a kernel that supports kill-switch logic', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + beforeEach('create kill switched app', async () => { + const initializeData = appBase.contract.initialize.getData(owner) + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(receipt)) + + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + }) + + describe('isInstanceWhitelisted', function () { + context('when there was no instance whitelisted value set yet', function () { + it('returns false', async () => { + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) + }) - before('deploy DAO without a kill switch and create kill-switched sample app', async () => { - const daoFactoryReceipt = await daoFactory.newDAO(root) - dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + context('when there was an whitelisted value already set', function () { + context('when it is whitelisted', function () { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) + }) - const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) - await app.initialize(owner) - await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + it('returns true', async () => { + assert(await killSwitch.isInstanceWhitelisted(app.address)) + }) }) - describe('integration', () => { - context('when the function being called is not tagged', () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) + context('when it is not whitelisted', function () { + beforeEach('do not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) }) - context('when the function being called is tagged', () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) + it('returns false', async () => { + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) }) }) + }) - context('when the kernel is initialized with a non-compliant kill-switch implementation', async () => { - before('create DAO factory', async () => { - daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) - }) + describe('setWhitelistedInstance', function () { + context('when the sender is authorized', function () { + const from = owner - before('deploy DAO with a kill switch and create kill-switched sample app', async () => { - const daoFactoryReceipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistryBase.address) - dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + context('when there was no instance whitelisted yet', function () { + it('sets a new whitelisted value', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from }) - const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getNewProxyAddress(receipt)) - await app.initialize(owner) - await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + assert(await killSwitch.isInstanceWhitelisted(app.address)) + }) + + it('emits an event', async () => { + const receipt = await killSwitch.setWhitelistedInstance(app.address, true, { from }) - // upgrade kernel to non-compliant implementation - await dao.setApp(CORE_NAMESPACE, KERNEL_APP_ID, kernelWithNonCompliantKillSwitchBase.address, { from: root }) + assertAmountOfEvents(receipt, 'ChangeWhitelistedInstance') + assertEvent(receipt, 'ChangeWhitelistedInstance', { whitelisted: true, instance: app.address }) + }) }) - describe('integration', () => { - context('when the function being called is not tagged', () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) + context('when there was a instance already whitelisted', function () { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from }) }) - context('when the function being called is tagged', () => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_UNEXPECTED_KERNEL_RESPONSE') - }) + it('changes the whitelisted value', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from }) + + assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) }) }) }) - context('when the kernel is initialized with a failing kill-switch implementation', async () => { - let killSwitch, defaultIssuesRegistry + context('when the sender is not authorized', function () { + const from = anyone - before('create DAO factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, failingKillSwitchBase.address, registryFactory.address) + it('reverts', async () => { + await assertRevert(killSwitch.setWhitelistedInstance(app.address, true, { from })) }) + }) + }) - before('create issues registry', async () => { - const daoReceipt = await daoFactory.newDAO(root) - const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) - const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) - - await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - - const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) - await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) + describe('isBaseImplementationBlacklisted', function () { + context('when there was no blacklisted value set yet', function () { + it('returns false', async () => { + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) + }) - beforeEach('deploy DAO with a kill switch', async () => { - const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - killSwitch = KillSwitch.at(await dao.killSwitch()) - - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) - }) + context('when there was a blacklisted value already set', function () { + context('when it is blacklisted', function () { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getNewProxyAddress(receipt)) - await app.initialize(owner) - await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + it('returns true', async () => { + assert.isTrue(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) + }) }) - describe('integration', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - } - - context('when the function being called is not tagged', () => { - itExecutesTheCall() + context('when it is not blacklisted', function () { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) }) - context('when the function being called is tagged', () => { - const itAlwaysExecutesTheCall = () => { - context('when the instance being called is whitelisted', () => { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - // Note that whitelisting a single instance has higher precedence than blacklisting a base implementation - itExecutesTheCall() - }) - }) - - context('when the instance being called is not marked as whitelisted', () => { - beforeEach('do not whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - } - - context('when there is no bug registered', () => { - itAlwaysExecutesTheCall() - }) + it('returns false', async () => { + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) + }) + }) + }) + }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + describe('setBlacklistedBaseImplementation', function () { + context('when the sender is authorized', function () { + const from = owner - context('when there is no highest whitelisted severity set for the contract being called', () => { - itAlwaysExecutesTheCall() - }) + context('when there was no base implementation blacklisted yet', function () { + it('sets a new blacklisted value', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - context('when there is a highest whitelisted severity set for the contract being called', () => { - context('when the highest whitelisted severity is under the reported bug severity', () => { - beforeEach('set highest whitelisted severity below the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) + assert(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) + }) - itAlwaysExecutesTheCall() - }) + it('emits an event', async () => { + const receipt = await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - context('when the highest whitelisted severity is equal to the reported bug severity', () => { - beforeEach('set highest whitelisted severity equal to the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) - }) + assertAmountOfEvents(receipt, 'ChangeBlacklistedBaseImplementation') + assertEvent(receipt, 'ChangeBlacklistedBaseImplementation', { base: appBase.address, blacklisted: true }) + }) + }) - itAlwaysExecutesTheCall() - }) + context('when there was a base implementation already blacklisted', function () { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) + }) - context('when the highest whitelisted severity is greater than the reported bug severity', () => { - beforeEach('set highest whitelisted severity above the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) - }) + it('changes the blacklisted value', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from }) - itAlwaysExecutesTheCall() - }) - }) - }) + assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) }) }) }) - context('when the kernel is initialized with a safe kill-switch implementation', async () => { - let killSwitch, defaultIssuesRegistry, specificIssuesRegistry + context('when the sender is not authorized', function () { + const from = anyone - before('create DAO factory', async () => { - daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + it('reverts', async () => { + await assertRevert(killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }), 'APP_AUTH_FAILED') }) + }) + }) - beforeEach('create issues registries', async () => { - const daoReceipt = await daoFactory.newDAO(root) - const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) - const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) - - await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) - - const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) - await defaultIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) - - const specificRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, '0x', false, { from: root }) - specificIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(specificRegistryReceipt)) - await specificIssuesRegistry.initialize() - await issuesRegistryACL.createPermission(securityPartner, specificIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) + describe('getIssuesRegistry', function () { + context('when there was no specific issues registry set', () => { + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) }) + }) - beforeEach('deploy DAO with a kill switch', async () => { - const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) - dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) - acl = ACL.at(await dao.acl()) - killSwitch = KillSwitch.at(await dao.killSwitch()) - await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) - await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + context('when there is a specific issues registry set', () => { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from: owner }) }) - beforeEach('create kill switched app', async () => { - const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, '0x', false, { from: root }) - app = KillSwitchedApp.at(getNewProxyAddress(receipt)) - await app.initialize(owner) - await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + it('returns the default registry', async () => { + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) }) + }) + }) - describe('isInstanceWhitelisted', function () { - context('when there was no instance whitelisted value set yet', function () { - it('returns false', async () => { - assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) - }) - }) - - context('when there was an whitelisted value already set', function () { - context('when it is whitelisted', function () { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) - }) - - it('returns true', async () => { - assert(await killSwitch.isInstanceWhitelisted(app.address)) - }) - }) + describe('setIssuesRegistry', function () { + context('when the sender is authorized', function () { + const from = owner - context('when it is not whitelisted', function () { - beforeEach('do not whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) - }) - - it('returns false', async () => { - assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) - }) - }) + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, ZERO_ADDRESS, { from })) }) }) - describe('setWhitelistedInstance', function () { - context('when the sender is authorized', function () { - const from = owner - - context('when there was no instance whitelisted yet', function () { - it('sets a new whitelisted value', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from }) + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - assert(await killSwitch.isInstanceWhitelisted(app.address)) - }) - - it('emits an event', async () => { - const receipt = await killSwitch.setWhitelistedInstance(app.address, true, { from }) - - assertAmountOfEvents(receipt, 'ChangeWhitelistedInstance') - assertEvent(receipt, 'ChangeWhitelistedInstance', { whitelisted: true, instance: app.address }) - }) - }) - - context('when there was a instance already whitelisted', function () { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from }) - }) - - it('changes the whitelisted value', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from }) - - assert.isFalse(await killSwitch.isInstanceWhitelisted(app.address)) - }) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) }) - }) - - context('when the sender is not authorized', function () { - const from = anyone - it('reverts', async () => { - await assertRevert(killSwitch.setWhitelistedInstance(app.address, true, { from })) - }) - }) - }) + it('emits an event', async () => { + const receipt = await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - describe('isBaseImplementationBlacklisted', function () { - context('when there was no blacklisted value set yet', function () { - it('returns false', async () => { - assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) + assertAmountOfEvents(receipt, 'ChangeIssuesRegistry') + assertEvent(receipt, 'ChangeIssuesRegistry', { appId: SAMPLE_APP_ID, issuesRegistry: specificIssuesRegistry.address }) }) }) - context('when there was a blacklisted value already set', function () { - context('when it is blacklisted', function () { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - it('returns true', async () => { - assert.isTrue(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) - }) + context('when there was a specific issues registry set', function () { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) }) - context('when it is not blacklisted', function () { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) + it('changes the issues registry', async () => { + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, defaultIssuesRegistry.address, { from }) - it('returns false', async () => { - assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) - }) + assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) }) }) }) + }) - describe('setBlacklistedBaseImplementation', function () { - context('when the sender is authorized', function () { - const from = owner - - context('when there was no base implementation blacklisted yet', function () { - it('sets a new blacklisted value', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - - assert(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) - }) - - it('emits an event', async () => { - const receipt = await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - - assertAmountOfEvents(receipt, 'ChangeBlacklistedBaseImplementation') - assertEvent(receipt, 'ChangeBlacklistedBaseImplementation', { base: appBase.address, blacklisted: true }) - }) - }) + context('when the sender is not authorized', function () { + const from = anyone - context('when there was a base implementation already blacklisted', function () { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }) - }) + it('reverts', async () => { + await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from })) + }) + }) + }) - it('changes the blacklisted value', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from }) + describe('setDefaultIssuesRegistry', function () { + context('when the sender is authorized', function () { + const from = owner - assert.isFalse(await killSwitch.isBaseImplementationBlacklisted(appBase.address)) - }) - }) + context('when the given address is not a contract', () => { + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(ZERO_ADDRESS, { from })) }) + }) - context('when the sender is not authorized', function () { - const from = anyone + context('when the given address is a contract', () => { + context('when there was no specific issues registry set yet', function () { + it('sets the given implementation', async () => { + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - it('reverts', async () => { - await assertRevert(killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from }), 'APP_AUTH_FAILED') + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) }) - }) - }) - describe('getIssuesRegistry', function () { - context('when there was no specific issues registry set', () => { - it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) + it('emits an event', async () => { + const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + + assertAmountOfEvents(receipt, 'ChangeDefaultIssuesRegistry') + assertEvent(receipt, 'ChangeDefaultIssuesRegistry', { issuesRegistry: specificIssuesRegistry.address }) }) }) - context('when there is a specific issues registry set', () => { + context('when there was a specific issues registry set', function () { beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from: owner }) + await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) + assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) }) - it('returns the default registry', async () => { - assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) + it('changes the issues registry', async () => { + await killSwitch.setDefaultIssuesRegistry(defaultIssuesRegistry.address, { from }) + + assert.equal(await killSwitch.defaultIssuesRegistry(), defaultIssuesRegistry.address) }) }) }) + }) - describe('setIssuesRegistry', function () { - context('when the sender is authorized', function () { - const from = owner - - context('when the given address is not a contract', () => { - it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, ZERO_ADDRESS, { from })) - }) - }) - - context('when the given address is a contract', () => { - context('when there was no specific issues registry set yet', function () { - it('sets the given implementation', async () => { - await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - - assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), specificIssuesRegistry.address) - }) - - it('emits an event', async () => { - const receipt = await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - - assertAmountOfEvents(receipt, 'ChangeIssuesRegistry') - assertEvent(receipt, 'ChangeIssuesRegistry', { appId: SAMPLE_APP_ID, issuesRegistry: specificIssuesRegistry.address }) - }) - }) - - context('when there was a specific issues registry set', function () { - beforeEach('set specific issues registry', async () => { - await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from }) - }) + context('when the sender is not authorized', function () { + const from = anyone - it('changes the issues registry', async () => { - await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, defaultIssuesRegistry.address, { from }) + it('reverts', async () => { + await assertRevert(killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from })) + }) + }) + }) - assert.equal(await killSwitch.getIssuesRegistry(SAMPLE_APP_ID), defaultIssuesRegistry.address) - }) - }) - }) + describe('hasExceededAllowedSeverity', function () { + context('when there is no bug registered', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) + }) - context('when the sender is not authorized', function () { - const from = anyone + context('when there is a highest allowed severity set for the contract being called', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) - it('reverts', async () => { - await assertRevert(killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from })) - }) + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) + }) - describe('setDefaultIssuesRegistry', function () { - context('when the sender is authorized', function () { - const from = owner + context('when there is a bug registered in the default issues registry', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) - context('when the given address is not a contract', () => { - it('reverts', async () => { - await assertRevert(killSwitch.setDefaultIssuesRegistry(ZERO_ADDRESS, { from })) - }) + context('when there is no specific issues registry set', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + it('returns true', async () => { + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) + }) - context('when the given address is a contract', () => { - context('when there was no specific issues registry set yet', function () { - it('sets the given implementation', async () => { - await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - - assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) - }) - - it('emits an event', async () => { - const receipt = await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - - assertAmountOfEvents(receipt, 'ChangeDefaultIssuesRegistry') - assertEvent(receipt, 'ChangeDefaultIssuesRegistry', { issuesRegistry: specificIssuesRegistry.address }) - }) + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - context('when there was a specific issues registry set', function () { - beforeEach('set specific issues registry', async () => { - await killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from }) - assert.equal(await killSwitch.defaultIssuesRegistry(), specificIssuesRegistry.address) - }) - - it('changes the issues registry', async () => { - await killSwitch.setDefaultIssuesRegistry(defaultIssuesRegistry.address, { from }) - - assert.equal(await killSwitch.defaultIssuesRegistry(), defaultIssuesRegistry.address) - }) + it('returns true', async () => { + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - }) - - context('when the sender is not authorized', function () { - const from = anyone - it('reverts', async () => { - await assertRevert(killSwitch.setDefaultIssuesRegistry(specificIssuesRegistry.address, { from })) - }) - }) - }) + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + }) - describe('hasExceededAllowedSeverity', function () { - context('when there is no bug registered', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - it('returns true', async () => { - assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is greater than the reported bug severity', () => { beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - it('returns true', async () => { - assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) }) + }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) + context('when there is a specific issues registry set', () => { + beforeEach('set specific issues registry', async () => { + await killSwitch.setIssuesRegistry(SAMPLE_APP_ID, specificIssuesRegistry.address, { from: owner }) + }) + context('when there is no bug registered in the specific issues registry', () => { context('when there is no highest allowed severity set for the contract being called', () => { it('returns false', async () => { assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) @@ -624,7 +422,7 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { + context('when the highest allowed severity is under the reported bug severity of the default registry', () => { beforeEach('set highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) @@ -634,339 +432,129 @@ contract('KillSwitch', ([_, root, owner, securityPartner, anyone]) => { }) }) - context('when the highest allowed severity is equal to the reported bug severity', () => { + context('when the highest allowed severity is equal to the reported bug severity of the default registry', () => { beforeEach('set highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - it('returns true', async () => { - assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - context('when the highest allowed severity is greater than the reported bug severity', () => { + context('when the highest allowed severity is greater than the reported bug severity of the default registry', () => { beforeEach('set highest allowed severity', async () => { await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) }) - it('returns true', async () => { - assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) }) }) - }) - - describe('setHighestAllowedSeverity', function () { - context('when the sender is authorized', function () { - const from = owner - - context('when there was no severity set', function () { - it('sets the highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - - assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.HIGH) - }) - - it('emits an event', async () => { - const receipt = await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - - assertAmountOfEvents(receipt, 'ChangeHighestAllowedSeverity') - assertEvent(receipt, 'ChangeHighestAllowedSeverity', { appId: SAMPLE_APP_ID, severity: SEVERITY.HIGH }) - }) - }) - - context('when there was a previous severity set', function () { - beforeEach('set highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from }) - assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.LOW) - }) - - it('changes the highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from }) - - assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.MID) - }) - }) - }) - - context('when the sender is not authorized', function () { - const from = anyone - - it('reverts', async () => { - await assertRevert(killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from })) - }) - }) - }) - - describe('integration', () => { - context('when the function being called is not tagged', () => { - - const itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted = () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - assert.equal(await app.read(), 42) - }) - } - - context('when the instance being called is whitelisted', () => { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - - context('when the instance being called is not marked as whitelisted', () => { - beforeEach('dot not whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() - }) - }) - } - context('when there is no bug registered', () => { - itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() + context('when there is a bug registered in the specific issues registry higher than the one reported in the default registry', () => { + beforeEach('register a bug', async () => { + await specificIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.HIGH, { from: securityPartner }) }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + context('when there is no highest allowed severity set for the contract being called', () => { + it('returns true', async () => { + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) - - itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() }) - }) - - context('when the function being called is tagged', () => { - const itExecutesTheCall = () => { - it('executes the call', async () => { - await app.write(10, { from: owner }) - assert.equal(await app.read(), 10) - }) - } - - const itDoesNotExecuteTheCall = () => { - it('does not execute the call', async () => { - await assertRevert(app.write(10, { from: owner }), 'APP_AUTH_FAILED') - }) - } - - const itExecutesTheCallOnlyWhenWhitelisted = () => { - context('when the instance being called is whitelisted', () => { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() - }) - - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - itExecutesTheCall() - }) - }) - - context('when the instance being called is not marked as whitelisted', () => { - beforeEach('dot not whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itDoesNotExecuteTheCall() + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity of the default registry', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) }) - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itDoesNotExecuteTheCall() + it('returns true', async () => { + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - } - - const itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted = () => { - context('when the instance being called is whitelisted', () => { - beforeEach('whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - itExecutesTheCall() + context('when the highest allowed severity is equal to the reported bug severity of the default registry but lower than the reported bug of the specific registry', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) }) - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itExecutesTheCall() + it('returns true', async () => { + assert.isTrue(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - context('when the instance being called is not marked as whitelisted', () => { - beforeEach('dot not whitelist instance', async () => { - await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) - }) - - context('when the base implementation is not blacklisted', () => { - beforeEach('do not blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) - }) - - itExecutesTheCall() + context('when the highest allowed severity is equal to the reported bug severity of the specific registry', () => { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from: owner }) }) - context('when the base implementation is blacklisted', () => { - beforeEach('blacklist base implementation', async () => { - await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) - }) - - itDoesNotExecuteTheCall() + it('returns false', async () => { + assert.isFalse(await killSwitch.hasExceededAllowedSeverity(SAMPLE_APP_ID, appBase.address)) }) }) - } - - context('when there is no bug registered', () => { - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() }) + }) + }) + }) + }) - context('when there is a bug registered', () => { - beforeEach('register a bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) - }) - - context('when the bug was real', () => { - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallOnlyWhenWhitelisted() - }) - - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity below the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) - - itExecutesTheCallOnlyWhenWhitelisted() - }) - - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity equal to the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) - }) - - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) - - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity above the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) - }) - - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) - }) - }) - - context('when the bug was a false positive', () => { - beforeEach('roll back reported bug', async () => { - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) - }) + describe('setHighestAllowedSeverity', function () { + context('when the sender is authorized', function () { + const from = owner - context('when there is no highest allowed severity set for the contract being called', () => { - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) + context('when there was no severity set', function () { + it('sets the highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - context('when there is a highest allowed severity set for the contract being called', () => { - context('when the highest allowed severity is under the reported bug severity', () => { - beforeEach('set highest allowed severity below the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) - }) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.HIGH) + }) - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) + it('emits an event', async () => { + const receipt = await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.HIGH, { from }) - context('when the highest allowed severity is equal to the reported bug severity', () => { - beforeEach('set highest allowed severity equal to the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) - }) + assertAmountOfEvents(receipt, 'ChangeHighestAllowedSeverity') + assertEvent(receipt, 'ChangeHighestAllowedSeverity', { appId: SAMPLE_APP_ID, severity: SEVERITY.HIGH }) + }) + }) - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) + context('when there was a previous severity set', function () { + beforeEach('set highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from }) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.LOW) + }) - context('when the highest allowed severity is greater than the reported bug severity', () => { - beforeEach('set highest allowed severity above the one reported', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) - }) + it('changes the highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from }) - itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() - }) - }) - }) - }) + assert.equal(await killSwitch.getHighestAllowedSeverity(SAMPLE_APP_ID), SEVERITY.MID) }) }) + }) - describe('gas costs', () => { - beforeEach('set a highest allowed severity', async () => { - await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) - await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) - }) - - it('kill switch should overload ~27k of gas to a function', skipCoverage(async () => { - const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) - const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) + context('when the sender is not authorized', function () { + const from = anyone - const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch - assert(killSwitchCost <= 27000, 'kill switch should overload ~27k of gas') - })) + it('reverts', async () => { + await assertRevert(killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from })) }) }) }) + + describe('gas costs', () => { + beforeEach('set a highest allowed severity', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.LOW, { from: securityPartner }) + }) + + it('kill switch should overload ~27k of gas to a function', skipCoverage(async () => { + const { receipt: { cumulativeGasUsed: gasUsedWithKillSwitch } } = await app.write(10, { from: owner }) + const { receipt: { cumulativeGasUsed: gasUsedWithoutKillSwitch } } = await app.writeWithoutKillSwitch(10, { from: owner }) + + const killSwitchCost = gasUsedWithKillSwitch - gasUsedWithoutKillSwitch + assert.isAtMost(killSwitchCost, 27000, 'kill switch should have maximum overhead of ~27k of gas') + })) + }) }) diff --git a/test/contracts/kill-switch/kill_switch_kernel.js b/test/contracts/kill-switch/kill_switch_kernel.js new file mode 100644 index 000000000..6445288a1 --- /dev/null +++ b/test/contracts/kill-switch/kill_switch_kernel.js @@ -0,0 +1,576 @@ +const { SEVERITY } = require('./enums') +const { assertRevert } = require('../../helpers/assertThrow') +const { getNewProxyAddress, getEventArgument } = require('../../helpers/events') + +const ACL = artifacts.require('ACL') +const Kernel = artifacts.require('Kernel') +const DAOFactory = artifacts.require('DAOFactory') +const KillSwitch = artifacts.require('KillSwitch') +const IssuesRegistry = artifacts.require('IssuesRegistry') +const KillSwitchedApp = artifacts.require('KillSwitchedAppMock') +const EVMScriptRegistryFactory = artifacts.require('EVMScriptRegistryFactory') + +const RevertingKillSwitchMock = artifacts.require('RevertingKillSwitchMock') +const KernelWithoutKillSwitchMock = artifacts.require('KernelWithoutKillSwitchMock') +const KernelWithNonCompliantKillSwitchMock = artifacts.require('KernelWithNonCompliantKillSwitchMock') + +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' +const SAMPLE_APP_ID = '0x1236000000000000000000000000000000000000000000000000000000000000' + +contract('KillSwitch Kernel', ([_, root, owner, securityPartner]) => { + let dao, acl, app, registryFactory + let kernelBase, aclBase, appBase, killSwitchBase, issuesRegistryBase, daoFactory + let kernelWithoutKillSwitchBase, kernelWithNonCompliantKillSwitchBase, failingKillSwitchBase + let CORE_NAMESPACE, KERNEL_APP_ID, APP_MANAGER_ROLE, CHANGE_SEVERITY_ROLE, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, CHANGE_ISSUES_REGISTRY_ROLE, CHANGE_WHITELISTED_INSTANCES_ROLE, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, WRITER_ROLE + + before('deploy base implementations', async () => { + // real + kernelBase = await Kernel.new(true) // petrify immediately + aclBase = await ACL.new() + registryFactory = await EVMScriptRegistryFactory.new() + killSwitchBase = await KillSwitch.new() + issuesRegistryBase = await IssuesRegistry.new() + + // mocks + appBase = await KillSwitchedApp.new() + failingKillSwitchBase = await RevertingKillSwitchMock.new() + kernelWithoutKillSwitchBase = await KernelWithoutKillSwitchMock.new() + kernelWithNonCompliantKillSwitchBase = await KernelWithNonCompliantKillSwitchMock.new() + }) + + before('load constants and roles', async () => { + WRITER_ROLE = await appBase.WRITER_ROLE() + CORE_NAMESPACE = await kernelBase.CORE_NAMESPACE() + KERNEL_APP_ID = await kernelBase.KERNEL_APP_ID() + APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE() + CHANGE_SEVERITY_ROLE = await issuesRegistryBase.CHANGE_SEVERITY_ROLE() + CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE = await killSwitchBase.CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE() + CHANGE_ISSUES_REGISTRY_ROLE = await killSwitchBase.CHANGE_ISSUES_REGISTRY_ROLE() + CHANGE_WHITELISTED_INSTANCES_ROLE = await killSwitchBase.CHANGE_WHITELISTED_INSTANCES_ROLE() + CHANGE_BLACKLISTED_BASE_IMPLS_ROLE = await killSwitchBase.CHANGE_BLACKLISTED_BASE_IMPLS_ROLE() + CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE = await killSwitchBase.CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE() + }) + + context('when the kernel version does not support kill-switch logic', async () => { + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, ZERO_ADDRESS, registryFactory.address) + }) + + beforeEach('deploy DAO without a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = appBase.contract.initialize.getData(owner) + const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + }) + + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + }) + + context('when the kernel version does support kill-switch logic', async () => { + context('when the kernel was not initialized with a kill-switch', async () => { + before('create DAO factory using a kernel that supports kill-switch logic', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + + before('deploy DAO without a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAO(root) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = appBase.contract.initialize.getData(owner) + const appReceipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(appReceipt)) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + }) + + context('when the function being called is not tagged', () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + }) + + context('when the function being called is tagged', () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + }) + }) + + context('when the kernel is initialized with a non-compliant kill-switch implementation', async () => { + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelWithoutKillSwitchBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + + before('deploy DAO with a kill switch and create kill-switched sample app', async () => { + const daoFactoryReceipt = await daoFactory.newDAOWithKillSwitch(root, issuesRegistryBase.address) + dao = Kernel.at(getEventArgument(daoFactoryReceipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = appBase.contract.initialize.getData(owner) + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(receipt)) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + + // upgrade kernel to non-compliant implementation + await dao.setApp(CORE_NAMESPACE, KERNEL_APP_ID, kernelWithNonCompliantKillSwitchBase.address, { from: root }) + }) + + context('when the function being called is not tagged', () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + }) + + context('when the function being called is tagged', () => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from: owner }), 'APP_AUTH_FAILED') + }) + }) + }) + + context('when the kernel is initialized with a failing kill-switch implementation', async () => { + let killSwitch, defaultIssuesRegistry + + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, failingKillSwitchBase.address, registryFactory.address) + }) + + before('create issues registry', async () => { + const daoReceipt = await daoFactory.newDAO(root) + const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) + + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = issuesRegistryBase.contract.initialize.getData() + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, initializeData, false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with a kill switch', async () => { + const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + killSwitch = KillSwitch.at(await dao.killSwitch()) + + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const initializeData = appBase.contract.initialize.getData(owner) + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(receipt)) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + }) + + const itExecutesTheCall = () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + } + + context('when the function being called is not tagged', () => { + itExecutesTheCall() + }) + + context('when the function being called is tagged', () => { + const itAlwaysExecutesTheCall = () => { + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + // Note that whitelisting a single instance has higher precedence than blacklisting a base implementation + itExecutesTheCall() + }) + }) + + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('do not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + } + + context('when there is no bug registered', () => { + itAlwaysExecutesTheCall() + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when there is no highest whitelisted severity set for the contract being called', () => { + itAlwaysExecutesTheCall() + }) + + context('when there is a highest whitelisted severity set for the contract being called', () => { + context('when the highest whitelisted severity is under the reported bug severity', () => { + beforeEach('set highest whitelisted severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + + context('when the highest whitelisted severity is equal to the reported bug severity', () => { + beforeEach('set highest whitelisted severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + + context('when the highest whitelisted severity is greater than the reported bug severity', () => { + beforeEach('set highest whitelisted severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) + }) + + itAlwaysExecutesTheCall() + }) + }) + }) + }) + }) + + context('when the kernel is initialized with a safe kill-switch implementation', async () => { + let killSwitch, defaultIssuesRegistry + + before('create DAO factory', async () => { + daoFactory = await DAOFactory.new(kernelBase.address, aclBase.address, killSwitchBase.address, registryFactory.address) + }) + + beforeEach('create issues registries', async () => { + const daoReceipt = await daoFactory.newDAO(root) + const issuesRegistryDAO = Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao')) + const issuesRegistryACL = ACL.at(await issuesRegistryDAO.acl()) + await issuesRegistryACL.createPermission(root, issuesRegistryDAO.address, APP_MANAGER_ROLE, root, { from: root }) + + const initializeData = issuesRegistryBase.contract.initialize.getData() + const defaultRegistryReceipt = await issuesRegistryDAO.newAppInstance('0x1234', issuesRegistryBase.address, initializeData, false, { from: root }) + defaultIssuesRegistry = IssuesRegistry.at(getNewProxyAddress(defaultRegistryReceipt)) + await issuesRegistryACL.createPermission(securityPartner, defaultIssuesRegistry.address, CHANGE_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('deploy DAO with a kill switch', async () => { + const receipt = await daoFactory.newDAOWithKillSwitch(root, defaultIssuesRegistry.address) + dao = Kernel.at(getEventArgument(receipt, 'DeployDAO', 'dao')) + acl = ACL.at(await dao.acl()) + killSwitch = KillSwitch.at(await dao.killSwitch()) + await acl.createPermission(root, dao.address, APP_MANAGER_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_DEFAULT_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_ISSUES_REGISTRY_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_WHITELISTED_INSTANCES_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_BLACKLISTED_BASE_IMPLS_ROLE, root, { from: root }) + await acl.createPermission(owner, killSwitch.address, CHANGE_HIGHEST_ALLOWED_SEVERITY_ROLE, root, { from: root }) + }) + + beforeEach('create kill switched app', async () => { + const initializeData = appBase.contract.initialize.getData(owner) + const receipt = await dao.newAppInstance(SAMPLE_APP_ID, appBase.address, initializeData, false, { from: root }) + app = KillSwitchedApp.at(getNewProxyAddress(receipt)) + await acl.createPermission(owner, app.address, WRITER_ROLE, root, { from: root }) + }) + + context('when the function being called is not tagged', () => { + + const itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted = () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + assert.equal(await app.read(), 42) + }) + } + + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + } + + context('when there is no bug registered', () => { + itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + itExecutesTheCallEvenWhenBaseImplementationIsBlacklisted() + }) + }) + + context('when the function being called is tagged', () => { + const itExecutesTheCall = () => { + it('executes the call', async () => { + await app.write(10, { from: owner }) + assert.equal(await app.read(), 10) + }) + } + + const itDoesNotExecuteTheCall = () => { + it('does not execute the call', async () => { + await assertRevert(app.write(10, { from: owner }), 'APP_AUTH_FAILED') + }) + } + + const itExecutesTheCallOnlyWhenWhitelisted = () => { + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + }) + } + + const itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted = () => { + context('when the instance being called is whitelisted', () => { + beforeEach('whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, true, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itExecutesTheCall() + }) + }) + + context('when the instance being called is not marked as whitelisted', () => { + beforeEach('dot not whitelist instance', async () => { + await killSwitch.setWhitelistedInstance(app.address, false, { from: owner }) + }) + + context('when the base implementation is not blacklisted', () => { + beforeEach('do not blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, false, { from: owner }) + }) + + itExecutesTheCall() + }) + + context('when the base implementation is blacklisted', () => { + beforeEach('blacklist base implementation', async () => { + await killSwitch.setBlacklistedBaseImplementation(appBase.address, true, { from: owner }) + }) + + itDoesNotExecuteTheCall() + }) + }) + } + + context('when there is no bug registered', () => { + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + + context('when there is a bug registered', () => { + beforeEach('register a bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.MID, { from: securityPartner }) + }) + + context('when the bug was real', () => { + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallOnlyWhenWhitelisted() + }) + + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallOnlyWhenWhitelisted() + }) + + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + }) + }) + + context('when the bug was a false positive', () => { + beforeEach('roll back reported bug', async () => { + await defaultIssuesRegistry.setSeverityFor(appBase.address, SEVERITY.NONE, { from: securityPartner }) + }) + + context('when there is no highest allowed severity set for the contract being called', () => { + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + + context('when there is a highest allowed severity set for the contract being called', () => { + context('when the highest allowed severity is under the reported bug severity', () => { + beforeEach('set highest allowed severity below the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.LOW, { from: owner }) + }) + + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + + context('when the highest allowed severity is equal to the reported bug severity', () => { + beforeEach('set highest allowed severity equal to the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.MID, { from: owner }) + }) + + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + + context('when the highest allowed severity is greater than the reported bug severity', () => { + beforeEach('set highest allowed severity above the one reported', async () => { + await killSwitch.setHighestAllowedSeverity(SAMPLE_APP_ID, SEVERITY.CRITICAL, { from: owner }) + }) + + itExecutesTheCallUnlessInstanceNotWhitelistedAndBaseBlacklisted() + }) + }) + }) + }) + }) + }) + }) +}) From c057f0d7aceef6292cd8e6a6f62b448b05be99ee Mon Sep 17 00:00:00 2001 From: Facundo Spagnuolo Date: Mon, 10 Jun 2019 11:45:30 -0300 Subject: [PATCH 37/37] kill-switch: rename mocks dir and improve inline doc --- contracts/apps/AragonApp.sol | 6 +++--- .../KernelWithNonCompliantKillSwitchMock.sol | 0 .../KernelWithoutKillSwitchMock.sol | 0 .../{kill_switch => kill-switch}/KillSwitchedAppMock.sol | 0 .../RevertingKillSwitchMock.sol | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename contracts/test/mocks/{kill_switch => kill-switch}/KernelWithNonCompliantKillSwitchMock.sol (100%) rename contracts/test/mocks/{kill_switch => kill-switch}/KernelWithoutKillSwitchMock.sol (100%) rename contracts/test/mocks/{kill_switch => kill-switch}/KillSwitchedAppMock.sol (100%) rename contracts/test/mocks/{kill_switch => kill-switch}/RevertingKillSwitchMock.sol (100%) diff --git a/contracts/apps/AragonApp.sol b/contracts/apps/AragonApp.sol index 1755edaa2..b49488aa0 100644 --- a/contracts/apps/AragonApp.sol +++ b/contracts/apps/AragonApp.sol @@ -71,19 +71,19 @@ contract AragonApp is AppStorage, Autopetrified, VaultRecoverable, ReentrancyGua } // 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 + // there is no kill switch. Therefore, the the call can be executed. if (!success) { return true; } - // if not, check returned value is 32 bytes length, otherwise return false + // If it does not revert, check if the returned value is 32-bytes length, otherwise return false uint256 _outputLength; assembly { _outputLength := returndatasize } if (_outputLength != 32) { return false; } - // forward returned value + // Forward returned value bool _shouldDenyCall; assembly { let ptr := mload(0x40) // get next free memory pointer diff --git a/contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol b/contracts/test/mocks/kill-switch/KernelWithNonCompliantKillSwitchMock.sol similarity index 100% rename from contracts/test/mocks/kill_switch/KernelWithNonCompliantKillSwitchMock.sol rename to contracts/test/mocks/kill-switch/KernelWithNonCompliantKillSwitchMock.sol diff --git a/contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol b/contracts/test/mocks/kill-switch/KernelWithoutKillSwitchMock.sol similarity index 100% rename from contracts/test/mocks/kill_switch/KernelWithoutKillSwitchMock.sol rename to contracts/test/mocks/kill-switch/KernelWithoutKillSwitchMock.sol diff --git a/contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol b/contracts/test/mocks/kill-switch/KillSwitchedAppMock.sol similarity index 100% rename from contracts/test/mocks/kill_switch/KillSwitchedAppMock.sol rename to contracts/test/mocks/kill-switch/KillSwitchedAppMock.sol diff --git a/contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol b/contracts/test/mocks/kill-switch/RevertingKillSwitchMock.sol similarity index 100% rename from contracts/test/mocks/kill_switch/RevertingKillSwitchMock.sol rename to contracts/test/mocks/kill-switch/RevertingKillSwitchMock.sol