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

Miner variable rewards I #399

Merged
merged 4 commits into from
Nov 15, 2018
Merged

Miner variable rewards I #399

merged 4 commits into from
Nov 15, 2018

Conversation

kronosapiens
Copy link
Contributor

@kronosapiens kronosapiens commented Oct 27, 2018

Partially addresses #164

  • Variable miner rewards as per WP sec 7.8 equations 3 & 4
  • Token staking timestamp updates as average of existing and new stake.
  • Currently total reward is fixed at 1200 CLNY.

@kronosapiens kronosapiens force-pushed the feature/miner-rewards branch from be707ca to e39a0d7 Compare October 27, 2018 07:32
@kronosapiens kronosapiens force-pushed the feature/miner-rewards branch 2 times, most recently from 49c4730 to adac96b Compare October 27, 2018 15:24
@kronosapiens kronosapiens changed the title Miner variable rewards Miner variable rewards I Oct 27, 2018
@@ -113,29 +114,61 @@ contract ColonyNetworkMining is ColonyNetworkStorage {
}
}

function rewardStakers(address[] stakers) internal {
// Constants for miner weight calculations
uint256 constant T = 7776000 * WAD; // Seconds in 90 days
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update comment to be clear it is Seconds in 90 days * WAD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

function rewardStakers(address[] stakers) internal {
// Constants for miner weight calculations
uint256 constant T = 7776000 * WAD; // Seconds in 90 days
uint256 constant N = 24 * WAD; // 2x maximum number of miners
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2x maximum number of miners * WAD

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

/// @param _timeStaked Amount of time (in seconds) that the miner has staked their CLNY
/// @param _submissonIndex Index of reputation hash submission (between 1 and 12)
/// @return minerWeight The weight of miner reward
function calculateMinerWeight(uint256 _timeStaked, uint256 _submissonIndex) public view returns (uint256 minerWeight);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function can be set to pure (both here and in the implementation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -199,12 +199,13 @@ contract IReputationMiningCycle {

/// @notice Start the reputation log with the rewards for the stakers who backed the accepted new reputation root hash.
/// @param stakers The array of stakers addresses to receive the reward.
/// @param commonColonyAddress The address of the common colony, which the special mining skill is earned in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aaa we still had this leftover common colony terminology around! Glad you found it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥇

/// @param reward The amount of reputation to be rewarded to each staker
/// @dev Only callable by colonyNetwork
/// @dev Note that the same address might be present multiple times in `stakers` - this is acceptable, and indicates the
/// same address backed the same hash multiple times with different entries.
function rewardStakersWithReputation(address[] stakers, address commonColonyAddress, uint reward, uint miningSkillId) public;
function rewardStakersWithReputation(address[] stakers, uint256[] weights, address metaColonyAddress, uint reward, uint miningSkillId) public;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was there beforehand but can we update the uint parameters to be explicitly uint256

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


uint256 timestamp = add(mul(prevWeight, lock.timestamp), mul(currWeight, now)) / add(prevWeight, currWeight);

userLocks[_token][msg.sender] = Lock(totalLockCount[_token], add(lock.balance, _amount), timestamp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above lock variable is a storage reference so you can set any of it properties directly e.g.

lock.balance = add(lock.balance, _amount);
lock.timestamp = timestamp;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but since we're setting all of the fields simultaneously it seems justified to do it in one go.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see us setting all the fields here, totalLockCount is not changed just the other two.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, could there be a situation where totalLockCount[_token] is different than it was when the last deposit was made?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elenadimitrova This test fails if we don't also set totalLockCount[_token]:

should be able to set user lock count equal to total lock count when depositing if user had 0 deposited tokens:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. Glad I dug in this function to find another issue (#437), unrelated to the changes here.
At least I'd like to reuse the lock storage reference and do

lock = Lock(totalLockCount[_token], add(lock.balance, _amount), newTimestamp);


for (i = 0; i < stakers.length; i++) {
(,,timeStaked) = ITokenLocking(tokenLocking).getUserLock(clnyToken, stakers[i]);
minerWeights[i] = calculateMinerWeight(now - timeStaked, i + 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the submissionIndex in calculateMinerWeight is 0-based then there won't be need to do i + 1 here and submissionIndex - 1 there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

@@ -188,7 +190,8 @@ contract ReputationMiningCycle is ReputationMiningCycleStorage, PatriciaTreeProo
IColonyNetwork(colonyNetworkAddress).setReputationRootHash(
submission.proposedNewRootHash,
submission.nNodes,
submittedHashes[submission.proposedNewRootHash][submission.nNodes]
submittedHashes[submission.proposedNewRootHash][submission.nNodes],
1200 * WAD // TODO: Make this a function of reputation state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid going round the houses and passing the reward to IColonyNetwork. setReputationRootHash which calls IColonyNetwork.rewardStakers which in turns circles back to here IReputationMiningCycle.rewardStakersWithReputation and make it a separate internal contract call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that 👆 works we'll avoid importing ITokenLocking in ColonyNetworkMining too and reuse the import here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewarding miners with tokens can stay in ColonyNetworkMining but reputation rewards can likely be done here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I would say in that case let's move the whole reward flow into ReputationMiningCycle. Having the reward logic split over two contracts seems unnecessary; we can get metaColonyAddress via IColonyNetwork(colonyNetworkAddress).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erm wait, I'm noticing that we're not technically going in a circle -- the original contract is the activeReputationMiningCycle and we call back into the inactiveReputationMiningCycle. So this circuitous route is not so circuitous after all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I still experimented a little with refactoring this but got no significant benefits clarity wise. Considering confirmNewHash gas cost of 687,823 which is less than a dollar at current prices this isn't a priority atm so happy to leave as is, for now at least.

@elenadimitrova elenadimitrova added this to the Sprint 12 milestone Nov 8, 2018
const info2 = await tokenLocking.getUserLock(token.address, userAddress);

const avgTime = (time1 + time2) / 2;
assert.closeTo(info2[2].toNumber(), avgTime, 1); // Tolerance of 1 second
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can be made deterministic if you query the most recent block after each deposit, getting the actual timestamp the transaction was executed at.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there also be tests where the deposit amount is unequal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

assert.isTrue(balance1Updated.sub(REWARD.divn(2)).gtn(0), "Account was not rewarded properly");
// Less than half of the reward
assert.isTrue(balance2Updated.sub(REWARD.divn(2)).ltn(0), "Account was not rewarded properly");
// Sum is total reward within 100 wei of precision error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This 100 wei figure is completely arbitrary?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it a conscious decision to only be checking that the total awarded was at least the right amount, rather than no more than the right amount? Are we enable to ensure we always land on one side or the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's somewhat arbitrary, as a "very small round number" (although I just tried it with 10 wei and it passed). And I believe that closeTo test is double-sided, looking for whether the amount is within 100 of the target in either direction.

Copy link
Contributor Author

@kronosapiens kronosapiens Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know, turns out even equal works. But part of me still prefers to write the test in terms of tolerance, since someone could change the token values later and the test could then fail for unrelated precision reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will write test in terms of stakers.length wei.

// Also give them some newly minted tokens.
ERC20Extended(IColony(metaColony).getToken()).transfer(stakers[i], reward);
for (i = 0; i < stakers.length; i++) {
ERC20Extended(clnyToken).transfer(stakers[i], wmul(reward, minerWeights[i]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's going to be some dust left at the end of this, no? Can we explicitly burn it, or something, rather than leaving it in an address that we can't recover it from?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also the issue with rounding up 0.5s with wmul/wdiv... We could calculate how much we should mint, and mint that? But then the total reward is not exactly 1200 CLNY...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm I like the idea of working backwards to figure out the amount to mint. Saying "we'd like to give out around X CLNY, but given some slight imprecision we're going to figure out what the rewards should be and mint that amount".

Copy link
Contributor Author

@kronosapiens kronosapiens Nov 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm proposing we introduce a realReward value which is calculated as a sum of the weighted reward, which avoids dust issues. Given that the reputation amount is meant to be variable anyway (i.e. a function of the reputation state), I think this approach is reasonable for now. If we need stronger guarantees we can circle back around to this issue.

@kronosapiens
Copy link
Contributor Author

@elenadimitrova @area Have updated in response to feedback

@kronosapiens kronosapiens merged commit 98522b6 into develop Nov 15, 2018
@kronosapiens kronosapiens deleted the feature/miner-rewards branch November 15, 2018 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants