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
28 changes: 18 additions & 10 deletions contracts/WorkerHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ ReentrancyGuardUpgradeable {
minerAddressesByModel[modelAddress].insert(msg.sender);
minerAddresses.insert(msg.sender);
miner.lastClaimedEpoch = currentEpoch;
boost[msg.sender].minerTimestamp = uint40(block.timestamp);

emit MinerJoin(msg.sender);
}
Expand All @@ -313,6 +314,8 @@ ReentrancyGuardUpgradeable {

if (minerAddresses.hasValue(msg.sender)) {
_claimReward(msg.sender, false);
boost[msg.sender].minerTimestamp = uint40(block.timestamp);

minerAddresses.erase(msg.sender);
minerAddressesByModel[miner.modelAddress].erase(msg.sender);
}
Expand Down Expand Up @@ -631,6 +634,8 @@ ReentrancyGuardUpgradeable {
uint256 fine = miner.stake * finePercentage / PERCENTAGE_DENOMINATOR; // Fine = stake * 5%
miner.stake -= fine;

// reset boost
boost[msg.sender].minerTimestamp = uint40(block.timestamp);
TransferHelper.safeTransferNative(treasury, fine);

emit FraudulentMinerPenalized(_miner, modelAddress, treasury, fine);
Expand Down Expand Up @@ -722,18 +727,21 @@ ReentrancyGuardUpgradeable {
totalReward = 0;
} else {
uint256 lastClaimed = uint256(miners[_miner].lastClaimedEpoch);
uint256 epochReward;
uint256 currentMiner;
for (; lastClaimed < lastEpoch; lastClaimed++) {
MinerEpochState memory state = rewardInEpoch[lastClaimed];
// reward at epoch
(epochReward, currentMiner) = (state.epochReward, state.totalMiner);
if (currentMiner > 0 && epochReward > 0) {
totalReward += epochReward / currentMiner;
}
}
uint256 epochReward = rewardPerEpoch * blocksPerEpoch / BLOCK_PER_YEAR; // reward per miner in 1 epoch
totalReward += (lastEpoch - lastClaimed) * epochReward * multiplier(_miner) / PERCENTAGE_DENOMINATOR;
}

return totalReward + minerRewards[_miner];
}

function multiplier(address _miner) public view returns(uint256) {
if (!minerAddresses.hasValue(_miner)) {
return PERCENTAGE_DENOMINATOR;
}

uint256 minerLastTimestamp = minerAddresses.hasValue(_miner) && boost[_miner].minerTimestamp == 0 ?
1716046859 : boost[_miner].minerTimestamp;
uint256 multiplierRes = (block.timestamp - minerLastTimestamp) / 30 days;
return PERCENTAGE_DENOMINATOR + 500 * (multiplierRes >= 12 ? 12 : multiplierRes);
}
}
7 changes: 7 additions & 0 deletions contracts/interfaces/IWorkerHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ interface IWorkerHub is IInferable {
uint40 unlockAt;
}

struct Boost {
uint40 minerTimestamp;
uint40 validatorTimestamp;
uint48 reserved1;
uint128 reserved2;
}

event MiningTimeLimitUpdate(uint40 newValue);

event ModelRegistration(
Expand Down
5 changes: 4 additions & 1 deletion contracts/storages/WorkerHubStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ abstract contract WorkerHubStorage is IWorkerHub {

// mapping tracking reward
mapping(address => uint256) internal minerRewards;
// tracking time miner join the network to
// determine multiplier value
mapping(address => Boost) internal boost;

uint256[98] private __gap;
uint256[97] private __gap;
}
47 changes: 44 additions & 3 deletions tests/Test.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ contract WorkHubTest is Test {
0,
0
);
// workerHub.setNewRewardInEpoch(1e16);

workerHub.registerModel(ModelAddr, 1, 1e18);
vm.stopPrank();
// Sunday, May 19, 2024 4:14:11 PM
vm.warp(1716135251);
}

function testRewards() public {
Expand Down Expand Up @@ -131,6 +133,8 @@ contract WorkHubTest is Test {
workerHub.unstakeForMiner();
assertEq(Miner1.balance, 15601179604261796 + 2e18);

assertEq(workerHub.multiplier(Miner1), 1e4);

vm.roll(51);
assertEq(workerHub.rewardToClaim(Miner1), 0);
workerHub.claimReward(Miner1);
Expand All @@ -155,9 +159,13 @@ contract WorkHubTest is Test {
workerHub.claimReward(Miner1);
assertEq(Miner1.balance, 15601179604261796 + 2e18 - 1e18);
assertEq(workerHub.rewardToClaim(Miner1), 0);

assertEq(workerHub.multiplier(Miner1), 1e4);
// test case miner active then unregis but no claim reward
workerHub.joinForMinting();

// assertEq(workerHub.test(Miner1), block.timestamp);
assertEq(workerHub.multiplier(Miner1), 1e4);

vm.roll(71);
assertEq(workerHub.rewardToClaim(Miner1), 7800589802130898);
vm.roll(81);
Expand All @@ -168,20 +176,53 @@ contract WorkHubTest is Test {
assertEq(Miner1.balance, 15601179604261796 + 2e18);
assertEq(workerHub.rewardToClaim(Miner1), 7800589802130898 * 2);

// assertEq(workerHub.test(Miner1) + 21 days, block.timestamp);

vm.roll(91);
assertEq(workerHub.rewardToClaim(Miner1), 7800589802130898 * 2);
workerHub.registerMiner{value: 1e18}(1);
workerHub.joinForMinting();

// assertEq(workerHub.test(Miner1), block.timestamp);

vm.roll(101);
assertEq(workerHub.rewardToClaim(Miner1), 7800589802130898 * 3);
// assertEq(workerHub.test(Miner1), block.timestamp);

// claim reward
workerHub.claimReward(Miner1);
assertEq(workerHub.rewardToClaim(Miner1), 0);
assertEq(Miner1.balance, 15601179604261796 + 2e18 - 1e18 + 7800589802130898 * 3);
vm.roll(109);
assertEq(workerHub.rewardToClaim(Miner1), 0);

vm.stopPrank();

// test boost reward
assertEq(workerHub.rewardToClaim(Miner1), 0);
// add 2 epochs
vm.roll(121);
assertEq(workerHub.multiplier(Miner2), 11000);
assertEq(workerHub.multiplier(Miner1), 1e4);
assertEq(workerHub.rewardToClaim(Miner1), 7800589802130898 * 2);
// assertEq(workerHub.test(Miner1), block.timestamp);
vm.warp(block.timestamp + 30 days);
uint boostReward = 7800589802130898 * 2 * 10500 / uint256(1e4);
assertEq(workerHub.rewardToClaim(Miner1), boostReward);

vm.warp(block.timestamp + 30 days);
boostReward = 7800589802130898 * 2 * 11000 / uint256(1e4);
assertEq(workerHub.rewardToClaim(Miner1), boostReward);

vm.warp(block.timestamp + 365 days);
boostReward = 7800589802130898 * 2 * 16000 / uint256(1e4);
assertEq(workerHub.rewardToClaim(Miner1), boostReward);

workerHub.claimReward(Miner1);
assertEq(workerHub.rewardToClaim(Miner1), 0);

// unregis reset boost
vm.startPrank(Miner1);
workerHub.unregisterMiner();
assertEq(workerHub.multiplier(Miner1), 1e4);
}
}