Skip to content

Commit

Permalink
Merge pull request #67 from valory-xyz/dispenser
Browse files Browse the repository at this point in the history
feat and refator: introducing dispenser contract
  • Loading branch information
DavidMinarsch authored Apr 15, 2022
2 parents 77c474f + 74464aa commit b96d827
Show file tree
Hide file tree
Showing 20 changed files with 1,185 additions and 130 deletions.
16 changes: 11 additions & 5 deletions contracts/Depository.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,17 @@ contract Depository is IErrors, Ownable {
uint256 sold;
}

// OLA interface
// OLA token address
address public immutable ola;
// Treasury interface
// Treasury address
address public treasury;
// Tokenomics interface
// Tokenomics address
address public tokenomics;
// Mapping of user address => list of bonds
mapping(address => Bond[]) public mapUserBonds;
// Map of token address => bond products they are present
mapping(address => Product[]) public mapTokenProducts;

// TODO later fix government / manager

constructor(address _ola, address _treasury, address _tokenomics) {
ola = _ola;
treasury = _treasury;
Expand Down Expand Up @@ -122,6 +121,9 @@ contract Depository is IErrors, Ownable {
mapUserBonds[user].push(Bond(payout, uint256(block.timestamp), expiry, productId, false));
emit CreateBond(productId, payout, tokenAmount);

// Take into account this bond in current epoch
ITokenomics(tokenomics).usedBond(payout);

// Transfer tokens to the depository
product.token.safeTransferFrom(msg.sender, address(this), tokenAmount);
// Approve treasury for the specified token amount
Expand Down Expand Up @@ -203,6 +205,10 @@ contract Depository is IErrors, Ownable {
/// @return productId New bond product Id.
function create(IERC20 token, uint256 supply, uint256 vesting) external onlyOwner returns (uint256 productId) {
// Create a new product.
if(!ITokenomics(tokenomics).allowedNewBond(supply)) {
// fixed later to correct revert
revert AmountLowerThan(ITokenomics(tokenomics).getBondLeft(), supply);
}
productId = mapTokenProducts[address(token)].length;
Product memory product = Product(token, supply, vesting, uint256(block.timestamp + vesting), 0, 0);
mapTokenProducts[address(token)].push(product);
Expand Down
194 changes: 194 additions & 0 deletions contracts/Dispenser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./governance/VotingEscrow.sol";
import "./interfaces/IErrors.sol";
import "./interfaces/IStructs.sol";
import "./interfaces/ITreasury.sol";
import "./interfaces/ITokenomics.sol";


/// @title Dispenser - Smart contract for rewards
/// @author AL
contract Dispenser is IErrors, IStructs, Ownable, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;

event VotingEscrowUpdated(address ve);
event TreasuryUpdated(address treasury);
event TokenomicsUpdated(address tokenomics);

// OLA token address
address public immutable ola;
// Voting Escrow address
address public ve;
// Treasury address
address public treasury;
// Tokenomics address
address public tokenomics;
// Mapping of owner of component / agent address => reward amount
mapping(address => uint256) public mapOwnerRewards;
// Mapping of staker address => reward amount
mapping(address => uint256) public mapStakerRewards;

constructor(address _ola, address _ve, address _treasury, address _tokenomics) {
ola = _ola;
ve = _ve;
treasury = _treasury;
tokenomics = _tokenomics;
}

// Only treasury has a privilege to manipulate a dispenser
modifier onlyTreasury() {
if (treasury != msg.sender) {
revert ManagerOnly(msg.sender, treasury);
}
_;
}

// Only voting escrow has a privilege to manipulate a dispenser
modifier onlyVotingEscrow() {
if (ve != msg.sender) {
revert ManagerOnly(msg.sender, ve);
}
_;
}

function changeVotingEscrow(address newVE) external onlyOwner {
ve = newVE;
emit VotingEscrowUpdated(newVE);
}

function changeTreasury(address newTreasury) external onlyOwner {
treasury = newTreasury;
emit TreasuryUpdated(newTreasury);
}

function changeTokenomics(address newTokenomics) external onlyOwner {
tokenomics = newTokenomics;
emit TokenomicsUpdated(newTokenomics);
}

/// @dev Distributes rewards between component and agent owners.
function _distributeOwnerRewards(uint256 componentReward, uint256 agentReward) internal {
uint256 componentRewardLeft = componentReward;
uint256 agentRewardLeft = agentReward;

// Get components owners and their UCFc-s
(address[] memory profitableComponents, uint256[] memory ucfcs) =
ITokenomics(tokenomics).getProfitableComponents();

uint256 numComponents = profitableComponents.length;
uint256 sumProfits;
if (numComponents > 0) {
// Calculate overall profits of UCFc-s
for (uint256 i = 0; i < numComponents; ++i) {
sumProfits += ucfcs[i];
}

// Calculate reward per component owner
for (uint256 i = 0; i < numComponents; ++i) {
uint256 rewardPerComponent = componentReward * ucfcs[i] / sumProfits;
// If there is a rounding error, floor to the correct value
if (rewardPerComponent > componentRewardLeft) {
rewardPerComponent = componentRewardLeft;
}
componentRewardLeft -= rewardPerComponent;
mapOwnerRewards[profitableComponents[i]] += rewardPerComponent;
}
}

// Get components owners and their UCFa-s
(address[] memory profitableAgents, uint256[] memory ucfas) = ITokenomics(tokenomics).getProfitableAgents();
uint256 numAgents = profitableAgents.length;
if (numAgents > 0) {
// Calculate overall profits of UCFa-s
sumProfits = 0;
for (uint256 i = 0; i < numAgents; ++i) {
sumProfits += ucfas[i];
}

uint256 rewardPerAgent;
for (uint256 i = 0; i < numAgents; ++i) {
rewardPerAgent = agentReward * ucfas[i] / sumProfits;
// If there is a rounding error, floor to the correct value
if (rewardPerAgent > agentRewardLeft) {
rewardPerAgent = agentRewardLeft;
}
agentRewardLeft -= rewardPerAgent;
mapOwnerRewards[profitableAgents[i]] += rewardPerAgent;
}
}
}

/// @dev Distributes rewards between stakers.
function _distributeStakerRewards(uint256 stakerReward) internal {
VotingEscrow veContract = VotingEscrow(ve);
address[] memory accounts = veContract.getLockAccounts();

// Get the overall amount of rewards for stakers
uint256 rewardLeft = stakerReward;

// Iterate over staker addresses and distribute
uint256 numAccounts = accounts.length;
uint256 supply = veContract.totalSupply();
if (supply > 0) {
for (uint256 i = 0; i < numAccounts; ++i) {
uint256 balance = veContract.balanceOf(accounts[i]);
// Reward for this specific staker
uint256 reward = stakerReward * balance / supply;

// If there is a rounding error, floor to the correct value
if (reward > rewardLeft) {
reward = rewardLeft;
}
rewardLeft -= reward;
mapStakerRewards[accounts[i]] += reward;
}
}
}

/// @dev Distributes rewards.
function distributeRewards(
uint256 stakerReward,
uint256 componentReward,
uint256 agentReward
) external onlyTreasury whenNotPaused
{
// Distribute rewards between component and agent owners
_distributeOwnerRewards(componentReward, agentReward);

// Distribute rewards for stakers
_distributeStakerRewards(stakerReward);
}

/// @dev Withdraws rewards for owners of components / agents.
function withdrawOwnerRewards() external nonReentrant {
uint256 balance = mapOwnerRewards[msg.sender];
if (balance > 0) {
mapOwnerRewards[msg.sender] = 0;
IERC20(ola).safeTransfer(msg.sender, balance);
}
}

/// @dev Withdraws rewards for stakers.
/// @param account Account address.
/// @return balance Reward balance.
function withdrawStakingRewards(address account) external onlyVotingEscrow returns (uint256 balance) {
balance = mapStakerRewards[account];
if (balance > 0) {
mapStakerRewards[account] = 0;
IERC20(ola).safeTransfer(ve, balance);
}
}

/// @dev Gets the paused state.
/// @return True, if paused.
function isPaused() external returns (bool) {
return paused();
}
}
Loading

0 comments on commit b96d827

Please sign in to comment.