Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
473 changes: 129 additions & 344 deletions contracts/colony/ColonyFunding.sol

Large diffs are not rendered by default.

208 changes: 208 additions & 0 deletions contracts/colony/ColonyRewards.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
This file is part of The Colony Network.

The Colony Network is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

The Colony Network is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
*/

pragma solidity 0.7.3;
pragma experimental "ABIEncoderV2";

import "./../tokenLocking/ITokenLocking.sol";
import "./ColonyStorage.sol";


contract ColonyRewards is ColonyStorage, PatriciaTreeProofs { // ignore-swc-123
function lockToken() public stoppable onlyOwnExtension returns (uint256) {
uint256 lockId = ITokenLocking(tokenLockingAddress).lockToken(token);
tokenLocks[msgSender()][lockId] = true;
return lockId;
}

function unlockTokenForUser(address _user, uint256 _lockId) public stoppable onlyOwnExtension {
require(tokenLocks[msgSender()][_lockId], "colony-bad-lock-id");
ITokenLocking(tokenLockingAddress).unlockTokenForUser(token, _user, _lockId);
}

function startNextRewardPayout(address _token, bytes memory key, bytes memory value, uint256 branchMask, bytes32[] memory siblings)
public stoppable auth
{
ITokenLocking tokenLocking = ITokenLocking(tokenLockingAddress);
uint256 totalLockCount = tokenLocking.lockToken(token);
uint256 thisPayoutAmount = sub(fundingPots[0].balance[_token], pendingRewardPayments[_token]);
require(thisPayoutAmount > 0, "colony-reward-payout-no-rewards");
pendingRewardPayments[_token] = add(pendingRewardPayments[_token], thisPayoutAmount);

uint256 totalTokens = sub(ERC20Extended(token).totalSupply(), ERC20Extended(token).balanceOf(address(this)));
require(totalTokens > 0, "colony-reward-payout-invalid-total-tokens");

bytes32 rootHash = IColonyNetwork(colonyNetworkAddress).getReputationRootHash();
uint256 colonyWideReputation = checkReputation(
rootHash,
domains[1].skillId,
address(0x0),
key,
value,
branchMask,
siblings
);
require(colonyWideReputation > 0, "colony-reward-payout-invalid-colony-wide-reputation");

rewardPayoutCycles[totalLockCount] = RewardPayoutCycle(
rootHash,
colonyWideReputation,
totalTokens,
thisPayoutAmount,
_token,
block.timestamp,
thisPayoutAmount,
false
);

emit RewardPayoutCycleStarted(msgSender(), totalLockCount);
}

// slither-disable-next-line reentrancy-no-eth
function claimRewardPayout(
uint256 _payoutId,
uint256[7] memory _squareRoots,
bytes memory key,
bytes memory value,
uint256 branchMask,
bytes32[] memory siblings
) public stoppable
{
uint256 userReputation = checkReputation(
rewardPayoutCycles[_payoutId].reputationState,
domains[1].skillId,
msgSender(),
key,
value,
branchMask,
siblings
);

address tokenAddress;
uint256 reward;
(tokenAddress, reward) = calculateRewardForUser(_payoutId, _squareRoots, userReputation);

ITokenLocking(tokenLockingAddress).unlockTokenForUser(token, msgSender(), _payoutId);

uint fee = calculateNetworkFeeForPayout(reward);
uint remainder = sub(reward, fee);

fundingPots[0].balance[tokenAddress] = sub(fundingPots[0].balance[tokenAddress], reward);
pendingRewardPayments[rewardPayoutCycles[_payoutId].tokenAddress] = sub(
pendingRewardPayments[rewardPayoutCycles[_payoutId].tokenAddress],
reward
);
rewardPayoutCycles[_payoutId].amountRemaining = sub(rewardPayoutCycles[_payoutId].amountRemaining, reward);

assert(ERC20Extended(tokenAddress).transfer(msgSender(), remainder));
assert(ERC20Extended(tokenAddress).transfer(colonyNetworkAddress, fee));

emit RewardPayoutClaimed(_payoutId, msgSender(), fee, remainder);
}

function finalizeRewardPayout(uint256 _payoutId) public stoppable {
RewardPayoutCycle memory payout = rewardPayoutCycles[_payoutId];
require(payout.reputationState != 0x00, "colony-reward-payout-does-not-exist");
require(!payout.finalized, "colony-reward-payout-already-finalized");
require(block.timestamp - payout.blockTimestamp > 60 days, "colony-reward-payout-active");

rewardPayoutCycles[_payoutId].finalized = true;
pendingRewardPayments[payout.tokenAddress] = sub(pendingRewardPayments[payout.tokenAddress], payout.amountRemaining);

emit RewardPayoutCycleEnded(msgSender(), _payoutId);
}

function getRewardPayoutInfo(uint256 _payoutId) public view returns (RewardPayoutCycle memory rewardPayoutCycle) {
rewardPayoutCycle = rewardPayoutCycles[_payoutId];
}

function setRewardInverse(uint256 _rewardInverse) public
stoppable
auth
{
require(_rewardInverse > 0, "colony-reward-inverse-cannot-be-zero");
rewardInverse = _rewardInverse;

emit ColonyRewardInverseSet(msgSender(), _rewardInverse);
}

function checkReputation(
bytes32 rootHash,
uint256 skillId,
address userAddress,
bytes memory key,
bytes memory value,
uint256 branchMask,
bytes32[] memory siblings
) internal view returns (uint256)
{
bytes32 impliedRoot = getImpliedRootHashKey(key, value, branchMask, siblings);
require(rootHash == impliedRoot, "colony-reputation-invalid-root-hash");

uint256 reputationValue;
address keyColonyAddress;
uint256 keySkill;
address keyUserAddress;

assembly {
reputationValue := mload(add(value, 32))
keyColonyAddress := mload(add(key, 20))
keySkill := mload(add(key, 52))
keyUserAddress := mload(add(key, 72))
}

require(keyColonyAddress == address(this), "colony-reputation-invalid-colony-address");
require(keySkill == skillId, "colony-reputation-invalid-skill-id");
require(keyUserAddress == userAddress, "colony-reputation-invalid-user-address");

return reputationValue;
}

function calculateRewardForUser(uint256 payoutId, uint256[7] memory squareRoots, uint256 userReputation) internal returns (address, uint256) {
RewardPayoutCycle memory payout = rewardPayoutCycles[payoutId];

// Checking if payout is active
require(block.timestamp - payout.blockTimestamp <= 60 days, "colony-reward-payout-not-active");

uint256 userTokens = ITokenLocking(tokenLockingAddress).getUserLock(token, msgSender()).balance;
require(userTokens > 0, "colony-reward-payout-invalid-user-tokens");
require(userReputation > 0, "colony-reward-payout-invalid-user-reputation");

// squareRoots[0] - square root of userReputation
// squareRoots[1] - square root of userTokens (deposited in TokenLocking)
// squareRoots[2] - square root of payout.colonyWideReputation
// squareRoots[3] - square root of totalTokens
// squareRoots[4] - square root of numerator
// squareRoots[5] - square root of denominator
// squareRoots[6] - square root of payout.amount

require(mul(squareRoots[0], squareRoots[0]) <= userReputation, "colony-reward-payout-invalid-parameter-user-reputation");
require(mul(squareRoots[1], squareRoots[1]) <= userTokens, "colony-reward-payout-invalid-parameter-user-token");
require(mul(squareRoots[2], squareRoots[2]) >= payout.colonyWideReputation, "colony-reward-payout-invalid-parameter-total-reputation");
require(mul(squareRoots[3], squareRoots[3]) >= payout.totalTokens, "colony-reward-payout-invalid-parameter-total-tokens");
require(mul(squareRoots[6], squareRoots[6]) <= payout.amount, "colony-reward-payout-invalid-parameter-amount");
uint256 numerator = mul(squareRoots[0], squareRoots[1]);
uint256 denominator = mul(squareRoots[2], squareRoots[3]);

require(mul(squareRoots[4], squareRoots[4]) <= numerator, "colony-reward-payout-invalid-parameter-numerator");
require(mul(squareRoots[5], squareRoots[5]) >= denominator, "colony-reward-payout-invalid-parameter-denominator");

uint256 reward = (mul(squareRoots[4], squareRoots[6]) / squareRoots[5]) ** 2;

return (payout.tokenAddress, reward);
}
}
11 changes: 11 additions & 0 deletions contracts/colony/ColonyStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,17 @@ contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, Commo
return domainId > 0 && domainId <= domainCount;
}

