diff --git a/contracts/ProxyOwnedByDao.sol b/contracts/ProxyOwnedByDao.sol new file mode 100644 index 0000000..3b60022 --- /dev/null +++ b/contracts/ProxyOwnedByDao.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.4.23; + +import "@thetta/core/contracts/DaoBase.sol"; +import "@thetta/core/contracts/DaoClient.sol"; + +import "./Proxy.sol"; + +contract ProxyOwnedByDAO is DaoClient { + Proxy public proxy; + + bytes32 public constant TRANSFER_DELEGATION = keccak256("Proxy_TransferDelegation"); + bytes32 public constant TRANSFER_OWNERSHIP = keccak256("Proxy_TransferOwnership"); + + constructor(DaoBase _daoBase, Proxy _proxy) DaoClient(_daoBase) { + // Please don't forget to call _proxy.transferOwnership() to this contract immediately + // after ProxyOwnedByDAO_1 is instantiated + proxy = _proxy; + } + +// These methods now require special custom permissions: + function DAO_transferDelegation(address _newDelegation) public isCanDo(TRANSFER_DELEGATION) { + proxy.transferDelegation(_newDelegation); + } + + function DAO_transferOwnership(address _newOwner) public isCanDo(TRANSFER_OWNERSHIP) { + proxy.transferOwnership(_newOwner); + } +} + diff --git a/migrations/3_deploy_contracts_dao.js b/migrations/3_deploy_contracts_dao.js new file mode 100644 index 0000000..a636eda --- /dev/null +++ b/migrations/3_deploy_contracts_dao.js @@ -0,0 +1,7 @@ +var migrateLibs = require('@thetta/core/scripts/migrateLibs'); + +module.exports = function (deployer, network, accounts) { + let additionalContracts = [ "./ProxyOwnedByDAO"]; + + return migrateLibs(artifacts, additionalContracts, deployer, network, accounts); +}; diff --git a/package.json b/package.json index f2cb22d..e20691d 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,17 @@ "babel-preset-stage-2": "^6.18.0", "babel-preset-stage-3": "^6.17.0", "babel-register": "^6.23.0", - "truffle-privatekey-provider": "0.0.6", "dotenv": "^5.0.1", "ganache-cli": "^6.0.3", "solium": "^1.1.2", - "truffle": "4.0.6" + "truffle": "^4.1.14", + "truffle-privatekey-provider": "0.0.6" }, "dependencies": { + "@thetta/core": "^1.4.1", "bignumber.js": "^4.0.1", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "zeppelin-solidity": "^1.6.0" } } diff --git a/test/icomalta_dao.js b/test/icomalta_dao.js new file mode 100644 index 0000000..75a0ebc --- /dev/null +++ b/test/icomalta_dao.js @@ -0,0 +1,104 @@ +const Proxy = artifacts.require('./Proxy.sol'); +const ProxyOwnedByDAO = artifacts.require('./ProxyOwnedByDAO.sol'); +const Controller = artifacts.require('./Controller.sol'); + +const { assertRevert } = require('./helpers/assertThrow') + +// Thetta +var DaoBase = artifacts.require('./DaoBase'); +var StdDaoToken = artifacts.require('./StdDaoToken'); +var DaoStorage = artifacts.require('./DaoStorage'); + +contract('ProxyOwnedByDAO', (accounts) => { + let proxy; + let proxyDao; + let token; + let controller; + + beforeEach(async () => { + proxy = await Proxy.new(); + + controller = await Controller.new(); + token = Controller.at(proxy.address); + }); + + describe('proxy + proxyDAO with no permissions', async() => { + beforeEach(async () => { + // Create new proxyDao + let t = await StdDaoToken.new("StdToken","STDT",18, true, true, 1000000000); + let store = await DaoStorage.new([t.address]); + let daoBase = await DaoBase.new(store.address); + proxyDao = await ProxyOwnedByDAO.new(daoBase.address, proxy.address); + await t.transferOwnership(daoBase.address); + await store.transferOwnership(daoBase.address); + + // finish initialization of DAOBase + await daoBase.renounceOwnership(); + + // initialize token contract + await token.initialize(controller.address, 400000000); + + // transfer ownership to proxyDao + await proxy.transferOwnership(proxyDao.address); + assert.equal(await proxy.owner(), proxyDao.address); + }); + + it('should not allow to transfer ownership directly', async() => { + return assertRevert(async () => { + await proxy.transferOwnership(accounts[1]); + }); + assert.notEqual(await proxy.owner(), accounts[1]); + }); + + it('should not allow to transfer ownership through the proxyDAO', async() => { + return assertRevert(async () => { + await proxyDao.DAO_transferOwnership(accounts[1]); + }); + assert.notEqual(await proxy.owner(), accounts[1]); + }); + }); + + describe('proxy + proxyDAO with permissions', async() => { + beforeEach(async () => { + // Create new proxyDao + let t = await StdDaoToken.new("StdToken","STDT",18, true, true, 1000000000); + let store = await DaoStorage.new([t.address]); + let daoBase = await DaoBase.new(store.address); + proxyDao = await ProxyOwnedByDAO.new(daoBase.address, proxy.address); + await t.transferOwnership(daoBase.address); + await store.transferOwnership(daoBase.address); + + // set permissions + await daoBase.addGroupMember("Employees", accounts[0]); + + const transferDelegationPerm = await proxyDao.TRANSFER_DELEGATION(); + const transferOwnershipPerm = await proxyDao.TRANSFER_OWNERSHIP(); + // NOTE: we can use votings here instead! + await daoBase.allowActionByAnyMemberOfGroup(transferDelegationPerm, "Employees"); + await daoBase.allowActionByAnyMemberOfGroup(transferOwnershipPerm, "Employees"); + + // finish initialization of DAOBase + await daoBase.renounceOwnership(); + + // initialize token contract + await token.initialize(controller.address, 400000000); + + // transfer ownership to proxyDao + await proxy.transferOwnership(proxyDao.address); + assert.equal(await proxy.owner(), proxyDao.address); + }); + + it('should not allow to transfer ownership directly', async() => { + return assertRevert(async () => { + await proxy.transferOwnership(accounts[1]); + }); + assert.notEqual(await proxy.owner(), accounts[1]); + }); + + it('should allow to transfer ownership through the proxyDAO!', async() => { + await proxyDao.DAO_transferOwnership(accounts[1]); + assert.equal(await proxy.owner(), accounts[1]); + }); + }); + +}); diff --git a/truffle.js b/truffle.js index 204e3e7..09c8212 100644 --- a/truffle.js +++ b/truffle.js @@ -9,7 +9,7 @@ module.exports = { host: "localhost", port: 8545, gasPrice: 10000000000, - gas: 5000000, + gas: 9000000, network_id: "*" // Match any network id }, kovan: {