Skip to content

Receiver Pot feature added, ClusterRewards: max number of tickets cha… #77

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

Open
wants to merge 5 commits into
base: arbitrum
Choose a base branch
from
Open
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
1,472 changes: 1,472 additions & 0 deletions .openzeppelin/unknown-421613.json → .openzeppelin/arbitrum-goerli.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions address.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@
},
"421613": {
"Pond": "0xa9472a9C135Cb33221de441a4DEd393FD515a69C",
"MarketV1": "0x29cbA3c69CB4Cf89a4e8b5f698df7C607A488e4f",
"MPond": "0x0B3d9b224496C2A2Fa1a4096D8EB4D350eFd9079",
"Bridge": "0xfeEa9a34e51e4E90b8B62F38120A345650164110",
"RewardDelegators": "0xe62CC1181Fb74c1954f13246b6863A8E26a0D77d",
"StakeManager": "0x03040082dc6d47cd9fa4AD474D6d821FD6F0A04C",
"ClusterRegistry": "0x1fe9f98C4c0eC29a012f8B8fFDe962a13fCECe1E",
"ClusterSelector_ETH": "0x9c17478fC7300AB513111d93455Bee5105792FB4",
"ReceiverStaking": "0x49fB661B8Cd1A76DEF8E11eB6d16e7c61C2C9270",
"ClusterRewards": "0xf0ac76EB619DA28ed6907965C17eDbFd16B15CE0"
"ClusterRewards": "0xf0ac76EB619DA28ed6907965C17eDbFd16B15CE0",
"MarketV1": "0xb727c1B1159B8731fE4a20C4C41106D6F85F276E"
}
}
171 changes: 86 additions & 85 deletions contracts/staking/ClusterRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeabl
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";

import "./interfaces/IClusterSelector.sol";
import "./ReceiverStaking.sol";
Expand Down Expand Up @@ -105,28 +106,26 @@ contract ClusterRewards is
}
totalRewardWeight = _weight;
_changeRewardPerEpoch(_totalRewardsPerEpoch);
payoutDenomination = 1e18;
}

//-------------------------------- Initializer end --------------------------------//

//-------------------------------- Admin functions start --------------------------------//

bytes32 public constant CLAIMER_ROLE = keccak256("CLAIMER_ROLE");
bytes32 public constant FEEDER_ROLE = keccak256("FEEDER_ROLE");
uint256 public constant RECEIVER_TICKETS_PER_EPOCH = 2**16;
uint256 public constant RECEIVER_TICKETS_PER_EPOCH = type(uint16).max;
uint256 constant SWITCHING_PERIOD = 33 days;

mapping(address => uint256) public clusterRewards;

mapping(bytes32 => uint256) public rewardWeight;
uint256 public totalRewardWeight;
uint256 public totalRewardsPerEpoch;
uint256 public payoutDenomination;
uint256 public __unused_payoutDenomination;

mapping(uint256 => uint256) public rewardDistributedPerEpoch;
uint256 public latestNewEpochRewardAt;
uint256 public rewardDistributionWaitTime;
mapping(uint256 => uint256) public __unused_rewardDistributedPerEpoch;
uint256 public __unused_latestNewEpochRewardAt;
uint256 public __unused_rewardDistributionWaitTime;

mapping(address => mapping(uint256 => uint256)) public ticketsIssued;
mapping(bytes32 => IClusterSelector) public clusterSelectors; // networkId -> clusterSelector
Expand All @@ -139,12 +138,7 @@ contract ClusterRewards is
event ReceiverStakingUpdated(address receiverStaking);
event RewardPerEpochChanged(uint256 updatedRewardPerEpoch);
event RewardDistributionWaitTimeChanged(uint256 updatedWaitTime);
event TicketsIssued(bytes32 indexed networkId, uint256 indexed epoch, address indexed user);

modifier onlyFeeder() {
require(hasRole(FEEDER_ROLE, _msgSender()), "only feeder");
_;
}
event TicketsIssued(bytes32 indexed networkId, uint256 indexed epoch, address indexed signer);

function addNetwork(bytes32 _networkId, uint256 _rewardWeight, address _clusterSelector) external onlyAdmin {
require(rewardWeight[_networkId] == 0, "CRW:AN-Network already exists");
Expand Down Expand Up @@ -205,68 +199,40 @@ contract ClusterRewards is
emit RewardPerEpochChanged(_updatedRewardPerEpoch);
}

function updateRewardWaitTime(uint256 _updatedWaitTime) external onlyAdmin {
_updateRewardWaitTime(_updatedWaitTime);
}

