diff --git a/docs/Apm-commands.md b/docs/Apm-commands.md index f88d1382f..275a6dfa4 100644 --- a/docs/Apm-commands.md +++ b/docs/Apm-commands.md @@ -87,3 +87,23 @@ aragon apm grant [addr1 ... addrN] ``` - `addresses`: The addresses being granted the permission to publish to the repo. + +## aragon apm revoke + +Revoke an address the permission to create new versions in your package (defined in `arapp.json`), by interacting directly with the ACL, without performing transaction pathing. + +```sh +aragon apm revoke +``` + +- `entity`: The entity address being revoked the permission to publish to the repo. + +## aragon apm manager + +Set an address to be the permission manager of your package (defined in `arapp.json`), by interacting directly with the ACL, without performing transaction pathing. + +```sh +aragon apm manager +``` + +- `entity`: The entity address being set as permission manager. diff --git a/packages/cli/src/commands/apm_cmds/manager.js b/packages/cli/src/commands/apm_cmds/manager.js new file mode 100644 index 000000000..7499e11ec --- /dev/null +++ b/packages/cli/src/commands/apm_cmds/manager.js @@ -0,0 +1,52 @@ +import { blue } from 'chalk' +import { apmSetPermissionManager } from '@aragon/toolkit' +// +import { ensureWeb3 } from '../../helpers/web3-fallback' + +export const command = 'manager ' +export const describe = + 'Set an entity as the permission manager of this package' + +export const builder = function(yargs) { + return yargs.positional('entity', { + description: 'The address to be the new permission manager ofthe repo', + }) +} + +export const handler = async function({ + // Globals + reporter, + gasPrice, + network, + module, + apm: apmOptions, + // Arguments + entity, +}) { + const web3 = await ensureWeb3(network) + + const progressHandler = (step, data) => { + switch (step) { + case 1: + reporter.info(`Fetching repository`) + break + case 2: + reporter.info( + `Setting permission manager for ${blue(module.appName)} for ${data}` + ) + break + case 3: + reporter.success(`Successful transaction (${blue(data)})`) + break + } + } + + await apmSetPermissionManager( + web3, + module.appName, + apmOptions, + entity, + progressHandler, + { gasPrice: gasPrice || network.gasPrice } + ) +} diff --git a/packages/cli/src/commands/apm_cmds/revoke.js b/packages/cli/src/commands/apm_cmds/revoke.js new file mode 100644 index 000000000..51f5867fb --- /dev/null +++ b/packages/cli/src/commands/apm_cmds/revoke.js @@ -0,0 +1,55 @@ +import { blue } from 'chalk' +import { apmRevokePermission } from '@aragon/toolkit' +// +import { ensureWeb3 } from '../../helpers/web3-fallback' + +export const command = 'revoke entity' +export const describe = + 'Revoke an entity the permission to create new versions in this package' + +export const builder = function(yargs) { + return yargs.positional('entity', { + description: + 'The address being revoked the permission to publish to the repo', + }) +} + +export const handler = async function({ + // Globals + reporter, + gasPrice, + network, + module, + apm: apmOptions, + // Arguments + entity, +}) { + const web3 = await ensureWeb3(network) + + const progressHandler = (step, data) => { + switch (step) { + case 1: + reporter.info(`Fetching repository`) + break + case 2: + reporter.info( + `Revoking permission to publish on ${blue( + module.appName + )} for ${data}` + ) + break + case 3: + reporter.success(`Successful transaction (${blue(data)})`) + break + } + } + + await apmRevokePermission( + web3, + module.appName, + apmOptions, + entity, + progressHandler, + { gasPrice: gasPrice || network.gasPrice } + ) +} diff --git a/packages/cli/src/helpers/ensureDevchain.js b/packages/cli/src/helpers/ensureDevchain.js index 69143d3fc..445c1ceb8 100644 --- a/packages/cli/src/helpers/ensureDevchain.js +++ b/packages/cli/src/helpers/ensureDevchain.js @@ -15,7 +15,7 @@ export const ensureDevchain = async ({ port, logger = noop }) => { try { const { detach } = await startProcess({ cmd: 'node', - args: [binPath, 'devchain', '--port', port], + args: [binPath, 'devchain', '--port', port, '--reset'], readyOutput: 'Devchain running at', execaOpts: { detached: true, diff --git a/packages/toolkit/src/apm/apmRevokePermission.js b/packages/toolkit/src/apm/apmRevokePermission.js new file mode 100644 index 000000000..03e4f9fd4 --- /dev/null +++ b/packages/toolkit/src/apm/apmRevokePermission.js @@ -0,0 +1,46 @@ +import APM from '@aragon/apm' +import ACL from './util/acl' + +export default async ( + web3, + apmRepoName, + apmOptions, + entity, + progressHandler = () => {}, + { gasPrice } +) => { + if (entity.length === 0) { + throw new Error('No addresse provided') + } + + const apm = await APM(web3, apmOptions) + const acl = ACL(web3) + + progressHandler(1) + + const repo = await apm.getRepository(apmRepoName) + if (repo === null) { + throw new Error( + `Repository ${apmRepoName} does not exist and it's registry does not exist` + ) + } + + /* eslint-disable-next-line */ + progressHandler(2, entity) + + // Decode sender + const accounts = await web3.eth.getAccounts() + const from = accounts[0] + + // Build transaction + const transaction = await acl.revoke(repo.options.address, entity, from) + + transaction.from = from + transaction.gasPrice = gasPrice + // the recommended gasLimit is already calculated by the ACL module + + const receipt = await web3.eth.sendTransaction(transaction) + progressHandler(3, receipt.transactionHash) + + return receipt +} diff --git a/packages/toolkit/src/apm/apmSetPermissionManager.js b/packages/toolkit/src/apm/apmSetPermissionManager.js new file mode 100644 index 000000000..623748832 --- /dev/null +++ b/packages/toolkit/src/apm/apmSetPermissionManager.js @@ -0,0 +1,50 @@ +import APM from '@aragon/apm' +import ACL from './util/acl' + +export default async ( + web3, + apmRepoName, + apmOptions, + entity, + progressHandler = () => {}, + { gasPrice } +) => { + if (entity.length === 0) { + throw new Error('No addresse provided') + } + + const apm = await APM(web3, apmOptions) + const acl = ACL(web3) + + progressHandler(1) + + const repo = await apm.getRepository(apmRepoName) + if (repo === null) { + throw new Error( + `Repository ${apmRepoName} does not exist and it's registry does not exist` + ) + } + + /* eslint-disable-next-line */ + progressHandler(2, entity) + + // Decode sender + const accounts = await web3.eth.getAccounts() + const from = accounts[0] + + // Build transaction + const transaction = await acl.setPermissionManager( + entity, + repo.options.address, + from + ) + + transaction.from = from + transaction.gasPrice = gasPrice + // the recommended gasLimit is already calculated by the ACL module + + const receipt = await web3.eth.sendTransaction(transaction) + progressHandler(3, receipt.transactionHash) + + return receipt +} diff --git a/packages/toolkit/src/apm/grantNewVersionsPermission.js b/packages/toolkit/src/apm/grantNewVersionsPermission.js index 086077e4f..7329e0bcd 100644 --- a/packages/toolkit/src/apm/grantNewVersionsPermission.js +++ b/packages/toolkit/src/apm/grantNewVersionsPermission.js @@ -36,7 +36,7 @@ export default async ( const from = accounts[0] // Build transaction - const transaction = await acl.grant(repo.options.address, address) + const transaction = await acl.grant(repo.options.address, address, from) transaction.from = from transaction.gasPrice = gasPrice diff --git a/packages/toolkit/src/apm/index.js b/packages/toolkit/src/apm/index.js index 2543d7a9f..5956cf254 100644 --- a/packages/toolkit/src/apm/index.js +++ b/packages/toolkit/src/apm/index.js @@ -6,3 +6,5 @@ export { default as grantNewVersionsPermission } from './grantNewVersionsPermiss export { default as apmPublishVersion } from './apmPublishVersion' export { default as apmPublishVersionIntent } from './apmPublishVersionIntent' export { default as apmGetFile } from './apmGetFile' +export { default as apmRevokePermission } from './apmRevokePermission' +export { default as apmSetPermissionManager } from './apmSetPermissionManager' diff --git a/packages/toolkit/src/apm/util/acl.js b/packages/toolkit/src/apm/util/acl.js index eea90602f..8becc19b5 100644 --- a/packages/toolkit/src/apm/util/acl.js +++ b/packages/toolkit/src/apm/util/acl.js @@ -21,13 +21,40 @@ export default web3 => { } return { - grant: async (repoAddr, grantee) => { + grant: async (repoAddr, grantee, from) => { const acl = await getACL(repoAddr) const roleId = await getRoleId(repoAddr) - const call = acl.methods.grantPermission(grantee, repoAddr, roleId) - const estimatedGas = call.estimateGas() + const estimatedGas = call.estimateGas({ from }) + + return { + to: acl.options.address, + data: call.encodeABI(), + gas: await getRecommendedGasLimit(web3, estimatedGas), + } + }, + + revoke: async (repoAddr, grantee, from) => { + const acl = await getACL(repoAddr) + + const roleId = await getRoleId(repoAddr) + const call = acl.methods.revokePermission(grantee, repoAddr, roleId) + const estimatedGas = call.estimateGas({ from }) + + return { + to: acl.options.address, + data: call.encodeABI(), + gas: await getRecommendedGasLimit(web3, estimatedGas), + } + }, + + setPermissionManager: async (entity, repoAddr, from) => { + const acl = await getACL(repoAddr) + + const roleId = await getRoleId(repoAddr) + const call = acl.methods.setPermissionManager(entity, repoAddr, roleId) + const estimatedGas = call.estimateGas({ from }) return { to: acl.options.address, diff --git a/packages/toolkit/test/apm/grantNewVersionsPermission.test.js b/packages/toolkit/test/apm/permissions.test.js similarity index 66% rename from packages/toolkit/test/apm/grantNewVersionsPermission.test.js rename to packages/toolkit/test/apm/permissions.test.js index c54db4520..965c66cd2 100644 --- a/packages/toolkit/test/apm/grantNewVersionsPermission.test.js +++ b/packages/toolkit/test/apm/permissions.test.js @@ -2,6 +2,8 @@ import test from 'ava' import sinon from 'sinon' // import grantNewVersionsPermission from '../../src/apm/grantNewVersionsPermission' +import apmRevokePermission from '../../src/apm/apmRevokePermission' +import apmSetPermissionManager from '../../src/apm/apmSetPermissionManager' import { getLocalWeb3, getApmOptions } from '../test-helpers' import { abi as aclAbi } from '@aragon/abis/os/artifacts/ACL' @@ -67,7 +69,7 @@ test.before('setup and make a successful call', async t => { /* Tests */ -test('permissions are not set for any accounts', async t => { +test.serial('permissions are not set for any accounts', async t => { const anyone = accounts[2] const hasPermission = await acl.methods @@ -77,7 +79,7 @@ test('permissions are not set for any accounts', async t => { t.false(hasPermission) }) -test('properly sets permissions for grantees', async t => { +test.serial('properly sets permissions for grantees', async t => { const grantee = grantees[0] const hasPermission = await acl.methods @@ -87,7 +89,7 @@ test('properly sets permissions for grantees', async t => { t.true(hasPermission) }) -test('properly calls the progressHandler', t => { +test.serial('properly calls the progressHandler', t => { const receipt = receipts[0] t.is(progressHandler.callCount, 3) @@ -96,7 +98,7 @@ test('properly calls the progressHandler', t => { t.true(progressHandler.getCall(2).calledWith(3, receipt.transactionHash)) }) -test('Should throw when no grantees are provided', async t => { +test.serial('Should throw when no grantees are provided', async t => { await t.throwsAsync( grantNewVersionsPermission( web3, @@ -108,3 +110,47 @@ test('Should throw when no grantees are provided', async t => { ) ) }) + +test.serial( + 'apmRevokePermission properly revokes permission for entity', + async t => { + const entity = grantees[0] + + await apmRevokePermission( + web3, + apmRepoName, + apmOptions, + entity, + () => {}, + txOptions + ) + + const hasPermission = await acl.methods + .hasPermission(entity, repoAddress, role) + .call() + + t.false(hasPermission) + } +) + +test.serial( + 'apmSetPermissionManager properly sets the permission manager', + async t => { + const entity = grantees[0] + + await apmSetPermissionManager( + web3, + apmRepoName, + apmOptions, + entity, + () => {}, + txOptions + ) + + const permissionManager = await acl.methods + .getPermissionManager(repoAddress, role) + .call() + + t.is(entity, permissionManager) + } +)