diff --git a/kits/payroll-demo/README.md b/kits/payroll-demo/README.md new file mode 100644 index 00000000..e691655f --- /dev/null +++ b/kits/payroll-demo/README.md @@ -0,0 +1,15 @@ +Demo Payroll Kit +=============== + +Usage +----- + +This kit requires you to already have the [Payroll Kit](../payroll) deployed onto a local chain. + +`$ aragon devchain` + +Inside [Payroll Kit](../payroll) `$ npm run migrate` + +Once you've deployed the base Payroll Kit, you can simply run this kit's migration + +`npm run migrate`. diff --git a/kits/payroll-demo/contracts/PPF.sol b/kits/payroll-demo/contracts/PPF.sol new file mode 100644 index 00000000..f4302262 --- /dev/null +++ b/kits/payroll-demo/contracts/PPF.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.4.24; + +import "@aragon/ppf-contracts/contracts/IFeed.sol"; + +contract PPF is IFeed { + function get(address base, address quote) external view returns (uint128 xrt, uint64 when) { + xrt = 1; + when = uint64(now); + } +} diff --git a/kits/payroll-demo/contracts/PayrollDemoImports.sol b/kits/payroll-demo/contracts/PayrollDemoImports.sol new file mode 100644 index 00000000..44f5dbe8 --- /dev/null +++ b/kits/payroll-demo/contracts/PayrollDemoImports.sol @@ -0,0 +1,9 @@ +pragma solidity 0.4.24; + +// HACK to workaround truffle artifact loading on dependencies + +import "@aragon/kits-payroll/contracts/Imports.sol"; +import "@aragon/kits-payroll/contracts/PayrollKit.sol"; +import "@aragon/apps-shared-minime/contracts/MiniMeToken.sol"; + +contract PayrollDemoImports {} diff --git a/kits/payroll-demo/contracts/misc/Migrations.sol b/kits/payroll-demo/contracts/misc/Migrations.sol new file mode 100644 index 00000000..35a56a2c --- /dev/null +++ b/kits/payroll-demo/contracts/misc/Migrations.sol @@ -0,0 +1,24 @@ +pragma solidity 0.4.24; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) + _; + } + + function Migrations() public { + owner = msg.sender; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/kits/payroll-demo/migrations/1_initial_migration.js b/kits/payroll-demo/migrations/1_initial_migration.js new file mode 100644 index 00000000..4d5f3f9b --- /dev/null +++ b/kits/payroll-demo/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +var Migrations = artifacts.require("./Migrations.sol"); + +module.exports = function(deployer) { + deployer.deploy(Migrations); +}; diff --git a/kits/payroll-demo/migrations/2_deploy.js b/kits/payroll-demo/migrations/2_deploy.js new file mode 100644 index 00000000..8db3c2cd --- /dev/null +++ b/kits/payroll-demo/migrations/2_deploy.js @@ -0,0 +1,77 @@ +const path = require('path') +const fs = require('fs') + +const namehash = require('eth-ens-namehash').hash + +const ENS = artifacts.require('@aragon/os/contracts/lib/ens/ENS') +const MiniMeToken = artifacts.require('@aragon/apps-shared-minime/contracts/MiniMeToken') +const PublicResolver = artifacts.require('@aragon/os/contracts/lib/ens/PublicResolver') +const Repo = artifacts.require('@aragon/os/contracts/apm/Repo') + +const PayrollKit = artifacts.require('@aragon/kits-payroll/contracts/PayrollKit') +const Payroll = artifacts.require('@aragon/future-apps-payroll/contracts/Payroll') + +// Utils +const payrollAppId = namehash('payroll.aragonpm.eth') +const payrollKitEnsNode = namehash('payroll-kit.aragonpm.eth') +const timeTravel = require('@aragon/test-helpers/timeTravel')(web3) +const pct16 = x => new web3.BigNumber(x).times(new web3.BigNumber(10).toPower(16)) +const createdPayrollDao = receipt => receipt.logs.filter(x => x.event == 'DeployInstance')[0].args.dao +const createdPayrollId = receipt => receipt.logs.filter(x => x.event == 'StartPayroll')[0].args.payroId +const installedApp = (receipt, appId) => receipt.logs.filter(x => x.event == 'InstalledApp' && x.args.appId === appId)[0].args.appProxy + +module.exports = (deployer, network, accounts) => { + deployer.then(async () => { + const root = accounts[0] + + const ens = ENS.at( + process.env.ENS || + '0x5f6f7e8cc7346a11ca2def8f827b7a0b612c56a1' // aragen's default ENS + ) + const payrollKitRepo = Repo.at( + await PublicResolver.at( + await ens.resolver(payrollKitEnsNode) + ).addr(payrollKitEnsNode) + ) + // Contract address is second return of Repo.getLatest() + const payrollKit = PayrollKit.at((await payrollKitRepo.getLatest())[1]) + + // const minimeFac = await MiniMeTokenFactory.new() + const denominationToken = await MiniMeToken.new( + '0x00', + '0x00', + '0x00', + 'USD Dolar', + 18, + 'USD', + true + ) + + const ppf = await artifacts.require('PPF').new() + + const SECONDS_IN_A_YEAR = 31557600 // 365.25 days + const RATE_EXPIRY_TIME = 1000 + + // Create DAO with Payroll installed + console.log('Creating Payroll DAO...') + const payrollDaoReceipt = await payrollKit.newInstance( + root, + root, + SECONDS_IN_A_YEAR, + denominationToken.address, + ppf.address, + RATE_EXPIRY_TIME + ) + + const payrollDaoAddr = createdPayrollDao(payrollDaoReceipt) + const payrollAppAddr = installedApp(payrollDaoReceipt, payrollAppId) + + // TODO: Create some sample data + + console.log('===========') + console.log('Payroll demo DAO set up!') + console.log('Payroll DAO:', payrollDaoAddr) + console.log("Payroll DAO's Payroll app:", payrollAppAddr) + console.log('Payroll Token:', denominationToken.address) + }) +} diff --git a/kits/payroll-demo/package.json b/kits/payroll-demo/package.json new file mode 100644 index 00000000..7dc13340 --- /dev/null +++ b/kits/payroll-demo/package.json @@ -0,0 +1,31 @@ +{ + "name": "@aragon/kits-payroll-demo", + "version": "1.0.0", + "description": "Demo kit for setting up a DAO to do Payroll", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "migrate": "truffle migrate --network rpc --reset" + }, + "author": "Aragon Institution MTU ", + "license": "GPL-3.0", + "devDependencies": { + "eth-ens-namehash": "^2.0.8", + "truffle": "4.1.14", + "truffle-hdwallet-provider": "0.0.3" + }, + "dependencies": { + "@aragon/kits-bare": "1.0.0", + "@aragon/kits-payroll": "1.0.0", + "@aragon/apps-shared-minime": "^1.0.0", + "@aragon/apps-finance": "^2.0.0-beta.2", + "@aragon/apps-vault": "^3.0.0-beta.2", + "@aragon/future-apps-payroll": "0.1.0", + "@aragon/test-helpers": "^1.0.1", + "@aragon/ppf-contracts": "^1.0.2", + "@aragon/ppf.js": "1.0.1", + "@aragon/os": "^4.0.0-beta.3" + } +} diff --git a/kits/payroll-demo/truffle.js b/kits/payroll-demo/truffle.js new file mode 100644 index 00000000..eae72b03 --- /dev/null +++ b/kits/payroll-demo/truffle.js @@ -0,0 +1,6 @@ +let x = require("@aragon/os/truffle-config") + +x.networks.rinkeby.gasPrice = 25000000001 +x.networks.rinkeby.gas = 7e6 + +module.exports = x diff --git a/kits/payroll/contracts/Imports.sol b/kits/payroll/contracts/Imports.sol index 8e8c23ef..c3b8f031 100644 --- a/kits/payroll/contracts/Imports.sol +++ b/kits/payroll/contracts/Imports.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.18; +pragma solidity 0.4.24; // HACK to workaround truffle artifact loading on dependencies diff --git a/kits/payroll/contracts/PayrollKit.sol b/kits/payroll/contracts/PayrollKit.sol index c783e777..a38fa4ec 100644 --- a/kits/payroll/contracts/PayrollKit.sol +++ b/kits/payroll/contracts/PayrollKit.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.18; +pragma solidity 0.4.24; import "@aragon/os/contracts/kernel/Kernel.sol"; import "@aragon/os/contracts/acl/ACL.sol"; @@ -11,84 +11,83 @@ import "@aragon/apps-vault/contracts/Vault.sol"; import "@aragon/kits-bare/contracts/KitBase.sol"; -contract PayrollKit is KitBase { - function PayrollKit(DAOFactory _fac, ENS _ens) KitBase(_fac, _ens) {} +contract PayrollKit is KitBase, APMNamehash { + constructor( + DAOFactory _fac, + ENS _ens + ) + KitBase(_fac, _ens) + public + {} function newInstance( address employer, address root, uint64 financePeriodDuration, address denominationToken, - IFeed priceFeed, + IFeed priceFeed, uint64 rateExpiryTime - ) returns (Kernel dao, Payroll payroll) { - dao = fac.newDAO(this); - ACL acl = ACL(dao.acl()); - - acl.createPermission(this, dao, dao.APP_MANAGER_ROLE(), this); - - Vault vault; - Finance finance; - (vault, finance, payroll) = deployApps(dao); - - finance.initialize(vault, financePeriodDuration); - payroll.initialize(finance, denominationToken, priceFeed, rateExpiryTime); - - // Payroll permissions - acl.createPermission(employer, payroll, payroll.ADD_EMPLOYEE_ROLE(), root); - acl.createPermission(employer, payroll, payroll.REMOVE_EMPLOYEE_ROLE(), root); - acl.createPermission(employer, payroll, payroll.ALLOWED_TOKENS_MANAGER_ROLE(), root); - acl.createPermission(root, payroll, payroll.CHANGE_PRICE_FEED_ROLE(), root); - acl.createPermission(root, payroll, payroll.MODIFY_RATE_EXPIRY_ROLE(), root); - - // Finance permissions - acl.createPermission(payroll, finance, finance.CREATE_PAYMENTS_ROLE(), root); - - // Vault permissions - bytes32 vaultTransferRole = vault.TRANSFER_ROLE(); - acl.createPermission(finance, vault, vaultTransferRole, this); // manager is this to allow 2 grants - acl.grantPermission(root, vault, vaultTransferRole); - acl.setPermissionManager(root, vault, vaultTransferRole); // set root as the final manager for the role - - cleanupDAOPermissions(dao, acl, root); - - DeployInstance(dao); - } + ) + public + returns (Kernel dao, Payroll payroll) + { + dao = fac.newDAO(this); + ACL acl = ACL(dao.acl()); - function deployApps(Kernel dao) internal returns (Vault, Finance, Payroll) { - bytes32 vaultAppId = apmNamehash("vault"); - bytes32 financeAppId = apmNamehash("finance"); - bytes32 payrollAppId = apmNamehash("payroll"); + acl.createPermission(this, dao, dao.APP_MANAGER_ROLE(), this); - Vault vault = Vault(dao.newAppInstance(vaultAppId, latestVersionAppBase(vaultAppId))); - Finance finance = Finance(dao.newAppInstance(financeAppId, latestVersionAppBase(financeAppId))); - Payroll payroll = Payroll(dao.newAppInstance(payrollAppId, latestVersionAppBase(payrollAppId))); + Vault vault; + Finance finance; + (vault, finance, payroll) = deployApps(dao); - InstalledApp(vault, vaultAppId); - InstalledApp(finance, financeAppId); - InstalledApp(payroll, payrollAppId); + finance.initialize(vault, financePeriodDuration); + payroll.initialize(finance, denominationToken, priceFeed, rateExpiryTime); - return (vault, finance, payroll); - } + // Payroll permissions + acl.createPermission(employer, payroll, payroll.ADD_EMPLOYEE_ROLE(), root); + acl.createPermission(employer, payroll, payroll.TERMINATE_EMPLOYEE_ROLE(), root); + acl.createPermission(employer, payroll, payroll.ALLOWED_TOKENS_MANAGER_ROLE(), root); + acl.createPermission(employer, payroll, payroll.SET_EMPLOYEE_SALARY_ROLE(), root); + acl.createPermission(employer, payroll, payroll.ADD_ACCRUED_VALUE_ROLE(), root); + acl.createPermission(root, payroll, payroll.CHANGE_PRICE_FEED_ROLE(), root); + acl.createPermission(root, payroll, payroll.MODIFY_RATE_EXPIRY_ROLE(), root); + + // Finance permissions + acl.createPermission(payroll, finance, finance.CREATE_PAYMENTS_ROLE(), root); + + // Vault permissions + setVaultPermissions(acl, vault, finance, root); + + /// EVMScriptRegistry permissions + EVMScriptRegistry reg = EVMScriptRegistry(dao.getApp(dao.APP_ADDR_NAMESPACE(), EVMSCRIPT_REGISTRY_APP_ID)); + acl.createBurnedPermission(reg, reg.REGISTRY_ADD_EXECUTOR_ROLE()); + acl.createBurnedPermission(reg, reg.REGISTRY_MANAGER_ROLE()); - function cleanupDAOPermissions(Kernel dao, ACL acl, address root) internal { - bytes32 daoAppManagerRole = dao.APP_MANAGER_ROLE(); - // Kernel permission clean up - acl.grantPermission(root, dao, daoAppManagerRole); - acl.revokePermission(this, dao, daoAppManagerRole); - acl.setPermissionManager(root, dao, daoAppManagerRole); - - // ACL permission clean up - bytes32 aclCreatePermissionsRole = acl.CREATE_PERMISSIONS_ROLE(); - acl.grantPermission(root, acl, aclCreatePermissionsRole); - acl.revokePermission(this, acl, aclCreatePermissionsRole); - acl.setPermissionManager(root, acl, aclCreatePermissionsRole); + cleanupDAOPermissions(dao, acl, root); + + emit DeployInstance(dao); } - function latestVersionAppBase(bytes32 appId) public view returns (address base) { - Repo repo = Repo(PublicResolver(ens.resolver(appId)).addr(appId)); - (,base,) = repo.getLatest(); + function deployApps(Kernel dao) internal returns (Vault, Finance, Payroll) { + bytes32 vaultAppId = apmNamehash("vault"); + bytes32 financeAppId = apmNamehash("finance"); + bytes32 payrollAppId = apmNamehash("payroll"); + + Vault vault = Vault(dao.newAppInstance(vaultAppId, latestVersionAppBase(vaultAppId))); + Finance finance = Finance(dao.newAppInstance(financeAppId, latestVersionAppBase(financeAppId))); + Payroll payroll = Payroll(dao.newAppInstance(payrollAppId, latestVersionAppBase(payrollAppId))); + + emit InstalledApp(vault, vaultAppId); + emit InstalledApp(finance, financeAppId); + emit InstalledApp(payroll, payrollAppId); + + return (vault, finance, payroll); + } - return base; + function setVaultPermissions(ACL acl, Vault vault, Finance finance, address root) internal { + bytes32 vaultTransferRole = vault.TRANSFER_ROLE(); + acl.createPermission(finance, vault, vaultTransferRole, this); // manager is this to allow 2 grants + acl.grantPermission(root, vault, vaultTransferRole); + acl.setPermissionManager(root, vault, vaultTransferRole); // set root as the final manager for the role } } diff --git a/kits/payroll/contracts/misc/Migrations.sol b/kits/payroll/contracts/misc/Migrations.sol index f170cb4f..35a56a2c 100644 --- a/kits/payroll/contracts/misc/Migrations.sol +++ b/kits/payroll/contracts/misc/Migrations.sol @@ -1,23 +1,24 @@ -pragma solidity ^0.4.17; +pragma solidity 0.4.24; contract Migrations { - address public owner; - uint public last_completed_migration; + address public owner; + uint public last_completed_migration; - modifier restricted() { - if (msg.sender == owner) _; - } + modifier restricted() { + if (msg.sender == owner) + _; + } - function Migrations() public { - owner = msg.sender; - } + function Migrations() public { + owner = msg.sender; + } - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } } diff --git a/kits/payroll/migrations/2_deploy.js b/kits/payroll/migrations/2_deploy.js new file mode 100644 index 00000000..6f0b0ec2 --- /dev/null +++ b/kits/payroll/migrations/2_deploy.js @@ -0,0 +1,49 @@ +const path = require('path') +const fs = require('fs') + +const namehash = require('eth-ens-namehash').hash + +const deployDAOFactory = require('@aragon/os/scripts/deploy-daofactory.js') + +const payrollAppId = namehash('payroll.aragonpm.eth') + +const newRepo = async (apm, name, acc, contract, contentURI = "ipfs:") => { + const c = await artifacts.require(contract).new() + console.log('creating apm repo for', name) + return await apm.newRepoWithVersion(name, acc, [1, 0, 0], c.address, contentURI) +} + +module.exports = function (deployer, network, accounts, arts = null) { + deployer.then(async () => { + if (arts != null) artifacts = arts // allow running outside + + const ENS = artifacts.require('@aragon/os/contracts/lib/ens/ENS.sol') + const PayrollKit = artifacts.require('PayrollKit') + const Payroll = artifacts.require('Payroll') + + const ens = ENS.at( + process.env.ENS || + '0x5f6f7e8cc7346a11ca2def8f827b7a0b612c56a1' // aragen's default ENS + ) + + const apmAddr = await artifacts.require('PublicResolver').at(await ens.resolver(namehash('aragonpm.eth'))).addr(namehash('aragonpm.eth')) + + console.log('Deploying DAOFactory...') + const { daoFactory } = await deployDAOFactory(null, { artifacts, verbose: false }) + + console.log('Deploying PayrollKit...') + const kit = await PayrollKit.new(daoFactory.address, ens.address) + + console.log('Creating APM package for PayrollKit...') + const apm = artifacts.require('APMRegistry').at(apmAddr) + await apm.newRepoWithVersion('payroll-kit', accounts[0], [1, 0, 0], kit.address, 'ipfs:') + + console.log('Creating APM package for Payroll...') + const payroll = await Payroll.new() + await apm.newRepoWithVersion('payroll', accounts[0], [1, 0, 0], payroll.address, 'ipfs:') + + console.log('PayrollKit:', kit.address) + + return kit.address + } +)} diff --git a/kits/payroll/package.json b/kits/payroll/package.json index c67ff762..1425a6a5 100644 --- a/kits/payroll/package.json +++ b/kits/payroll/package.json @@ -13,15 +13,15 @@ "license": "GPL-3.0", "devDependencies": { "eth-ens-namehash": "^2.0.8", - "truffle": "4.0.5", + "truffle": "4.1.14", "truffle-hdwallet-provider": "0.0.3" }, "dependencies": { - "@aragon/apps-finance": "^1.0.0", - "@aragon/apps-vault": "^2.0.1", - "@aragon/future-apps-payroll": "^0.0.1", - "@aragon/kits-bare": "^1.0.0", + "@aragon/apps-finance": "^2.0.0-beta.2", + "@aragon/apps-vault": "^3.0.0-beta.2", + "@aragon/future-apps-payroll": "0.1.0", + "@aragon/kits-bare": "1.0.0", "@aragon/ppf-contracts": "^1.0.2", - "@aragon/os": "^3.1.9" + "@aragon/os": "^4.0.0-beta.3" } } diff --git a/link-all.sh b/link-all.sh new file mode 100755 index 00000000..df0cdef3 --- /dev/null +++ b/link-all.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +GREEN="\033[1;32m" +NOCOLOR="\033[0m" + +# Kit Bare +cd kits/bare/ +echo -e "=> Linking ${GREEN}@aragon/kits-bare${NOCOLOR}" +npm link + +echo -e "\n=> Linking ${GREEN}@aragon/kits-payroll${NOCOLOR}" +cd ../payroll +npm link @aragon/kits-bare +npm link @aragon/future-apps-payroll +npm link + +echo -e "\n=> Linking ${GREEN}@aragon/kits-payroll-demo${NOCOLOR}" +cd ../payroll-demo +npm link @aragon/kits-bare +npm link @aragon/kits-payroll +npm link @aragon/future-apps-payroll