function _updateRewardWaitTime(uint256 _updatedWaitTime) internal {
rewardDistributionWaitTime = _updatedWaitTime;
emit RewardDistributionWaitTimeChanged(_updatedWaitTime);
}

//-------------------------------- Admin functions end --------------------------------//

//-------------------------------- User functions start --------------------------------//

function feed(
bytes32 _networkId,
address[] calldata _clusters,
uint256[] calldata _payouts,
uint256 _epoch
) external onlyFeeder {
require(receiverStaking.START_TIME() + SWITCHING_PERIOD + 1 days > block.timestamp, "CRW:F-Invalid method");
uint256 rewardDistributed = rewardDistributedPerEpoch[_epoch];
if(rewardDistributed == 0) {
require(
block.timestamp > latestNewEpochRewardAt + rewardDistributionWaitTime,
"CRW:F-Cant distribute reward for new epoch within such short interval"
);
latestNewEpochRewardAt = block.timestamp;
}
uint256 currentPayoutDenomination = payoutDenomination;
uint256 networkRewardWeight = rewardWeight[_networkId];
uint256 currentTotalRewardsPerEpoch = totalRewardsPerEpoch*1 days/receiverStaking.EPOCH_LENGTH()*networkRewardWeight/totalRewardWeight;
for(uint256 i=0; i < _clusters.length; i++) {
uint256 clusterReward = (currentTotalRewardsPerEpoch * _payouts[i]) / currentPayoutDenomination;
rewardDistributed = rewardDistributed + clusterReward;
clusterRewards[_clusters[i]] = clusterRewards[_clusters[i]] + clusterReward;
function _getRewardShare(
uint256 _totalNetworkRewardsPerEpoch,
uint256 _epochTotalStake,
uint256 _epochReceiverStake
) internal pure returns (uint256 _rewardShare) {
unchecked {
// Note: multiplication can't overflow as max token supply is 10^38, hence max value of multiplication is 10^38*10^38 < 2^256
_rewardShare = (_totalNetworkRewardsPerEpoch * _epochReceiverStake) / _epochTotalStake;
}
require(
rewardDistributed <= currentTotalRewardsPerEpoch,
"CRW:F-Reward Distributed cant be more than totalRewardPerEpoch"
);
rewardDistributedPerEpoch[_epoch] = rewardDistributed;
emit ClusterRewarded(_networkId);
}

function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _totalNetworkRewardsPerEpoch, uint256 _epochTotalStake, uint256 _epochReceiverStake) internal {
function _processReceiverTickets(address _receiver, uint256 _epoch, address[] memory _selectedClusters, uint16[] memory _tickets, uint256 _rewardShare) internal {
require(!_isTicketsIssued(_receiver, _epoch), "CRW:IPRT-Tickets already issued");

unchecked {
require(_selectedClusters.length <= _tickets.length + 1, "CRW:IPRT-Tickets length not matching selected clusters");
uint256 _rewardShare = _totalNetworkRewardsPerEpoch * _epochReceiverStake / _epochTotalStake;

uint256 _totalTickets;
uint256 i;
for(; i < _selectedClusters.length - 1; ++i) {
for (; i < _selectedClusters.length - 1; ++i) {
// cant overflow as max supply of POND is 1e28, so max value of multiplication is 1e28*2^16 < uint256
// value that can be added per iteration is < 1e28*2^16/2^16, so clusterRewards for cluster cant overflow
clusterRewards[_selectedClusters[i]] += _rewardShare * uint256(_tickets[i]) / RECEIVER_TICKETS_PER_EPOCH;
clusterRewards[_selectedClusters[i]] += (_rewardShare * uint256(_tickets[i])) / RECEIVER_TICKETS_PER_EPOCH;

// cant overflow as tickets[i] <= 2^16
_totalTickets += uint256(_tickets[i]);
}
require(RECEIVER_TICKETS_PER_EPOCH >= _totalTickets, "CRW:IPRT-Total ticket count invalid");
clusterRewards[_selectedClusters[i]] += _rewardShare * uint256(RECEIVER_TICKETS_PER_EPOCH - _totalTickets)/RECEIVER_TICKETS_PER_EPOCH;
clusterRewards[_selectedClusters[i]] +=
(_rewardShare * (RECEIVER_TICKETS_PER_EPOCH - _totalTickets)) /
RECEIVER_TICKETS_PER_EPOCH;
}

_markAsIssued(_receiver, _epoch);
Expand Down Expand Up @@ -295,46 +261,66 @@ contract ClusterRewards is
}

function issueTickets(bytes32 _networkId, uint24[] calldata _epochs, uint16[][] calldata _tickets) external {
uint256 numberOfEpochs = _epochs.length;
require(numberOfEpochs == _tickets.length, "CRW:MIT-invalid inputs");
require(_epochs.length == _tickets.length, "CRW:MIT-invalid inputs");

address _receiver = receiverStaking.signerToStaker(msg.sender);

ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver];
uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId);
uint256 _rewardToGive;

unchecked {
for(uint256 i=0; i < numberOfEpochs; ++i) {
uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epochs[i], _networkId);
(uint256 _epochTotalStake, uint256 _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]);
require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed");
for (uint256 i = 0; i < _epochs.length; ++i) {
_rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch);
uint256 _epochTotalStake;
{
uint256 _currentEpoch;
(_epochTotalStake, _currentEpoch) = receiverStaking.getEpochInfo(_epochs[i]);

require(_epochs[i] < _currentEpoch, "CRW:IT-Epoch not completed");
}
address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epochs[i]);
(uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epochs[i]);
_processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake);
uint256 _epochReceiverStake = receiverStaking.balanceOfAt(_receiver, _epochs[i]);

uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + _rewardToGive;

_processReceiverTickets(_receiver, _epochs[i], _selectedClusters, _tickets[i], _rewardShare);
// Note: no checks before casting as inputs are uint128
receiverPayment.rewardRemaining -= uint128(_rewardToGive);
emit TicketsIssued(_networkId, _epochs[i], msg.sender);
}
}
receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining;
}

function issueTickets(bytes calldata _ticketInfo) external {
(
bytes32 _networkId,
uint256 _fromEpoch,
uint256 _noOfEpochs,
uint16[][] memory _tickets
) = _parseTicketInfo(_ticketInfo);
(bytes32 _networkId, uint256 _fromEpoch, uint256 _noOfEpochs, uint16[][] memory _tickets) = _parseTicketInfo(_ticketInfo);

ReceiverStaking _receiverStaking = receiverStaking;
require(_fromEpoch + _noOfEpochs <= _receiverStaking.getCurrentEpoch(), "CRW:ITC-Epochs not completed");

uint256[] memory _stakes = _receiverStaking.totalSupplyAtRanged(_fromEpoch, _noOfEpochs);
(uint256[] memory _balances, address _receiver) = _receiverStaking.balanceOfSignerAtRanged(msg.sender, _fromEpoch, _noOfEpochs);
address[][] memory _selectedClusters = clusterSelectors[_networkId].getClustersRanged(_fromEpoch, _noOfEpochs);
uint256 _epochLength = _receiverStaking.EPOCH_LENGTH();
uint256 _totalNetworkRewardsPerEpoch;

ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver];
uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId);
uint256 _rewardToGive;

unchecked {
for(uint256 i=0; i < _noOfEpochs; ++i) {
_totalNetworkRewardsPerEpoch = _getRewardForEpoch(_fromEpoch, _networkId, _epochLength);
_processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]);
for (uint256 i = 0; i < _noOfEpochs; ++i) {
_rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch);
uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _stakes[i], _balances[i]) + _rewardToGive;

_processReceiverTickets(_receiver, _fromEpoch, _selectedClusters[i], _tickets[i], _rewardShare);
emit TicketsIssued(_networkId, _fromEpoch, msg.sender);
// Note: no checks before casting as inputs are uint128
receiverPayment.rewardRemaining -= uint128(_rewardToGive);
++_fromEpoch;
}
}

receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining;
}

function _parseTicketInfo(bytes memory ticketInfo) internal view returns(
Expand Down Expand Up @@ -402,14 +388,21 @@ contract ClusterRewards is

require(_epoch < _currentEpoch, "CRW:IT-Epoch not completed");

address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch);
(uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch);
ReceiverPayment memory receiverPayment = receiverRewardPayment[_receiver];

uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_epoch, _networkId);
address[] memory _selectedClusters = clusterSelectors[_networkId].getClusters(_epoch);
uint256 _totalNetworkRewardsPerEpoch = getRewardForEpoch(_networkId);
uint256 _rewardToGive = MathUpgradeable.min(receiverPayment.rewardRemaining, receiverPayment.rewardPerEpoch);

(uint256 _epochReceiverStake, address _receiver) = receiverStaking.balanceOfSignerAt(msg.sender, _epoch);
_processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake);
uint256 _rewardShare = _getRewardShare(_totalNetworkRewardsPerEpoch, _epochTotalStake, _epochReceiverStake) + _rewardToGive;

