Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Feature/154 move ratings to three point grading scale #161

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 1 addition & 3 deletions contracts/ColonyStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ contract ColonyStorage is DSAuth {
uint256[] skills;

// TODO switch this mapping to a uint8 when all role instances are uint8-s specifically ColonyFunding source
mapping (uint256 => Role) roles;
mapping (uint256 => Role) roles;
// Maps a token to the sum of all payouts of it for this task
mapping (address => uint256) totalPayouts;
// Maps task role ids (0,1,2..) to a token amount to be paid on task completion
Expand All @@ -94,8 +94,6 @@ contract ColonyStorage is DSAuth {
struct Role {
// Address of the user for the given role
address user;
// Has the user work been rated
bool rated;
// Rating the user received
uint8 rating;
}
Expand Down
93 changes: 52 additions & 41 deletions contracts/ColonyTask.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,11 @@ contract ColonyTask is ColonyStorage, DSMath {

modifier taskWorkRatingsClosed(uint256 _id) {
uint taskCompletionTime = tasks[_id].deliverableTimestamp != 0 ? tasks[_id].deliverableTimestamp : tasks[_id].dueDate;
require(sub(now, taskCompletionTime) > add(RATING_COMMIT_TIMEOUT, RATING_REVEAL_TIMEOUT)); // More than 10 days from work submission have passed
_;
}

modifier taskWorkRatingsAssigned(uint256 _id) {
require(tasks[_id].roles[WORKER].rated);
require(tasks[_id].roles[MANAGER].rated);
// Work has been rated or more than 10 days from work submission have passed
require(
tasks[_id].roles[MANAGER].rating != 0 && tasks[_id].roles[WORKER].rating != 0 ||
sub(now, taskCompletionTime) > add(RATING_COMMIT_TIMEOUT, RATING_REVEAL_TIMEOUT)
);
_;
}

Expand All @@ -123,7 +121,6 @@ contract ColonyTask is ColonyStorage, DSMath {
tasks[taskCount] = task;
tasks[taskCount].roles[MANAGER] = Role({
user: msg.sender,
rated: false,
rating: 0
});

Expand Down Expand Up @@ -183,33 +180,12 @@ contract ColonyTask is ColonyStorage, DSMath {
{
bytes32 ratingSecret = generateSecret(_salt, _rating);
require(ratingSecret == taskWorkRatings[_id].secret[_role]);
require(_rating >= 1 && _rating <= 3);

Role storage role = tasks[_id].roles[_role];
role.rated = true;
role.rating = _rating;
}

// In the event of a user not committing or revealing within the 10 day rating window,
// their rating of their counterpart is assumed to be the highest possible
// and their own rating is decreased by 5 (e.g. 0.5 points)
function assignWorkRating(uint256 _id) public
taskWorkRatingsClosed(_id)
{
Role storage managerRole = tasks[_id].roles[MANAGER];
Role storage workerRole = tasks[_id].roles[WORKER];

if (!workerRole.rated) {
workerRole.rated = true;
workerRole.rating = 50;
}

if (!managerRole.rated) {
managerRole.rated = true;
managerRole.rating = 50;
workerRole.rating = (workerRole.rating > 5) ? (workerRole.rating - 5) : 0;
}
}

function generateSecret(bytes32 _salt, uint256 _value) public pure returns (bytes32) {
return keccak256(_salt, _value);
}
Expand All @@ -230,7 +206,6 @@ contract ColonyTask is ColonyStorage, DSMath {
{
tasks[_id].roles[_role] = Role({
user: _user,
rated: false,
rating: 0
});
}
Expand Down Expand Up @@ -284,29 +259,65 @@ contract ColonyTask is ColonyStorage, DSMath {
function finalizeTask(uint256 _id) public
auth
taskExists(_id)
taskWorkRatingsAssigned(_id)
taskWorkRatingsClosed(_id)
taskNotFinalized(_id)
{
Task storage task = tasks[_id];
IColonyNetwork colonyNetworkContract = IColonyNetwork(colonyNetworkAddress);

int8[3] memory ratingMultiplicator = [-10, 10, 15];

for (uint8 roleId = 0; roleId <= 2; roleId++) {
uint payout = task.payouts[roleId][token];
Role storage role = task.roles[roleId];

uint8 rating = (roleId == EVALUATOR) ? 50 : role.rating;
int divider = (roleId == WORKER) ? 30 : 50;
uint8 submittedRating = role.rating;

// Evaluator does not get rated by other users, so we always assign 2
if (roleId == EVALUATOR) {
submittedRating = 2;
role.rating = 2;
} else if (role.rating == 0) {
// If submitted rating was 0, we assign the highest possible rating.
submittedRating = 0;
role.rating = 3;
}

int reputation = SafeMath.mulInt(int(payout), (int(rating)*2 - 50)) / divider;
int reputation = SafeMath.mulInt(int(payout), (int(ratingMultiplicator[role.rating - 1]))) / 10;

// // Give reputation according to role's rating
colonyNetworkContract.appendReputationUpdateLog(role.user, reputation, task.domains[0]);

if (roleId == WORKER) {
colonyNetworkContract.appendReputationUpdateLog(role.user, reputation, task.skills[0]);
}

// Finally, assign reputation penalities if a role didn't submit rating on time
if (submittedRating == 0 && roleId == MANAGER) {
// Worker is penalized in domain and skill
colonyNetworkContract.appendReputationUpdateLog(
task.roles[WORKER].user,
-int(task.payouts[WORKER][token] / 2),
task.skills[0]
);
colonyNetworkContract.appendReputationUpdateLog(
task.roles[WORKER].user,
-int(task.payouts[WORKER][token] / 2),
task.domains[0]
);
} else if (submittedRating == 0 && roleId == WORKER) {
// Evaluator is penalized in domain
colonyNetworkContract.appendReputationUpdateLog(
task.roles[EVALUATOR].user,
-int(task.payouts[EVALUATOR][token] / 2),
task.domains[0]
);
}

if (rating <= 20) {
task.payouts[roleId][token] = 0;
task.totalPayouts[token] = sub(task.totalPayouts[token], payout);
}
// If the work was rejeced with rating 1, don't pay out worker's reward
if (roleId == WORKER && role.rating == 1) {
task.payouts[roleId][token] = 0;
task.totalPayouts[token] = sub(task.totalPayouts[token], payout);
}
}

Expand All @@ -326,9 +337,9 @@ contract ColonyTask is ColonyStorage, DSMath {
return (t.specificationHash, t.deliverableHash, t.finalized, t.cancelled, t.dueDate, t.payoutsWeCannotMake, t.potId, t.deliverableTimestamp);
}

function getTaskRole(uint256 _id, uint8 _idx) public view returns (address, bool, uint8) {
function getTaskRole(uint256 _id, uint8 _idx) public view returns (address, uint8) {
Role storage role = tasks[_id].roles[_idx];
return (role.user, role.rated, role.rating);
return (role.user, role.rating);
}

function getTaskSkill(uint256 _id, uint256 _idx) public view returns (uint256) {
Expand Down
3 changes: 1 addition & 2 deletions contracts/IColony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ contract IColony {
function approveTaskChange(uint256 _transactionId, uint8 _role) public;
function submitTaskWorkRating(uint256 _id, uint8 _role, bytes32 _ratingSecret) public;
function revealTaskWorkRating(uint256 _id, uint8 _role, uint8 _rating, bytes32 _salt) public;
function assignWorkRating(uint256 _id) public;
function generateSecret(bytes32 _salt, uint256 _value) public pure returns (bytes32);
function getTaskWorkRatings(uint256 _id) public view returns (uint256, uint256);
function getTaskWorkRatingSecret(uint256 _id, uint8 _role) public view returns (bytes32);
Expand All @@ -57,7 +56,7 @@ contract IColony {
function finalizeTask(uint256 _id) public;
function cancelTask(uint256 _id) public;
function getTask(uint256 _id) public view returns (bytes32, bytes32, bool, bool, uint256, uint256, uint256, uint256);
function getTaskRole(uint256 _id, uint8 _idx) public view returns (address, bool, uint8);
function getTaskRole(uint256 _id, uint8 _idx) public view returns (address, uint8);
function getTaskSkill(uint256 _id, uint256 _idx) public view returns (uint256);
function getTaskDomain(uint256 _id, uint256 _idx) public view returns (uint256);

Expand Down
4 changes: 2 additions & 2 deletions helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const INITIAL_FUNDING = 360 * 1e18;
const MANAGER_PAYOUT = web3Utils.toBN(100 * 1e18);
const EVALUATOR_PAYOUT = web3Utils.toBN(50 * 1e18);
const WORKER_PAYOUT = web3Utils.toBN(200 * 1e18);
const MANAGER_RATING = 30;
const WORKER_RATING = 40;
const MANAGER_RATING = 2;
const WORKER_RATING = 2;
const SECONDS_PER_DAY = 86400;
const RATING_1_SALT = web3Utils.soliditySha3(getRandomString(10));
const RATING_2_SALT = web3Utils.soliditySha3(getRandomString(10));
Expand Down
4 changes: 2 additions & 2 deletions test/colony-funding.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,13 @@ contract("Colony Funding", () => {
assert.equal(colonyPotBalance.toNumber(), 0);
});

it("should not allow user to claim payout if rating is less or equal than 2", async () => {
it("should not allow user to claim payout if rating is 1", async () => {
await fundColonyWithTokens(colony, token, INITIAL_FUNDING);
const taskId = await setupRatedTask({
colonyNetwork,
colony,
token,
workerRating: 20
workerRating: 1
});
await colony.finalizeTask(taskId);
const payout = await colony.getTaskPayout.call(taskId, WORKER_ROLE, token.address);
Expand Down