Skip to content

Commit

Permalink
Store complement of skill, allow mining reward scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
area committed Jun 10, 2023
1 parent 7671757 commit 8d2fbd4
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 19 deletions.
16 changes: 16 additions & 0 deletions contracts/colony/Colony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP
IColonyNetwork(colonyNetworkAddress).setReputationMiningCycleReward(_amount);
}

function setReputationMiningCycle(uint256 _amount) public
stoppable
auth
{
IColonyNetwork(colonyNetworkAddress).setReputationMiningCycleReward(_amount);
}


function addNetworkColonyVersion(uint256 _version, address _resolver) public
stoppable
auth
Expand Down Expand Up @@ -334,6 +342,9 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP

sig = bytes4(keccak256("setReputationDecayRate(uint256,uint256)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true);

sig = bytes4(keccak256("setReputationMiningCycleRewardReputationScaling(uint256)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true);
}

function setTokenReputationRate(address _token, uint256 _rate) public stoppable {
Expand Down Expand Up @@ -423,4 +434,9 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP
return tokenApprovalTotals[_token];
}

function setReputationMiningCycleRewardReputationScaling(uint256 _factor) public stoppable auth {
require(_factor <= WAD, "colony-invalid-scale-factor");
IColonyNetwork(colonyNetworkAddress).setReputationMiningCycleRewardReputationScaling(_factor);
emit MiningReputationScalingSet(_factor);
}
}
1 change: 1 addition & 0 deletions contracts/colony/ColonyAuthority.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ contract ColonyAuthority is CommonAuthority {
// Added in colony v xxxxx
addRoleCapability(ROOT_ROLE, "setDomainReputationScaling(uint256,bool,uint256)");
addRoleCapability(ROOT_ROLE, "setReputationDecayRate(uint256,uint256)");
addRoleCapability(ROOT_ROLE, "setReputationMiningCycleRewardReputationScaling(uint256)");

}

Expand Down
2 changes: 2 additions & 0 deletions contracts/colony/ColonyDataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ interface ColonyDataTypes {

event DomainReputationScalingSet(uint256 domainId, bool enabled, uint256 factor);

event MiningReputationScalingSet(uint256 factor);

struct RewardPayoutCycle {
// Reputation root hash at the time of reward payout creation
bytes32 reputationState;
Expand Down
5 changes: 5 additions & 0 deletions contracts/colony/IMetaColony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ interface IMetaColony is IColony {
/// @param _amount The CLNY awarded per mining cycle to the miners
function setReputationMiningCycleReward(uint256 _amount) external;

/// @notice Called to set the total per-cycle reputation scaling factor for the tokens paid out
/// @dev Calls the corresponding function on the ColonyNetwork.
/// @param _factor The scale factor to apply to reputation mining rewards
function setReputationMiningCycleRewardReputationScaling(uint256 _factor) external;

/// @notice Add a new extension/version to the Extensions repository.
/// @dev Calls `IColonyNetwork.addExtensionToNetwork`.
/// @dev The extension version is queried from the resolver itself.
Expand Down
13 changes: 6 additions & 7 deletions contracts/colonyNetwork/ColonyNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -282,26 +282,25 @@ contract ColonyNetwork is ColonyDataTypes, BasicMetaTransaction, ColonyNetworkSt
{
require(_factor <= WAD, "colony-network-invalid-reputation-scale-factor");
uint256 skillId = IColony(msgSender()).getDomain(_domainId).skillId;
skills[skillId].earnedReputationScaling = _enabled;
skills[skillId].reputationScalingFactor = _factor;
skills[skillId].reputationScalingFactorComplement = WAD - _factor;
}

function getSkillReputationScaling(uint256 _skillId) public view returns (uint256) {
uint256 factor;
Skill storage s = skills[_skillId];
factor = s.earnedReputationScaling ? s.reputationScalingFactor : WAD;
factor = WAD - s.reputationScalingFactorComplement;

while (s.nParents > 0) {
s = skills[s.parents[0]];
// If reputation scaling is in effect for this skill, then take the value for this skill in to
// account. Otherwise, no effect and continue walking up the tree
if (s.earnedReputationScaling) {
if (s.reputationScalingFactor == 0){
// If scaling is in effect and is 0, we can short circuit - regardless of the rest of the tree
if (s.reputationScalingFactorComplement > 0) {
if (s.reputationScalingFactorComplement == 1){
// If scaling is in effect and is 0 (because factor = 1 - complement), we can short circuit - regardless of the rest of the tree
// the scaling factor will be 0
return 0;
} else {
factor = wmul(factor, s.reputationScalingFactor);
factor = wmul(factor, WAD - s.reputationScalingFactorComplement);
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions contracts/colonyNetwork/ColonyNetworkDataTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,8 @@ interface ColonyNetworkDataTypes {
bool globalSkill;
// `true` for a global skill that is deprecated
bool deprecated;
// `true` if global scaling is in effect
bool earnedReputationScaling;
// NB extra storage space available here for more booleans etc
// scaling in effect for reputation earned in this skill
uint256 reputationScalingFactor;
// This is the complement of the reputaiton scaling factor. So the scaling factor is WAD-reputationScalingFactorComplement
uint256 reputationScalingFactorComplement;
}

struct ENSRecord {
Expand Down
13 changes: 11 additions & 2 deletions contracts/colonyNetwork/ColonyNetworkMining.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ pragma experimental "ABIEncoderV2";
import "./../common/ERC20Extended.sol";
import "./../common/EtherRouter.sol";
import "./../common/MultiChain.sol";
import "./../common/ScaleReputation.sol";
import "./../reputationMiningCycle/IReputationMiningCycle.sol";
import "./../tokenLocking/ITokenLocking.sol";
import "./ColonyNetworkStorage.sol";


contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain, ScaleReputation {
// TODO: Can we handle a dispute regarding the very first hash that should be set?

modifier onlyReputationMiningCycle () {
Expand Down Expand Up @@ -201,7 +202,8 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
stakers,
minerWeights,
metaColony,
totalMinerRewardPerCycle,
// totalMinerRewardPerCycle,
uint256(scaleReputation(int256(totalMinerRewardPerCycle), WAD - skills[reputationMiningSkillId].reputationScalingFactorComplement)),
reputationMiningSkillId
);
}
Expand Down Expand Up @@ -275,6 +277,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
}

function setReputationMiningCycleReward(uint256 _amount) public stoppable calledByMetaColony {
require(_amount < uint256(type(int256).max), "colony-network-too-large-reward");
totalMinerRewardPerCycle = _amount;

emit ReputationMiningRewardSet(_amount);
Expand All @@ -284,6 +287,12 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain {
return totalMinerRewardPerCycle;
}

function setReputationMiningCycleRewardReputationScaling(uint256 _factor) public calledByMetaColony stoppable
{
require(_factor <= WAD, "colony-network-invalid-reputation-scale-factor");
skills[reputationMiningSkillId].reputationScalingFactorComplement = WAD - _factor;
}

uint256 constant UINT192_MAX = 2**192 - 1; // Used for updating the stake timestamp

function getNewTimestamp(uint256 _prevWeight, uint256 _currWeight, uint256 _prevTime, uint256 _currTime) internal pure returns (uint256) {
Expand Down
5 changes: 5 additions & 0 deletions contracts/colonyNetwork/IColonyNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -487,4 +487,9 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransac
/// @return numerator The numerator of the fraction reputation does down by every reputation cycle
/// @return denominator The denominator of the fraction reputation does down by every reputation cycle
function getColonyReputationDecayRate(address _colony) external view returns (uint256 numerator, uint256 denominator);

/// @notice Called to set the total per-cycle reputation scaling factor for the tokens paid out
/// @dev Calls the corresponding function on the ColonyNetwork.
/// @param _factor The scale factor to apply to reputation mining rewards
function setReputationMiningCycleRewardReputationScaling(uint256 _factor) external;
}
13 changes: 13 additions & 0 deletions docs/interfaces/icolonynetwork.md
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,19 @@ Called to set the total per-cycle reputation reward, which will be split between
|_amount|uint256|The CLNY awarded per mining cycle to the miners


### `setReputationMiningCycleRewardReputationScaling(uint256 _factor)`

Called to set the total per-cycle reputation scaling factor for the tokens paid out

*Note: Calls the corresponding function on the ColonyNetwork.*

**Parameters**

|Name|Type|Description|
|---|---|---|
|_factor|uint256|The scale factor to apply to reputation mining rewards


### `setReputationRootHash(bytes32 _newHash, uint256 _newNLeaves, address[] memory _stakers)`

Set a new Reputation root hash and starts a new mining cycle. Can only be called by the ReputationMiningCycle contract.
Expand Down
15 changes: 14 additions & 1 deletion docs/interfaces/imetacolony.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,17 @@ Called to set the total per-cycle reputation reward, which will be split between

|Name|Type|Description|
|---|---|---|
|_amount|uint256|The CLNY awarded per mining cycle to the miners
|_amount|uint256|The CLNY awarded per mining cycle to the miners


### `setReputationMiningCycleRewardReputationScaling(uint256 _factor)`

Called to set the total per-cycle reputation scaling factor for the tokens paid out

*Note: Calls the corresponding function on the ColonyNetwork.*

**Parameters**

|Name|Type|Description|
|---|---|---|
|_factor|uint256|The scale factor to apply to reputation mining rewards
6 changes: 2 additions & 4 deletions test/contracts-network/colony.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,14 +555,12 @@ contract("Colony", (accounts) => {

const domain = await colony.getDomain(1);
let skill = await colonyNetwork.getSkill(domain.skillId);
expect(skill.reputationScalingFactor).to.be.eq.BN(WAD.divn(2));
expect(skill.earnedReputationScaling).to.be.true;
expect(skill.reputationScalingFactorComplement).to.be.eq.BN(WAD.divn(2));

await colony.setDomainReputationScaling(1, false, 0);

skill = await colonyNetwork.getSkill(domain.skillId);
expect(skill.reputationScalingFactor).to.be.eq.BN(0);
expect(skill.earnedReputationScaling).to.be.false;
expect(skill.reputationScalingFactorComplement).to.be.eq.BN(WAD);
});

it("setting domain reputation scaling to false with a nonzero scale factor fails", async () => {
Expand Down
141 changes: 141 additions & 0 deletions test/reputation-system/root-hash-submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,70 @@ contract("Reputation mining - root hash submissions", (accounts) => {
expect(reputationUpdateLogLength).to.eq.BN(2);
});

it("should respect reputation scaling for mining rewards", async () => {
const miningSkillId = 3;

await metaColony.setReputationMiningCycleReward(WAD.muln(10));
await metaColony.setReputationMiningCycleRewardReputationScaling(WAD.divn(2));
const repCycle = await getActiveRepCycle(colonyNetwork);
await forwardTime(MINING_CYCLE_DURATION / 2, this);

const entryNumber = await getValidEntryNumber(colonyNetwork, MINER1, "0x12345678");
const entryNumber2 = await getValidEntryNumber(colonyNetwork, MINER1, "0x12345678", entryNumber + 1);

await repCycle.submitRootHash("0x12345678", 10, "0x00", entryNumber, { from: MINER1 });
await repCycle.submitRootHash("0x12345678", 10, "0x00", entryNumber2, { from: MINER1 });

const nUniqueSubmittedHashes = await repCycle.getNUniqueSubmittedHashes();
expect(nUniqueSubmittedHashes).to.eq.BN(1);

await forwardTime(MINING_CYCLE_DURATION / 2 + CHALLENGE_RESPONSE_WINDOW_DURATION + 1, this);
const lockedFor1 = await tokenLocking.getUserLock(clnyToken.address, MINER1);

await repCycle.confirmNewHash(0, { from: MINER1 });
const lockedFor1Updated = await tokenLocking.getUserLock(clnyToken.address, MINER1);

const addr = await colonyNetwork.getReputationMiningCycle(false);
const inactiveRepCycle = await IReputationMiningCycle.at(addr);

const blockTime = await currentBlockTime();
const stake = await colonyNetwork.getMiningStake(MINER1);
const mw1 = await colonyNetwork.calculateMinerWeight(blockTime - stake.timestamp, 0);
const mw2 = await colonyNetwork.calculateMinerWeight(blockTime - stake.timestamp, 1);

const r1 = await WAD.muln(10)
.mul(mw1.mul(WAD).div(mw1.add(mw2)))
.div(WAD);

const r2 = await WAD.muln(10)
.mul(mw2.mul(WAD).div(mw1.add(mw2)))
.div(WAD);

// Check they've been awarded the tokens
const m1Reward = new BN(lockedFor1Updated.balance).sub(new BN(lockedFor1.balance));
expect(m1Reward, "Account was not rewarded properly").to.be.eq.BN(r1.add(r2));

// Check that they will be getting the reputation owed to them.
let repLogEntryMiner = await inactiveRepCycle.getReputationUpdateLogEntry(0);
expect(repLogEntryMiner.user).to.equal(MINER1);
expect(repLogEntryMiner.amount).to.eq.BN(r1.divn(2));
expect(repLogEntryMiner.skillId).to.eq.BN(miningSkillId);
expect(repLogEntryMiner.colony).to.equal(metaColony.address);
expect(repLogEntryMiner.nUpdates).to.eq.BN(4);
expect(repLogEntryMiner.nPreviousUpdates).to.be.zero;

repLogEntryMiner = await inactiveRepCycle.getReputationUpdateLogEntry(1);
expect(repLogEntryMiner.user).to.equal(MINER1);
expect(repLogEntryMiner.amount).to.eq.BN(r2.divn(2));
expect(repLogEntryMiner.skillId).to.eq.BN(miningSkillId);
expect(repLogEntryMiner.colony).to.equal(metaColony.address);
expect(repLogEntryMiner.nUpdates).to.eq.BN(4);
expect(repLogEntryMiner.nPreviousUpdates).to.eq.BN(4);

const reputationUpdateLogLength = await inactiveRepCycle.getReputationUpdateLogLength();
expect(reputationUpdateLogLength).to.eq.BN(2);
});

it("should only allow 12 entries to back a single hash in each cycle", async () => {
const repCycle = await getActiveRepCycle(colonyNetwork);
await forwardTime(MINING_CYCLE_DURATION - 600, this);
Expand Down Expand Up @@ -767,6 +831,83 @@ contract("Reputation mining - root hash submissions", (accounts) => {
expect(reputationUpdateLogLength).to.eq.BN(2);
});

it("should scale staking rewards by the scaling factor set for miners", async () => {
const miningSkillId = 3;

await metaColony.setReputationMiningCycleReward(WAD.muln(10));
await metaColony.setReputationMiningCycleRewardReputationScaling(WAD.divn(2));
await advanceMiningCycleNoContest({ colonyNetwork, test: this });
await clnyToken.burn(REWARD, { from: MINER1 });

const repCycle = await getActiveRepCycle(colonyNetwork);
await forwardTime(MINING_CYCLE_DURATION / 2, this);

const entryNumber = await getValidEntryNumber(colonyNetwork, MINER1, "0x12345678");
const entryNumber2 = await getValidEntryNumber(colonyNetwork, MINER2, "0x12345678");

await repCycle.submitRootHash("0x12345678", 10, "0x00", entryNumber, { from: MINER1 });
await repCycle.submitRootHash("0x12345678", 10, "0x00", entryNumber2, { from: MINER2 });

const lockedFor1 = await tokenLocking.getUserLock(clnyToken.address, MINER1);
const lockedFor2 = await tokenLocking.getUserLock(clnyToken.address, MINER2);

await forwardTime(MINING_CYCLE_DURATION / 2 + CHALLENGE_RESPONSE_WINDOW_DURATION, this);
await forwardTime(MINING_CYCLE_DURATION / 2 + CHALLENGE_RESPONSE_WINDOW_DURATION + 1, this);

await repCycle.confirmNewHash(0, { from: MINER1 });

const blockTime = await currentBlockTime();
const stake1 = await colonyNetwork.getMiningStake(MINER1);
const stake2 = await colonyNetwork.getMiningStake(MINER2);
const mw1 = await colonyNetwork.calculateMinerWeight(blockTime - stake1.timestamp, 0);
const mw2 = await colonyNetwork.calculateMinerWeight(blockTime - stake2.timestamp, 1);

const r1 = await WAD.muln(10)
.mul(mw1.mul(WAD).div(mw1.add(mw2)))
.div(WAD);

const r2 = await WAD.muln(10)
.mul(mw2.mul(WAD).div(mw1.add(mw2)))
.div(WAD);

// Check that they have had their balance increase
const lockedFor1Updated = await tokenLocking.getUserLock(clnyToken.address, MINER1);
const lockedFor2Updated = await tokenLocking.getUserLock(clnyToken.address, MINER2);
// More than half of the reward
const m1Reward = new BN(lockedFor1Updated.balance).sub(new BN(lockedFor1.balance));
expect(m1Reward).to.eq.BN(r1);
// Less than half of the reward
const m2Reward = new BN(lockedFor2Updated.balance).sub(new BN(lockedFor2.balance));
expect(m2Reward).to.eq.BN(r2);
expect(m1Reward.add(m2Reward)).to.be.lte.BN(WAD.muln(10));
// The first 18 significant figures should be correct, and they are 19 significant
// figures long. The biggest possible error in the sum is therefore 18 wei.
expect(WAD.muln(10).sub(m1Reward).sub(m2Reward).abs()).to.be.lte.BN(new BN(18));

const addr = await colonyNetwork.getReputationMiningCycle(false);
const inactiveRepCycle = await IReputationMiningCycle.at(addr);

// Check that they will be getting the reputation owed to them.
let repLogEntryMiner = await inactiveRepCycle.getReputationUpdateLogEntry(0);
expect(repLogEntryMiner.user).to.equal(MINER1);
expect(repLogEntryMiner.amount).to.eq.BN(r1.divn(2));
expect(repLogEntryMiner.skillId).to.eq.BN(miningSkillId);
expect(repLogEntryMiner.colony).to.equal(metaColony.address);
expect(repLogEntryMiner.nUpdates).to.eq.BN(4);
expect(repLogEntryMiner.nPreviousUpdates).to.be.zero;

repLogEntryMiner = await inactiveRepCycle.getReputationUpdateLogEntry(1);
expect(repLogEntryMiner.user).to.equal(MINER2);
expect(repLogEntryMiner.amount).to.eq.BN(r2.divn(2));
expect(repLogEntryMiner.skillId).to.eq.BN(miningSkillId);
expect(repLogEntryMiner.colony).to.equal(metaColony.address);
expect(repLogEntryMiner.nUpdates).to.eq.BN(4);
expect(repLogEntryMiner.nPreviousUpdates).to.eq.BN(4);

const reputationUpdateLogLength = await inactiveRepCycle.getReputationUpdateLogLength();
expect(reputationUpdateLogLength).to.eq.BN(2);
});

it("should be able to complete a cycle and claim rewards even if CLNY has been locked", async () => {
await metaColony.setReputationMiningCycleReward(WAD.muln(10));
await metaColony.mintTokens(WAD);
Expand Down

0 comments on commit 8d2fbd4

Please sign in to comment.