_processReceiverTickets(_receiver, _epoch, _selectedClusters, _tickets, _rewardShare);
emit TicketsIssued(_networkId, _epoch, msg.sender);

// Note: no checks before casting as inputs are uint128
receiverPayment.rewardRemaining -= uint128(_rewardToGive);
receiverRewardPayment[_receiver].rewardRemaining = receiverPayment.rewardRemaining;
}

function claimReward(address _cluster) external onlyRole(CLAIMER_ROLE) returns(uint256) {
Expand All @@ -422,14 +415,22 @@ contract ClusterRewards is
return 0;
}

function _getRewardForEpoch(uint256 _epoch, bytes32 _networkId, uint256 _epochLength) internal view returns(uint256) {
if(_epoch < SWITCHING_PERIOD/_epochLength) return 0;
function getRewardForEpoch(bytes32 _networkId) public view override returns (uint256) {
return (totalRewardsPerEpoch * rewardWeight[_networkId]) / totalRewardWeight;
}

function getRewardForEpoch(uint256 _epoch, bytes32 _networkId) public view returns(uint256) {
return _getRewardForEpoch(_epoch, _networkId, receiverStaking.EPOCH_LENGTH());
//-------------------------------- User functions end --------------------------------//

bytes32 public constant RECEIVER_PAYMENTS_MANAGER = keccak256("RECEIVER_PAYMENTS_MANAGER");

mapping(address => ReceiverPayment) public receiverRewardPayment;

function _increaseReceiverBalance(address staker, uint128 amount) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) {
receiverRewardPayment[staker].rewardRemaining += amount;
}

//-------------------------------- User functions end --------------------------------//
}
function _setReceiverRewardPerEpoch(address staker, uint128 rewardPerEpoch) external override onlyRole(RECEIVER_PAYMENTS_MANAGER) {
require(staker != address(0), "CRW: address 0");
receiverRewardPayment[staker].rewardPerEpoch = rewardPerEpoch;
}
}
25 changes: 25 additions & 0 deletions contracts/staking/RewardDelegators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -524,4 +524,29 @@ contract RewardDelegators is
return 0;
}
}

// ------- receiver payments ------------------ //

event ReceiverBalanceAdded(address indexed receiver, uint256 amount);
event ReceiverRewardPerEpochUpdated(address indexed receiver, uint256 amount);

function addReceiverBalance(address receiver, uint128 amount) public {
require(receiver != address(0), "RD: address 0");
require(amount != 0, "RD: amount 0");
PONDToken.transferFrom(msg.sender, address(this), amount);
clusterRewards._increaseReceiverBalance(receiver, amount);
emit ReceiverBalanceAdded(receiver, amount);
}

function setReceiverRewardPerEpoch(uint128 rewardPerEpoch) public {
address _sender = _msgSender();
clusterRewards._setReceiverRewardPerEpoch(_sender, rewardPerEpoch);
emit ReceiverRewardPerEpochUpdated(_sender, rewardPerEpoch);
}

function setupReceiverReward(uint128 amount, uint128 rewardPerEpoch) external {
address _sender = _msgSender();
addReceiverBalance(_sender, amount);
setReceiverRewardPerEpoch(rewardPerEpoch);
}
}
12 changes: 9 additions & 3 deletions contracts/staking/interfaces/IClusterRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ pragma solidity ^0.8.0;
import "./IClusterSelector.sol";

interface IClusterRewards {
struct ReceiverPayment {
uint128 rewardRemaining;
uint128 rewardPerEpoch;
}
function clusterSelectors(bytes32 networkId) external returns (IClusterSelector);
function clusterRewards(address cluster) external returns(uint256);
function rewardWeight(bytes32 networkId) external returns(uint256);
function totalRewardsPerEpoch() external returns(uint256);
function addNetwork(bytes32 networkId, uint256 rewardWeight, address clusterSelector) external;
function addNetwork(bytes32 networkId, uint256 _rewardWeight, address clusterSelector) external;
function removeNetwork(bytes32 networkId) external;
function updateNetwork(bytes32 networkId, uint256 updatedRewardWeight, address updatedClusterSelector) external;
function getRewardForEpoch(uint256 epoch, bytes32 networkId) external view returns(uint256);
function getRewardForEpoch(bytes32 networkId) external view returns(uint256);
function claimReward(address cluster) external returns(uint256);
function changeRewardPerEpoch(uint256 updatedRewardPerEpoch) external;
}
function _increaseReceiverBalance(address receiver, uint128 amount) external;
function _setReceiverRewardPerEpoch(address signer, uint128 rewardPerEpoch) external;
}
Loading