pragma solidity ^0.5.16; // Inheritance import "./Owned.sol"; import "./interfaces/IRewardsDistribution.sol"; // Libraires import "./SafeDecimalMath.sol"; // Internal references import "./interfaces/IERC20.sol"; import "./interfaces/IFeePool.sol"; // https://docs.oikos.cash/contracts/RewardsDistribution contract RewardsDistribution is Owned, IRewardsDistribution { using SafeMath for uint; using SafeDecimalMath for uint; /** * @notice Authorised address able to call distributeRewards */ address public authority; /** * @notice Address of the Oikos ProxyERC20 */ address public oikosProxy; /** * @notice Address of the RewardEscrow contract */ address public rewardEscrow; /** * @notice Address of the FeePoolProxy */ address public feePoolProxy; /** * @notice Stores an address and amount * of the inflationary supply to sent to the address. */ struct DistributionData { address destination; uint amount; } /** * @notice An array of addresses and amounts to send */ DistributionData[] public distributions; /** * @dev _authority maybe the underlying oikos contract. * Remember to set the autority on a oikos upgrade */ constructor( address _owner, address _authority, address _oikosProxy, address _rewardEscrow, address _feePoolProxy ) public Owned(_owner) { authority = _authority; oikosProxy = _oikosProxy; rewardEscrow = _rewardEscrow; feePoolProxy = _feePoolProxy; } // ========== EXTERNAL SETTERS ========== function setOikosProxy(address _oikosProxy) external onlyOwner { oikosProxy = _oikosProxy; } function setRewardEscrow(address _rewardEscrow) external onlyOwner { rewardEscrow = _rewardEscrow; } function setFeePoolProxy(address _feePoolProxy) external onlyOwner { feePoolProxy = _feePoolProxy; } /** * @notice Set the address of the contract authorised to call distributeRewards() * @param _authority Address of the authorised calling contract. */ function setAuthority(address _authority) external onlyOwner { authority = _authority; } // ========== EXTERNAL FUNCTIONS ========== /** * @notice Adds a Rewards DistributionData struct to the distributions * array. Any entries here will be iterated and rewards distributed to * each address when tokens are sent to this contract and distributeRewards() * is called by the autority. * @param destination An address to send rewards tokens too * @param amount The amount of rewards tokens to send */ function addRewardDistribution(address destination, uint amount) external onlyOwner returns (bool) { require(destination != address(0), "Cant add a zero address"); require(amount != 0, "Cant add a zero amount"); DistributionData memory rewardsDistribution = DistributionData(destination, amount); distributions.push(rewardsDistribution); emit RewardDistributionAdded(distributions.length - 1, destination, amount); return true; } /** * @notice Deletes a RewardDistribution from the distributions * so it will no longer be included in the call to distributeRewards() * @param index The index of the DistributionData to delete */ function removeRewardDistribution(uint index) external onlyOwner { require(index <= distributions.length - 1, "index out of bounds"); // shift distributions indexes across for (uint i = index; i < distributions.length - 1; i++) { distributions[i] = distributions[i + 1]; } distributions.length--; // Since this function must shift all later entries down to fill the // gap from the one it removed, it could in principle consume an // unbounded amount of gas. However, the number of entries will // presumably always be very low. } /** * @notice Edits a RewardDistribution in the distributions array. * @param index The index of the DistributionData to edit * @param destination The destination address. Send the same address to keep or different address to change it. * @param amount The amount of tokens to edit. Send the same number to keep or change the amount of tokens to send. */ function editRewardDistribution( uint index, address destination, uint amount ) external onlyOwner returns (bool) { require(index <= distributions.length - 1, "index out of bounds"); distributions[index].destination = destination; distributions[index].amount = amount; return true; } function distributeRewards(uint amount) external returns (bool) { require(amount > 0, "Nothing to distribute"); require(msg.sender == authority, "Caller is not authorised"); require(rewardEscrow != address(0), "RewardEscrow is not set"); require(oikosProxy != address(0), "OikosProxy is not set"); require(feePoolProxy != address(0), "FeePoolProxy is not set"); require( IERC20(oikosProxy).balanceOf(address(this)) >= amount, "RewardsDistribution contract does not have enough tokens to distribute" ); uint remainder = amount; // Iterate the array of distributions sending the configured amounts for (uint i = 0; i < distributions.length; i++) { if (distributions[i].destination != address(0) || distributions[i].amount != 0) { remainder = remainder.sub(distributions[i].amount); // Transfer the OKS IERC20(oikosProxy).transfer(distributions[i].destination, distributions[i].amount); // If the contract implements RewardsDistributionRecipient.sol, inform it how many OKS its received. bytes memory payload = abi.encodeWithSignature("notifyRewardAmount(uint256)", distributions[i].amount); // solhint-disable avoid-low-level-calls (bool success, ) = distributions[i].destination.call(payload); if (!success) { // Note: we're ignoring the return value as it will fail for contracts that do not implement RewardsDistributionRecipient.sol } } } // After all ditributions have been sent, send the remainder to the RewardsEscrow contract IERC20(oikosProxy).transfer(rewardEscrow, remainder); // Tell the FeePool how much it has to distribute to the stakers IFeePool(feePoolProxy).setRewardsToDistribute(remainder); emit RewardsDistributed(amount); return true; } /* ========== VIEWS ========== */ /** * @notice Retrieve the length of the distributions array */ function distributionsLength() external view returns (uint) { return distributions.length; } /* ========== Events ========== */ event RewardDistributionAdded(uint index, address destination, uint amount); event RewardsDistributed(uint amount); }