function calculateNetworkFeeForPayout(uint256 _payout) internal view returns (uint256 fee) {
uint256 feeInverse = IColonyNetwork(colonyNetworkAddress).getFeeInverse();

// slither-disable-next-line incorrect-equality
if (_payout == 0 || feeInverse == 1) {
fee = _payout;
} else {
fee = _payout / feeInverse + 1;
}
}

function executeCall(address to, uint256 value, bytes memory data) internal returns (bool success) {
assembly {
// call contract at address a with input mem[in…(in+insize))
Expand Down
34 changes: 33 additions & 1 deletion contracts/colony/ColonyTask.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pragma solidity 0.7.3;
pragma experimental "ABIEncoderV2";

import "./ColonyStorage.sol";
import "./IColony.sol";


contract ColonyTask is ColonyStorage {
Expand Down Expand Up @@ -371,6 +372,37 @@ contract ColonyTask is ColonyStorage {
emit TaskDueDateSet(_id, _dueDate);
}

function setAllTaskPayouts(
uint256 _id,
address _token,
uint256 _managerAmount,
uint256 _evaluatorAmount,
uint256 _workerAmount
)
public
stoppable
confirmTaskRoleIdentity(_id, TaskRole.Manager)
{
Task storage task = tasks[_id];
address manager = task.roles[uint8(TaskRole.Manager)].user;
address evaluator = task.roles[uint8(TaskRole.Evaluator)].user;
address worker = task.roles[uint8(TaskRole.Worker)].user;

require(
evaluator == manager ||
evaluator == address(0x0),
"colony-funding-evaluator-already-set");

require(
worker == manager ||
worker == address(0x0),
"colony-funding-worker-already-set");

IColony(address(this)).setTaskManagerPayout(_id, _token, _managerAmount);
IColony(address(this)).setTaskEvaluatorPayout(_id, _token, _evaluatorAmount);
IColony(address(this)).setTaskWorkerPayout(_id, _token, _workerAmount);
}

function submitTaskDeliverable(uint256 _id, bytes32 _deliverableHash) public
stoppable
taskExists(_id)
Expand Down Expand Up @@ -610,4 +642,4 @@ contract ColonyTask is ColonyStorage {

emit TaskRoleUserSet(_id, _role, _user);
}
}
}
12 changes: 7 additions & 5 deletions helpers/upgradable-contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ exports.setupColonyVersionResolver = async function setupColonyVersionResolver(
colony,
colonyDomains,
colonyExpenditure,
colonyTask,
colonyPayment,
colonyFunding,
colonyPayment,
colonyRewards,
colonyRoles,
colonyTask,
contractRecovery,
colonyArbitraryTransaction,
resolver
Expand All @@ -86,10 +87,11 @@ exports.setupColonyVersionResolver = async function setupColonyVersionResolver(
deployedImplementations.Colony = colony.address;
deployedImplementations.ColonyDomains = colonyDomains.address;
deployedImplementations.ColonyExpenditure = colonyExpenditure.address;
deployedImplementations.ColonyTask = colonyTask.address;
deployedImplementations.ColonyRoles = colonyRoles.address;
deployedImplementations.ColonyPayment = colonyPayment.address;
deployedImplementations.ColonyFunding = colonyFunding.address;
deployedImplementations.ColonyPayment = colonyPayment.address;
deployedImplementations.ColonyRewards = colonyRewards.address;
deployedImplementations.ColonyRoles = colonyRoles.address;
deployedImplementations.ColonyTask = colonyTask.address;
deployedImplementations.ContractRecovery = contractRecovery.address;
deployedImplementations.ColonyArbitraryTransaction = colonyArbitraryTransaction.address;

Expand Down
13 changes: 8 additions & 5 deletions migrations/4_setup_colony_version_resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const { setupColonyVersionResolver } = require("../helpers/upgradable-contracts"

const Colony = artifacts.require("./Colony");
const ColonyDomains = artifacts.require("./ColonyDomains");
const ColonyFunding = artifacts.require("./ColonyFunding");
const ColonyExpenditure = artifacts.require("./ColonyExpenditure");
const ColonyFunding = artifacts.require("./ColonyFunding");
const ColonyRewards = artifacts.require("./ColonyRewards");
const ColonyRoles = artifacts.require("./ColonyRoles");
const ColonyTask = artifacts.require("./ColonyTask");
const ColonyPayment = artifacts.require("./ColonyPayment");
Expand All @@ -20,11 +21,12 @@ module.exports = async function (deployer) {
// Create a new Colony (version) and setup a new Resolver for it
const colony = await Colony.new();
const colonyDomains = await ColonyDomains.new();
const colonyFunding = await ColonyFunding.new();
const colonyExpenditure = await ColonyExpenditure.new();
const colonyFunding = await ColonyFunding.new();
const colonyPayment = await ColonyPayment.new();
const colonyRewards = await ColonyRewards.new();
const colonyRoles = await ColonyRoles.new();
const colonyTask = await ColonyTask.new();
const colonyPayment = await ColonyPayment.new();
const colonyArbitraryTransaction = await ColonyArbitraryTransaction.new();
const contractRecovery = await ContractRecovery.deployed();
const version = await colony.version();
Expand All @@ -38,10 +40,11 @@ module.exports = async function (deployer) {
colony,
colonyDomains,
colonyExpenditure,
colonyTask,
colonyPayment,
colonyFunding,
colonyPayment,
colonyRewards,
colonyRoles,
colonyTask,
contractRecovery,
colonyArbitraryTransaction,
resolver
Expand Down
Loading