diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cee703e8..5482190b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## v.NEXT +**Maintenance** + +* General updates for interacting with ColonyNetwork contracts. + **Enhancements** * All events logged during method calls are now parsed according to their specifications on the `ContractClient`, without specifying event handlers (`@colony/colony-js-adapter-ethers`, `@colony/colony-js-contract-client`, `@colony/colony-js-client`) diff --git a/docs/_API_ColonyClient.md b/docs/_API_ColonyClient.md index d31d5b13c..fedc637ce 100644 --- a/docs/_API_ColonyClient.md +++ b/docs/_API_ColonyClient.md @@ -48,6 +48,19 @@ A promise which resolves to an object containing the following properties: |---|---|---| |address|Address|The colony's Authority contract address| +### `getRecoveryRolesCount.call()` + +Returns the number of recovery roles. + + +**Returns** + +A promise which resolves to an object containing the following properties: + +|Return value|Type|Description| +|---|---|---| +|count|number|Number of users with the recovery role (excluding owner)| + ### `generateSecret.call({ salt, value })` Helper function used to generate the rating secret used in task ratings. Accepts a salt value and a value to hide, and returns the keccak256 hash of both. @@ -191,6 +204,25 @@ A promise which resolves to an object containing the following properties: |---|---|---| |amount|BigNumber|Amount of specified tokens to payout for that task and a role.| +### `getTotalTaskPayout.call({ taskId, token })` + +Given a specific task, and a token address, will return any payout attached to the task in the token specified (for all roles). + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|taskId|number|Integer taskId.| +|token|Token address|Address of the token's contract. `0x0` value indicates Ether.| + +**Returns** + +A promise which resolves to an object containing the following properties: + +|Return value|Type|Description| +|---|---|---| +|amount|BigNumber|Amount of specified tokens to payout for that task.| + ### `getTaskRole.call({ taskId, role })` Every task has three roles associated with it which determine permissions for editing the task, submitting work, and ratings for performance. @@ -340,7 +372,7 @@ A promise which resolves to an object containing the following properties: ## Senders **All senders return an instance of a `ContractResponse`.** Every `send()` method takes an `options` object as the second argument. For a reference please check [here](/colonyjs/docs-contractclient/#senders). -### `createTask.send({ specificationHash, domainId }, options)` +### `createTask.send({ specificationHash, domainId, skillId, dueDate }, options)` Creates a new task by invoking `makeTask` on-chain. @@ -350,6 +382,8 @@ Creates a new task by invoking `makeTask` on-chain. |---|---|---| |specificationHash|IPFS hash|Hashed output of the task's work specification, stored so that it can later be referenced for task ratings or in the event of a dispute.| |domainId|number|Domain in which the task has been created (default value: `1`).| +|skillId|number|The skill associated with the task (optional)| +|dueDate|Date|The due date of the task (optional)| **Returns** @@ -364,38 +398,139 @@ An instance of a `ContractResponse` which will eventually receive the following |PotAdded|object|Contains the data defined in [PotAdded](#events-PotAdded)| |DomainAdded|object|Contains the data defined in [DomainAdded](#events-DomainAdded)| -### `setTaskDomain.send({ taskId, domainId }, options)` +### `enterRecoveryMode.send(options)` -Every task must belong to a single existing Domain. This can only be called by the manager of the task. +Put the colony into recovery mode. Can only be called by user with a recovery role. + + +**Returns** + +An instance of a `ContractResponse` + + + +### `exitRecoveryMode.send({ newVersion }, options)` + +Exit recovery mode. Can be called by anyone if enough whitelist approvals are given. **Arguments** |Argument|Type|Description| |---|---|---| -|taskId|number|Integer taskId.| -|domainId|number|Integer domainId.| +|newVersion|number|Resolver version to upgrade to (>= current version)| **Returns** -An instance of a `ContractResponse` which will eventually receive the following event data: +An instance of a `ContractResponse` -|Event data|Type|Description| + + +### `registerColonyLabel.send({ subnode }, options)` + +Register the colony's ENS label. + +**Arguments** + +|Argument|Type|Description| |---|---|---| -|taskId|number|The task ID.| -|domainId|number|The task's new domain ID.| -|TaskDomainChanged|object|Contains the data defined in [TaskDomainChanged](#events-TaskDomainChanged)| +|subnode|string|The keccak256 hash of the label to register| + +**Returns** + +An instance of a `ContractResponse` + + -### `setTaskRoleUser.send({ taskId, role, user }, options)` +### `setOwnerRole.send({ user }, options)` -Set the user for role `_role` in task `_id`. Only allowed before the task is `finalized`, meaning that the value cannot be changed after the task is complete. This can only be called by the manager of the task. +Set a new colony owner role. There can only be one address assigned to owner role at a time. Whoever calls this function will lose their owner role. Can be called by owner role. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|user|Address|User we want to give an owner role to| + +**Returns** + +An instance of a `ContractResponse` + + + +### `setAdminRole.send({ user }, options)` + +Set a new colony admin role. Can be called by an owner or admin role. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|user|Address|User we want to give an admin role to| + +**Returns** + +An instance of a `ContractResponse` + + + +### `setRecoveryRole.send({ user }, options)` + +Set a new colony recovery role. Can be called by an owner role. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|user|Address|User we want to give a recovery role to| + +**Returns** + +An instance of a `ContractResponse` + + + +### `removeAdminRole.send({ user }, options)` + +Remove a colony admin role. Can only be called by an owner role. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|user|Address|User we want to remove an admin role from| + +**Returns** + +An instance of a `ContractResponse` + + + +### `removeRecoveryRole.send({ user }, options)` + +Remove a colony recovery role. Can only be called by an owner role. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|user|Address|User we want to remove a recovery role from| + +**Returns** + +An instance of a `ContractResponse` + + + +### `setTaskDomain.send({ taskId, domainId }, options)` + +Every task must belong to a single existing Domain. This can only be called by the manager of the task. **Arguments** |Argument|Type|Description| |---|---|---| |taskId|number|Integer taskId.| -|role|Role|MANAGER, EVALUATOR, or WORKER.| -|user|Address|address of the user.| +|domainId|number|Integer domainId.| **Returns** @@ -404,9 +539,8 @@ An instance of a `ContractResponse` which will eventually receive the following |Event data|Type|Description| |---|---|---| |taskId|number|The task ID.| -|role|number|The role that changed for the task.| -|user|Address|The user with the role that changed for the task.| -|TaskRoleUserChanged|object|Contains the data defined in [TaskRoleUserChanged](#events-TaskRoleUserChanged)| +|domainId|number|The task's new domain ID.| +|TaskDomainChanged|object|Contains the data defined in [TaskDomainChanged](#events-TaskDomainChanged)| ### `setTaskSkill.send({ taskId, skillId }, options)` @@ -580,7 +714,7 @@ An instance of a `ContractResponse` ### `addDomain.send({ parentSkillId }, options)` -Adds a domain to the Colony along with the new domain's respective local skill. This can only be called by owners of the colony. +Adds a domain to the Colony along with the new domain's respective local skill (with id `parentSkillId`). This can only be called by owners of the colony. Adding new domains is currently retricted to one level only, i.e. `parentSkillId` has to be the root domain (id: 1). **Arguments** @@ -779,6 +913,28 @@ An instance of a `MultiSigOperation` whose sender will eventually receive the fo |dueDate|Date|The task's new due date.| |TaskDueDateChanged|object|Contains the data defined in [TaskDueDateChanged](#events-TaskDueDateChanged)| +### `setTaskManagerRole.startOperation({ taskId, user })` + +Set the manager role for the address `user` in task `taskId`. Only allowed before the task is `finalized`, meaning that the value cannot be changed after the task is complete. The current manager and the user we want to assign role to both need to sign this transaction. + +**Arguments** + +|Argument|Type|Description| +|---|---|---| +|taskId|number|Integer taskId.| +|user|Address|address of the user.| + +**Returns** + +An instance of a `MultiSigOperation` whose sender will eventually receive the following event data: + +|Event Data|Type|Description| +|---|---|---| +|taskId|number|The task ID.| +|role|number|The role that changed for the task.| +|user|Address|The user with the role that changed for the task.| +|TaskRoleUserChanged|object|Contains the data defined in [TaskRoleUserChanged](#events-TaskRoleUserChanged)| + ### `setTaskEvaluatorPayout.startOperation({ taskId, token, amount })` Sets the payout given to the EVALUATOR role when the task is finalized. diff --git a/docs/_API_ColonyNetworkClient.md b/docs/_API_ColonyNetworkClient.md index da0a0fefa..43efbc14a 100644 --- a/docs/_API_ColonyNetworkClient.md +++ b/docs/_API_ColonyNetworkClient.md @@ -311,8 +311,9 @@ An instance of a `ContractResponse` which will eventually receive the following |Event data|Type|Description| |---|---|---| -|skillId|number|Id of the added skill| -|parentSkillId|number|Id of the parent skill under which the new skill is added| +|skillId|number|| +|parentSkillId|number|| +|SkillAdded|object|Contains the data defined in [SkillAdded](#events-SkillAdded)| ### `setTokenLocking.send({ tokenLockingAddress }, options)` diff --git a/packages/colony-js-client/src/AuthorityClient/index.js b/packages/colony-js-client/src/AuthorityClient/index.js index 8dce088de..8fb3f2137 100644 --- a/packages/colony-js-client/src/AuthorityClient/index.js +++ b/packages/colony-js-client/src/AuthorityClient/index.js @@ -65,7 +65,10 @@ export default class AuthorityClient extends ContractClient { output: [['hasRole', 'boolean']], }); this.addSender('setUserRole', { - input: [user, role, ['enabled', 'boolean', true]], + input: [user, role, ['enabled', 'boolean']], + defaultValues: { + enabled: true, + }, }); } } diff --git a/packages/colony-js-client/src/ColonyClient/callers/GetTask.js b/packages/colony-js-client/src/ColonyClient/callers/GetTask.js index db10bbe24..1ce69ee2c 100644 --- a/packages/colony-js-client/src/ColonyClient/callers/GetTask.js +++ b/packages/colony-js-client/src/ColonyClient/callers/GetTask.js @@ -13,7 +13,7 @@ type CallResult = [ string, string, boolean, - boolean, + number, number, number, number, @@ -36,13 +36,13 @@ export default class GetTask extends ContractClient.Caller< output: [ ['specificationHash', 'ipfsHash'], ['deliverableHash', 'ipfsHash'], - ['finalized', 'boolean'], - ['cancelled', 'boolean'], + ['status', 'taskStatus'], ['dueDate', 'date'], ['payoutsWeCannotMake', 'number'], ['potId', 'number'], - ['deliverableDate', 'date'], + ['completionDate', 'date'], ['domainId', 'number'], + ['skillId', 'number'], ], ...params, }); diff --git a/packages/colony-js-client/src/ColonyClient/index.js b/packages/colony-js-client/src/ColonyClient/index.js index f02f4d5fc..88c9c7627 100644 --- a/packages/colony-js-client/src/ColonyClient/index.js +++ b/packages/colony-js-client/src/ColonyClient/index.js @@ -16,10 +16,12 @@ import GetTask from './callers/GetTask'; import CreateTask from './senders/CreateTask'; import { ROLES, + ADMIN_ROLE, WORKER_ROLE, EVALUATOR_ROLE, MANAGER_ROLE, DEFAULT_DOMAIN_ID, + TASK_STATUSES, } from '../constants'; type Address = string; @@ -27,6 +29,7 @@ type TokenAddress = string; type HexString = string; type Role = $Keys; type IPFSHash = string; +type TaskStatus = $Keys; type DomainAdded = ContractClient.Event<{ domainId: number, // The ID of the domain that was added. @@ -45,6 +48,9 @@ type TaskBriefChanged = ContractClient.Event<{ taskId: number, // The task ID. specificationHash: string, // The IPFS hash of the task's new specification. }>; +type TaskCompleted = ContractClient.Event<{ + taskId: number, // The task ID. +}>; type TaskDueDateChanged = ContractClient.Event<{ taskId: number, // The task ID. dueDate: Date, // The task's new due date. @@ -67,12 +73,24 @@ type TaskWorkerPayoutChanged = ContractClient.Event<{ token: TokenAddress, // The token address (0x indicates ether). amount: number, // The token amount. }>; +type TaskDeliverableSubmitted = ContractClient.Event<{ + taskId: number, // The task ID. + deliverableHash: IPFSHash, // The IPFS hash of the deliverable. +}>; +type TaskWorkRatingRevealed = ContractClient.Event<{ + taskId: number, // The task ID. + role: Role, // The role of the work rating. + rating: number, // The rating value. +}>; type TaskFinalized = ContractClient.Event<{ taskId: number, // The task ID of the task that was finalized. }>; type TaskCanceled = ContractClient.Event<{ taskId: number, // The task ID of the task that was canceled. }>; +type RewardPayoutCycleStarted = ContractClient.Event<{ + payoutId: number, // The payout ID logged when a new reward payout cycle has started. +}>; export default class ColonyClient extends ContractClient { networkClient: ColonyNetworkClient; @@ -89,6 +107,26 @@ export default class ColonyClient extends ContractClient { }, ColonyClient, >; + /* + Gets the Colony contract version. This starts from 1 and is incremented with every deployed contract change. + */ + getVersion: ColonyClient.Caller< + {}, + { + version: number, // The version number. + }, + ColonyClient, + >; + /* + Returns the number of recovery roles. + */ + getRecoveryRolesCount: ColonyClient.Caller< + {}, + { + count: number, // Number of users with the recovery role (excluding owner) + }, + ColonyClient, + >; /* Helper function used to generate the rating secret used in task ratings. Accepts a salt value and a value to hide, and returns the keccak256 hash of both. */ @@ -163,17 +201,16 @@ export default class ColonyClient extends ContractClient { getTask: ColonyClient.Caller< { taskId: number }, { - cancelled: boolean, // Boolean flag denoting whether the task is cancelled. - deliverableDate: ?Date, // Date when the deliverable is due. + completionDate: ?Date, // Date when the task was completed. deliverableHash: ?IPFSHash, // Unique hash of the deliverable content. domainId: number, // Integer Domain ID the task belongs to. dueDate: ?Date, // When the task is due. - finalized: boolean, // Boolean flag denoting whether the task is finalized. id: number, // Integer task ID. payoutsWeCannotMake: ?number, // Number of payouts that cannot be completed with the current task funding. potId: ?number, // Integer ID of funding pot for the task. skillId: number, // Integer Skill ID the task is assigned to. specificationHash: IPFSHash, // Unique hash of the specification content. + status: TaskStatus, // The task status (ACTIVE, CANCELLED or FINALIZED). }, ColonyClient, >; @@ -191,6 +228,19 @@ export default class ColonyClient extends ContractClient { }, ColonyClient, >; + /* + Given a specific task, and a token address, will return any payout attached to the task in the token specified (for all roles). + */ + getTotalTaskPayout: ColonyClient.Caller< + { + taskId: number, // Integer taskId. + token: TokenAddress, // Address of the token's contract. `0x0` value indicates Ether. + }, + { + amount: BigNumber, // Amount of specified tokens to payout for that task. + }, + ColonyClient, + >; /* Every task has three roles associated with it which determine permissions for editing the task, submitting work, and ratings for performance. */ @@ -300,11 +350,23 @@ export default class ColonyClient extends ContractClient { createTask: ColonyClient.Sender< { specificationHash: IPFSHash, // Hashed output of the task's work specification, stored so that it can later be referenced for task ratings or in the event of a dispute. - domainId: number, // Domain in which the task has been created (default value: `1`). + domainId?: number, // Domain in which the task has been created (default value: `1`). + skillId?: number, // The skill associated with the task (optional) + dueDate?: Date, // The due date of the task (optional) }, { TaskAdded: TaskAdded, PotAdded: PotAdded, DomainAdded: DomainAdded }, ColonyClient, >; + /* + Mark a task as complete after the due date has passed. This allows the task to be rated and finalized (and funds recovered) even in the presence of a worker who has disappeared. Note that if the due date was not set, then this function will throw. + */ + completeTask: ColonyClient.Sender< + { + taskId: number, // The task ID. + }, + { TaskCompleted: TaskCompleted }, + ColonyClient, + >; /* The task brief, or specification, is a description of the tasks work specification. The description is hashed and stored with the task for future reference in ratings or in the event of a dispute. */ @@ -316,6 +378,80 @@ export default class ColonyClient extends ContractClient { { TaskBriefChanged: TaskBriefChanged }, ColonyClient, >; + /* + Put the colony into recovery mode. Can only be called by user with a recovery role. + */ + enterRecoveryMode: ColonyClient.Sender<{}, {}, ColonyClient>; + /* + Exit recovery mode. Can be called by anyone if enough whitelist approvals are given. + */ + exitRecoveryMode: ColonyClient.Sender< + { + newVersion: number, // Resolver version to upgrade to (>= current version) + }, + {}, + ColonyClient, + >; + /* + Register the colony's ENS label. + */ + registerColonyLabel: ColonyClient.Sender< + { + subnode: string, // The keccak256 hash of the label to register + }, + {}, + ColonyClient, + >; + /* + Set a new colony owner role. There can only be one address assigned to owner role at a time. Whoever calls this function will lose their owner role. Can be called by owner role. + */ + setOwnerRole: ColonyClient.Sender< + { + user: Address, // User we want to give an owner role to + }, + {}, + ColonyClient, + >; + /* + Set a new colony admin role. Can be called by an owner or admin role. + */ + setAdminRole: ColonyClient.Sender< + { + user: Address, // User we want to give an admin role to + }, + {}, + ColonyClient, + >; + /* + Set a new colony recovery role. Can be called by an owner role. + */ + setRecoveryRole: ColonyClient.Sender< + { + user: Address, // User we want to give a recovery role to + }, + {}, + ColonyClient, + >; + /* + Remove a colony admin role. Can only be called by an owner role. + */ + removeAdminRole: ColonyClient.Sender< + { + user: Address, // User we want to remove an admin role from + }, + {}, + ColonyClient, + >; + /* + Remove a colony recovery role. Can only be called by an owner role. + */ + removeRecoveryRole: ColonyClient.Sender< + { + user: Address, // User we want to remove a recovery role from + }, + {}, + ColonyClient, + >; /* Every task must belong to a single existing Domain. This can only be called by the manager of the task. */ @@ -339,12 +475,11 @@ export default class ColonyClient extends ContractClient { ColonyClient, >; /* - Set the user for role `_role` in task `_id`. Only allowed before the task is `finalized`, meaning that the value cannot be changed after the task is complete. This can only be called by the manager of the task. + Set the manager role for the address `user` in task `taskId`. Only allowed before the task is `finalized`, meaning that the value cannot be changed after the task is complete. The current manager and the user we want to assign role to both need to sign this transaction. */ - setTaskRoleUser: ColonyClient.Sender< + setTaskManagerRole: ColonyClient.MultisigSender< { taskId: number, // Integer taskId. - role: Role, // MANAGER, EVALUATOR, or WORKER. user: Address, // address of the user. }, { TaskRoleUserChanged: TaskRoleUserChanged }, @@ -361,6 +496,22 @@ export default class ColonyClient extends ContractClient { { TaskSkillChanged: TaskSkillChanged }, ColonyClient, >; + /* + Set the payouts for the task manager, evaluator and worker in one transaction, for a specific token address. This can only be called by the task manager, and only if the evaluator and worker roles are either unassigned or the same as the manager. + */ + setAllTaskPayouts: ColonyClient.Sender< + { + taskId: number, // The task ID. + token: Address, // Address of the token, `0x0` value indicates Ether. + managerAmount: BigNumber, // Payout amount for the manager. + evaluatorAmount: BigNumber, // Payout amount for the evaluator. + workerAmount: BigNumber, // Payout amount for the worker. + }, + { + TaskWorkerPayoutChanged: TaskWorkerPayoutChanged, + }, + ColonyClient, + >; /* Sets the payout given to the EVALUATOR role when the task is finalized. */ @@ -403,9 +554,12 @@ export default class ColonyClient extends ContractClient { submitTaskDeliverable: ColonyClient.Sender< { taskId: number, // Integer taskId. - deliverableHash: IPFSHash, // Hash of the work performed. + deliverableHash: IPFSHash, // IPFS hash of the work performed. + }, + { + TaskCompleted: TaskCompleted, + TaskDeliverableSubmitted: TaskDeliverableSubmitted, }, - {}, ColonyClient, >; /* @@ -420,6 +574,21 @@ export default class ColonyClient extends ContractClient { {}, ColonyClient, >; + /* + Submit the task deliverable for the worker and the rating for the manager. + */ + submitTaskDeliverableAndRating: ColonyClient.Sender< + { + taskId: number, // The task ID. + deliverableHash: IPFSHash, // IPFS hash of the work performed. + secret: HexString, // hidden work rating, generated as the output of `generateSecret(_salt, _rating)`, where `_rating` is a score from 1-3. + }, + { + TaskCompleted: TaskCompleted, + TaskDeliverableSubmitted: TaskDeliverableSubmitted, + }, + ColonyClient, + >; /* Reveals a previously submitted work rating, by proving that the `_rating` and `_salt` values result in the same `secret` submitted during the rating submission period. This is checked on-chain using the `generateSecret` function. */ @@ -430,7 +599,7 @@ export default class ColonyClient extends ContractClient { rating: number, // Rating scored (1-3). salt: string, // `_salt` value to be used in `generateSecret`. A correct value will result in the same `secret` submitted during the work rating submission period. }, - {}, + { TaskWorkRatingRevealed: TaskWorkRatingRevealed }, ColonyClient, >; /* @@ -476,7 +645,7 @@ export default class ColonyClient extends ContractClient { ColonyClient, >; /* - Adds a domain to the Colony along with the new domain's respective local skill. This can only be called by owners of the colony. + Adds a domain to the Colony along with the new domain's respective local skill (with id `parentSkillId`). This can only be called by owners of the colony. Adding new domains is currently retricted to one level only, i.e. `parentSkillId` has to be the root domain (id: 1). */ addDomain: ColonyClient.Sender< { @@ -555,7 +724,7 @@ export default class ColonyClient extends ContractClient { { token: TokenAddress, // Address of token used for reward payout (`0x0` value indicates Ether). }, - {}, + { RewardPayoutCycleStarted: RewardPayoutCycleStarted }, ColonyClient, >; /* @@ -569,7 +738,56 @@ export default class ColonyClient extends ContractClient { ColonyClient, >; - events: {}; + /* + Update the value of an arbitrary storage variable. This can only be called by a user with the recovery role. Certain critical variables are protected from editing in this function. + */ + setStorageSlotRecovery: ColonyClient.Sender< + { + slot: number, // Address of storage slot to be updated. + value: HexString, // Word of data to be set. + }, + {}, + ColonyClient, + >; + /* + Set the colony token. Secured function to authorised members. Note that if the `mint` functionality is to be controlled through the colony, control has to be transferred to the colony after this call. + */ + setToken: ColonyClient.Sender< + { + token: Address, // Address of the token contract to use. + }, + {}, + ColonyClient, + >; + /* + Upgrades the colony to a new Colony contract version. Downgrades are not allowed (i.e. `newVersion` should be higher than the currect colony version). + */ + upgrade: ColonyClient.Sender< + { + newVersion: number, + }, + {}, + ColonyClient, + >; + + events: { + DomainAdded: DomainAdded, + PotAdded: PotAdded, + RewardPayoutCycleStarted: RewardPayoutCycleStarted, + SkillAdded: SkillAdded, + TaskAdded: TaskAdded, + TaskBriefChanged: TaskBriefChanged, + TaskCanceled: TaskCanceled, + TaskCompleted: TaskCompleted, + TaskDeliverableSubmitted: TaskDeliverableSubmitted, + TaskDomainChanged: TaskDomainChanged, + TaskDueDateChanged: TaskDueDateChanged, + TaskFinalized: TaskFinalized, + TaskRoleUserChanged: TaskRoleUserChanged, + TaskSkillChanged: TaskSkillChanged, + TaskWorkerPayoutChanged: TaskWorkerPayoutChanged, + TaskWorkRatingRevealed: TaskWorkRatingRevealed, + }; static get defaultQuery() { return { @@ -650,6 +868,11 @@ export default class ColonyClient extends ContractClient { [['role', 'role'], ['token', 'tokenAddress']], [['amount', 'bigNumber']], ); + makeTaskCaller( + 'getTotalTaskPayout', + [['token', 'tokenAddress']], + [['amount', 'bigNumber']], + ); makeTaskCaller( 'getTaskRole', [['role', 'role']], @@ -671,6 +894,14 @@ export default class ColonyClient extends ContractClient { functionName: 'authority', output: [['address', 'address']], }); + this.addCaller('getVersion', { + functionName: 'version', + output: [['version', 'number']], + }); + this.addCaller('getRecoveryRolesCount', { + functionName: 'numRecoveryRoles', + output: [['count', 'number']], + }); this.addCaller('generateSecret', { input: [['salt', 'string'], ['value', 'number']], output: [['secret', 'hexString']], @@ -725,6 +956,8 @@ export default class ColonyClient extends ContractClient { this.addEvent('DomainAdded', [['domainId', 'number']]); this.addEvent('PotAdded', [['potId', 'number']]); this.addEvent('TaskAdded', [['taskId', 'number']]); + this.addEvent('TaskCompleted', [['taskId', 'number']]); + this.addEvent('RewardPayoutCycleStarted', [['payoutId', 'number']]); this.addEvent('TaskBriefChanged', [ ['taskId', 'number'], ['specificationHash', 'ipfsHash'], @@ -744,7 +977,7 @@ export default class ColonyClient extends ContractClient { this.addEvent('TaskRoleUserChanged', [ ['taskId', 'number'], ['role', 'number'], - ['user', 'address'], + ['user', 'tokenAddress'], // XXX because 0x0 is valid ]); this.addEvent('TaskWorkerPayoutChanged', [ ['taskId', 'number'], @@ -791,11 +1024,21 @@ export default class ColonyClient extends ContractClient { client: this, name: 'createTask', functionName: 'makeTask', - input: [['specificationHash', 'ipfsHash'], ['domainId', 'number']], + input: [ + ['specificationHash', 'ipfsHash'], + ['domainId', 'number'], + ['skillId', 'number'], + ['dueDate', 'date'], + ], defaultValues: { domainId: DEFAULT_DOMAIN_ID, + skillId: 0, + dueDate: new Date(0), }, }); + this.addSender('completeTask', { + input: [['taskId', 'number']], + }); this.addSender('finalizeTask', { input: [['taskId', 'number']], }); @@ -827,9 +1070,6 @@ export default class ColonyClient extends ContractClient { this.addSender('setTaskDomain', { input: [['taskId', 'number'], ['domainId', 'number']], }); - this.addSender('setTaskRoleUser', { - input: [['taskId', 'number'], ['role', 'role'], ['user', 'address']], - }); this.addSender('setTaskManagerPayout', { input: [ ['taskId', 'number'], @@ -837,12 +1077,34 @@ export default class ColonyClient extends ContractClient { ['amount', 'bigNumber'], ], }); + this.addSender('setAllTaskPayouts', { + input: [ + ['taskId', 'number'], + ['token', 'tokenAddress'], + ['managerAmount', 'bigNumber'], + ['evaluatorAmount', 'bigNumber'], + ['workerAmount', 'bigNumber'], + ], + }); + this.addSender('setStorageSlotRecovery', { + input: [['slot', 'number'], ['value', 'hexString']], + }); + this.addSender('setToken', { + input: [['token', 'tokenAddress']], + }); this.addSender('setTaskSkill', { input: [['taskId', 'number'], ['skillId', 'number']], }); this.addSender('submitTaskDeliverable', { input: [['taskId', 'number'], ['deliverableHash', 'ipfsHash']], }); + this.addSender('submitTaskDeliverableAndRating', { + input: [ + ['taskId', 'number'], + ['deliverableHash', 'ipfsHash'], + ['secret', 'hexString'], + ], + }); this.addSender('startNextRewardPayout', { input: [['token', 'tokenAddress']], }); @@ -852,8 +1114,40 @@ export default class ColonyClient extends ContractClient { this.addSender('submitTaskWorkRating', { input: [['taskId', 'number'], ['role', 'role'], ['secret', 'hexString']], }); + this.addSender('registerColonyLabel', { + input: [['subnode', 'string']], + }); + this.addSender('exitRecoveryMode', { + input: [[]], + }); + this.addSender('approveExitRecovery', {}); + this.addSender('enterRecoveryMode', {}); + this.addSender('setOwnerRole', { + input: [['user', 'address']], + }); + this.addSender('setAdminRole', { + input: [['user', 'address']], + }); + this.addSender('setRecoveryRole', { + input: [['user', 'address']], + }); + this.addSender('removeAdminRole', { + input: [['user', 'address']], + }); + this.addSender('removeRecoveryRole', { + input: [['user', 'address']], + }); + this.addSender('upgrade', { + input: [['newVersion', 'number']], + }); + + // Remove duplicate/invalid signees and normalise lowercase + const filterRequiredSignees = (signees: Array
) => + [...new Set(signees)] + .filter(isValidAddress) + .map(signee => signee.toLowerCase()); - // Multisig Senders + // Task change MultisigSenders const makeExecuteTaskChange = ( name: string, input: Array<*>, @@ -865,7 +1159,7 @@ export default class ColonyClient extends ContractClient { const taskRoles = await Promise.all( roles.map(role => this.getTaskRole.call({ taskId, role })), ); - return taskRoles.map(({ address }) => address).filter(isValidAddress); + return filterRequiredSignees(taskRoles.map(({ address }) => address)); }, multisigFunctionName: 'executeTaskChange', nonceFunctionName: 'getTaskChangeNonce', @@ -891,5 +1185,68 @@ export default class ColonyClient extends ContractClient { [['token', 'tokenAddress'], ['amount', 'bigNumber']], [MANAGER_ROLE, EVALUATOR_ROLE], ); + makeExecuteTaskChange( + 'removeTaskWorkerRole', + [], + [MANAGER_ROLE, WORKER_ROLE], + ); + makeExecuteTaskChange( + 'removeTaskEvaluatorRole', + [], + [MANAGER_ROLE, EVALUATOR_ROLE], + ); + + // Task role change MultisigSenders + const makeExecuteTaskRoleChange = ( + name: string, + getRequiredSignees: (args: InputArgs) => Promise, + ) => + this.addMultisigSender(name, { + input: [['taskId', 'number'], ['user', 'address']], + getRequiredSignees: async (args: InputArgs) => { + const { taskId, user } = args; + + // The manager's sig is required for all role change operations + const { address: manager } = await this.getTaskRole.call({ + taskId, + role: MANAGER_ROLE, + }); + + const requiredSignees = await getRequiredSignees(args); + const signees = [manager, user].concat(requiredSignees); + + return filterRequiredSignees(signees); + }, + multisigFunctionName: 'executeTaskRoleAssignment', + nonceFunctionName: 'getTaskChangeNonce', + nonceInput: [['taskId', 'number']], + }); + makeExecuteTaskRoleChange('setTaskManagerRole', async ({ user }) => { + const isAdmin = await this.authority.hasUserRole.call({ + user, + role: ADMIN_ROLE, + }); + if (!isAdmin) + throw new Error('Unable to set task role; user must be an admin'); + return null; + }); + makeExecuteTaskRoleChange('setTaskEvaluatorRole', async ({ taskId }) => { + const { address } = await this.getTaskRole.call({ + taskId, + role: EVALUATOR_ROLE, + }); + if (isValidAddress(address)) + throw new Error('Unable to set task role; evaluator is already set'); + return null; + }); + makeExecuteTaskRoleChange('setTaskWorkerRole', async ({ taskId }) => { + const { address } = await this.getTaskRole.call({ + taskId, + role: WORKER_ROLE, + }); + if (isValidAddress(address)) + throw new Error('Unable to set task role; worker is already set'); + return null; + }); } } diff --git a/packages/colony-js-client/src/ColonyClient/senders/CreateTask.js b/packages/colony-js-client/src/ColonyClient/senders/CreateTask.js index 381ebb2b6..117e1c48b 100644 --- a/packages/colony-js-client/src/ColonyClient/senders/CreateTask.js +++ b/packages/colony-js-client/src/ColonyClient/senders/CreateTask.js @@ -7,6 +7,8 @@ import type ColonyClient from '../index'; type InputValues = { specificationHash: string, domainId: number, + skillId: number, + dueDate: Date, }; type OutputValues = { diff --git a/packages/colony-js-client/src/ColonyNetworkClient/index.js b/packages/colony-js-client/src/ColonyNetworkClient/index.js index 95ead0983..7bae896c0 100644 --- a/packages/colony-js-client/src/ColonyNetworkClient/index.js +++ b/packages/colony-js-client/src/ColonyNetworkClient/index.js @@ -179,10 +179,7 @@ export default class ColonyNetworkClient extends ContractClient { parentSkillId: number, // The skill under which the new skill will be added globalSkill: boolean, // Whether the new skill is global }, - { - skillId: number, // Id of the added skill - parentSkillId: number, // Id of the parent skill under which the new skill is added - }, + { SkillAdded: SkillAdded }, ColonyNetworkClient, >; /* diff --git a/packages/colony-js-client/src/constants.js b/packages/colony-js-client/src/constants.js index f693e98f2..deefcf9d0 100644 --- a/packages/colony-js-client/src/constants.js +++ b/packages/colony-js-client/src/constants.js @@ -20,4 +20,14 @@ export const AUTHORITY_ROLES = { [ADMIN_ROLE]: 1, }; +export const ACTIVE_TASK_STATUS = 'ACTIVE'; +export const CANCELLED_TASK_STATUS = 'CANCELLED'; +export const FINALIZED_TASK_STATUS = 'FINALIZED'; + +export const TASK_STATUSES = { + [ACTIVE_TASK_STATUS]: 0, + [CANCELLED_TASK_STATUS]: 1, + [FINALIZED_TASK_STATUS]: 2, +}; + export const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000'; diff --git a/packages/colony-js-client/src/index.js b/packages/colony-js-client/src/index.js index 9542e7860..322b7c702 100644 --- a/packages/colony-js-client/src/index.js +++ b/packages/colony-js-client/src/index.js @@ -13,6 +13,10 @@ export { OWNER_ROLE, ROLES, WORKER_ROLE, + ACTIVE_TASK_STATUS, + CANCELLED_TASK_STATUS, + FINALIZED_TASK_STATUS, + TASK_STATUSES, } from './constants'; export default ColonyNetworkClient; diff --git a/packages/colony-js-client/src/paramTypes.js b/packages/colony-js-client/src/paramTypes.js index 378011def..d6f35175a 100644 --- a/packages/colony-js-client/src/paramTypes.js +++ b/packages/colony-js-client/src/paramTypes.js @@ -4,14 +4,14 @@ import { isBigNumber } from '@colony/colony-js-utils'; import { isHexStrict, hexToNumber } from 'web3-utils'; import { addParamType } from '@colony/colony-js-contract-client'; -import { ROLES, AUTHORITY_ROLES } from './constants'; +import { ROLES, AUTHORITY_ROLES, TASK_STATUSES } from './constants'; -const roleType = (roles: { [roleName: string]: number }) => ({ +const dictType = (dict: { [key: string]: number }) => ({ validate(value: any) { - return Object.hasOwnProperty.call(roles, value); + return Object.hasOwnProperty.call(dict, value); }, - convertInput(value: $Keys) { - return roles[value]; + convertInput(value: $Keys) { + return dict[value]; }, convertOutput(value: any) { let converted; @@ -22,10 +22,12 @@ const roleType = (roles: { [roleName: string]: number }) => ({ } else { converted = value; } - return Object.keys(roles).find(name => roles[name] === converted) || null; + return Object.keys(dict).find(name => dict[name] === converted) || null; }, }); -addParamType('role', roleType(ROLES)); +addParamType('taskStatus', dictType(TASK_STATUSES)); -addParamType('authorityRole', roleType(AUTHORITY_ROLES)); +addParamType('role', dictType(ROLES)); + +addParamType('authorityRole', dictType(AUTHORITY_ROLES)); diff --git a/packages/colony-js-contract-client/src/__tests__/MultisigOperation.js b/packages/colony-js-contract-client/src/__tests__/MultisigOperation.js index e64f72252..23bfbb219 100644 --- a/packages/colony-js-contract-client/src/__tests__/MultisigOperation.js +++ b/packages/colony-js-contract-client/src/__tests__/MultisigOperation.js @@ -197,7 +197,7 @@ describe('MultisigOperation', () => { expect(assert).toHaveBeenCalledWith( true, - expect.stringContaining('Missing signatures (from addresses'), + expect.stringContaining('Missing signatures (from address'), ); expect(valid).toBe(true); @@ -217,7 +217,7 @@ describe('MultisigOperation', () => { expect(assert).toHaveBeenCalledWith( false, - `Missing signatures (from addresses ${addressThree})`, + `Missing signatures (from address ${addressThree})`, ); }); @@ -423,7 +423,10 @@ describe('MultisigOperation', () => { op._addSignature(signature, address); - expect(op._signers).toEqual({ [address]: { ...signature, ...mode } }); + expect(op._signers).toEqual({ + // The address should have been converted to lowercase + [address.toLowerCase()]: { ...signature, ...mode }, + }); expect(op._findSignatureMode).toHaveBeenCalled(); }); diff --git a/packages/colony-js-contract-client/src/__tests__/paramTypes.js b/packages/colony-js-contract-client/src/__tests__/paramTypes.js index abc8efc86..1a6ca0319 100644 --- a/packages/colony-js-contract-client/src/__tests__/paramTypes.js +++ b/packages/colony-js-contract-client/src/__tests__/paramTypes.js @@ -117,7 +117,7 @@ describe('Parameter types', () => { // Validation expect(validateValueType(date, 'date')).toBe(true); - expect(validateValueType(new Date('not a valid date'), 'date')).toBe(false); + expect(validateValueType(new Date(0), 'date')).toBe(true); expect(validateValueType(0, 'date')).toBe(false); expect(validateValueType(null, 'date')).toBe(false); diff --git a/packages/colony-js-contract-client/src/classes/MultisigOperation.js b/packages/colony-js-contract-client/src/classes/MultisigOperation.js index 28d92790d..c325a5e4c 100644 --- a/packages/colony-js-contract-client/src/classes/MultisigOperation.js +++ b/packages/colony-js-contract-client/src/classes/MultisigOperation.js @@ -229,9 +229,12 @@ export default class MultisigOperation< * this operation). */ _validateRequiredSignees() { + const missing = this.missingSignees; defaultAssert( - this.missingSignees.length === 0, - `Missing signatures (from addresses ${this.missingSignees.join(', ')})`, + missing.length === 0, + `Missing signatures (from address${ + missing.length > 1 ? 'es' : '' + } ${missing.join(', ')})`, ); return true; } @@ -251,10 +254,11 @@ export default class MultisigOperation< * add the address/signature to the signers. */ _addSignature(signature: Signature, address: string) { - const mode = this._findSignatureMode(signature, address); + const normalisedAddress = address.toLowerCase(); + const mode = this._findSignatureMode(signature, normalisedAddress); this._signers = Object.assign({}, this._signers, { - [address]: { + [normalisedAddress]: { mode, ...signature, }, diff --git a/packages/colony-js-contract-client/src/flowtypes/params.js b/packages/colony-js-contract-client/src/flowtypes/params.js index ef88a4c1c..69f721bd0 100644 --- a/packages/colony-js-contract-client/src/flowtypes/params.js +++ b/packages/colony-js-contract-client/src/flowtypes/params.js @@ -11,7 +11,7 @@ export type ParamTypes = | 'tokenAddress' | 'string'; -// [param name, param type, default value (optional)] +// [param name, param type] export type Param = [string, ParamTypes]; export type Params = Array; diff --git a/packages/colony-js-contract-client/src/modules/paramTypes.js b/packages/colony-js-contract-client/src/modules/paramTypes.js index 61dd8e29f..ae0b74ad2 100644 --- a/packages/colony-js-contract-client/src/modules/paramTypes.js +++ b/packages/colony-js-contract-client/src/modules/paramTypes.js @@ -57,7 +57,8 @@ const PARAM_TYPE_MAP: { }, date: { validate(value: any) { - return value instanceof Date && !!value.valueOf(); + // XXX This allows dates initialised without a value (or with `0` value) + return value instanceof Date; }, convertOutput(value: any) { const converted = parseInt(