/**
*Submitted for verification at Etherscan.io on 2018-04-30
*/
pragma solidity 0.4.18;
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
library MathUtils {
using SafeMath for uint256;
// Divisor used for representing percentages
uint256 public constant PERC_DIVISOR = 1000000;
/*
* @dev Returns whether an amount is a valid percentage out of PERC_DIVISOR
* @param _amount Amount that is supposed to be a percentage
*/
function validPerc(uint256 _amount) internal pure returns (bool) {
return _amount <= PERC_DIVISOR;
}
/*
* @dev Compute percentage of a value with the percentage represented by a fraction
* @param _amount Amount to take the percentage of
* @param _fracNum Numerator of fraction representing the percentage
* @param _fracDenom Denominator of fraction representing the percentage
*/
function percOf(uint256 _amount, uint256 _fracNum, uint256 _fracDenom) internal pure returns (uint256) {
return _amount.mul(percPoints(_fracNum, _fracDenom)).div(PERC_DIVISOR);
}
/*
* @dev Compute percentage of a value with the percentage represented by a fraction over PERC_DIVISOR
* @param _amount Amount to take the percentage of
* @param _fracNum Numerator of fraction representing the percentage with PERC_DIVISOR as the denominator
*/
function percOf(uint256 _amount, uint256 _fracNum) internal pure returns (uint256) {
return _amount.mul(_fracNum).div(PERC_DIVISOR);
}
/*
* @dev Compute percentage representation of a fraction
* @param _fracNum Numerator of fraction represeting the percentage
* @param _fracDenom Denominator of fraction represeting the percentage
*/
function percPoints(uint256 _fracNum, uint256 _fracDenom) internal pure returns (uint256) {
return _fracNum.mul(PERC_DIVISOR).div(_fracDenom);
}
}
contract ILivepeerToken is ERC20, Ownable {
function mint(address _to, uint256 _amount) public returns (bool);
function burn(uint256 _amount) public;
}
contract IController is Pausable {
event SetContractInfo(bytes32 id, address contractAddress, bytes20 gitCommitHash);
function setContractInfo(bytes32 _id, address _contractAddress, bytes20 _gitCommitHash) external;
function updateController(bytes32 _id, address _controller) external;
function getContract(bytes32 _id) public view returns (address);
}
contract IManager {
event SetController(address controller);
event ParameterUpdate(string param);
function setController(address _controller) external;
}
contract Manager is IManager {
// Controller that contract is registered with
IController public controller;
// Check if sender is controller
modifier onlyController() {
require(msg.sender == address(controller));
_;
}
// Check if sender is controller owner
modifier onlyControllerOwner() {
require(msg.sender == controller.owner());
_;
}
// Check if controller is not paused
modifier whenSystemNotPaused() {
require(!controller.paused());
_;
}
// Check if controller is paused
modifier whenSystemPaused() {
require(controller.paused());
_;
}
function Manager(address _controller) public {
controller = IController(_controller);
}
/*
* @dev Set controller. Only callable by current controller
* @param _controller Controller contract address
*/
function setController(address _controller) external onlyController {
controller = IController(_controller);
SetController(_controller);
}
}
/*
* @title Interface for BondingManager
*/
contract IBondingManager {
event TranscoderUpdate(address indexed transcoder, uint256 pendingRewardCut, uint256 pendingFeeShare, uint256 pendingPricePerSegment, bool registered);
event TranscoderEvicted(address indexed transcoder);
event TranscoderResigned(address indexed transcoder);
event TranscoderSlashed(address indexed transcoder, address finder, uint256 penalty, uint256 finderReward);
event Reward(address indexed transcoder, uint256 amount);
event Bond(address indexed delegate, address indexed delegator);
event Unbond(address indexed delegate, address indexed delegator);
event WithdrawStake(address indexed delegator);
event WithdrawFees(address indexed delegator);
// External functions
function setActiveTranscoders() external;
function updateTranscoderWithFees(address _transcoder, uint256 _fees, uint256 _round) external;
function slashTranscoder(address _transcoder, address _finder, uint256 _slashAmount, uint256 _finderFee) external;
function electActiveTranscoder(uint256 _maxPricePerSegment, bytes32 _blockHash, uint256 _round) external view returns (address);
// Public functions
function transcoderTotalStake(address _transcoder) public view returns (uint256);
function activeTranscoderTotalStake(address _transcoder, uint256 _round) public view returns (uint256);
function isRegisteredTranscoder(address _transcoder) public view returns (bool);
function getTotalBonded() public view returns (uint256);
}
/**
* @title RoundsManager interface
*/
contract IRoundsManager {
// Events
event NewRound(uint256 round);
// External functions
function initializeRound() external;
// Public functions
function blockNum() public view returns (uint256);
function blockHash(uint256 _block) public view returns (bytes32);
function currentRound() public view returns (uint256);
function currentRoundStartBlock() public view returns (uint256);
function currentRoundInitialized() public view returns (bool);
function currentRoundLocked() public view returns (bool);
}
/**
* @title Minter interface
*/
contract IMinter {
// Events
event SetCurrentRewardTokens(uint256 currentMintableTokens, uint256 currentInflation);
// External functions
function createReward(uint256 _fracNum, uint256 _fracDenom) external returns (uint256);
function trustedTransferTokens(address _to, uint256 _amount) external;
function trustedBurnTokens(uint256 _amount) external;
function trustedWithdrawETH(address _to, uint256 _amount) external;
function depositETH() external payable returns (bool);
function setCurrentRewardTokens() external;
// Public functions
function getController() public view returns (IController);
}
/**
* @title Minter
* @dev Manages inflation rate and the minting of new tokens for each round of the Livepeer protocol
*/
contract Minter is Manager, IMinter {
using SafeMath for uint256;
// Per round inflation rate
uint256 public inflation;
// Change in inflation rate per round until the target bonding rate is achieved
uint256 public inflationChange;
// Target bonding rate
uint256 public targetBondingRate;
// Current number of mintable tokens. Reset every round
uint256 public currentMintableTokens;
// Current number of minted tokens. Reset every round
uint256 public currentMintedTokens;
// Checks if caller is BondingManager
modifier onlyBondingManager() {
require(msg.sender == controller.getContract(keccak256("BondingManager")));
_;
}
// Checks if caller is RoundsManager
modifier onlyRoundsManager() {
require(msg.sender == controller.getContract(keccak256("RoundsManager")));
_;
}
// Checks if caller is either BondingManager or JobsManager
modifier onlyBondingManagerOrJobsManager() {
require(msg.sender == controller.getContract(keccak256("BondingManager")) || msg.sender == controller.getContract(keccak256("JobsManager")));
_;
}
// Checks if caller is either the currently registered Minter or JobsManager
modifier onlyMinterOrJobsManager() {
require(msg.sender == controller.getContract(keccak256("Minter")) || msg.sender == controller.getContract(keccak256("JobsManager")));
_;
}
/**
* @dev Minter constructor
* @param _inflation Base inflation rate as a percentage of current total token supply
* @param _inflationChange Change in inflation rate each round (increase or decrease) if target bonding rate is not achieved
* @param _targetBondingRate Target bonding rate as a percentage of total bonded tokens / total token supply
*/
function Minter(address _controller, uint256 _inflation, uint256 _inflationChange, uint256 _targetBondingRate) public Manager(_controller) {
// Inflation must be valid percentage
require(MathUtils.validPerc(_inflation));
// Inflation change must be valid percentage
require(MathUtils.validPerc(_inflationChange));
// Target bonding rate must be valid percentage
require(MathUtils.validPerc(_targetBondingRate));
inflation = _inflation;
inflationChange = _inflationChange;
targetBondingRate = _targetBondingRate;
}
/**
* @dev Set targetBondingRate. Only callable by Controller owner
* @param _targetBondingRate Target bonding rate as a percentage of total bonded tokens / total token supply
*/
function setTargetBondingRate(uint256 _targetBondingRate) external onlyControllerOwner {
// Must be valid percentage
require(MathUtils.validPerc(_targetBondingRate));
targetBondingRate = _targetBondingRate;
ParameterUpdate("targetBondingRate");
}
/**
* @dev Set inflationChange. Only callable by Controller owner
* @param _inflationChange Inflation change as a percentage of total token supply
*/
function setInflationChange(uint256 _inflationChange) external onlyControllerOwner {
// Must be valid percentage
require(MathUtils.validPerc(_inflationChange));
inflationChange = _inflationChange;
ParameterUpdate("inflationChange");
}
/**
* @dev Migrate to a new Minter by transferring ownership of the token as well
* as the current Minter's token balance to the new Minter. Only callable by Controller when system is paused
* @param _newMinter Address of new Minter
*/
function migrateToNewMinter(IMinter _newMinter) external onlyControllerOwner whenSystemPaused {
// New Minter cannot be the current Minter
require(_newMinter != this);
// Check for null address
require(address(_newMinter) != address(0));
IController newMinterController = _newMinter.getController();
// New Minter must have same Controller as current Minter
require(newMinterController == controller);
// New Minter's Controller must have the current Minter registered
require(newMinterController.getContract(keccak256("Minter")) == address(this));
// Transfer ownership of token to new Minter
livepeerToken().transferOwnership(_newMinter);
// Transfer current Minter's token balance to new Minter
livepeerToken().transfer(_newMinter, livepeerToken().balanceOf(this));
// Transfer current Minter's ETH balance to new Minter
_newMinter.depositETH.value(this.balance)();
}
/**
* @dev Create reward based on a fractional portion of the mintable tokens for the current round
* @param _fracNum Numerator of fraction (active transcoder's stake)
* @param _fracDenom Denominator of fraction (total active stake)
*/
function createReward(uint256 _fracNum, uint256 _fracDenom) external onlyBondingManager whenSystemNotPaused returns (uint256) {
// Compute and mint fraction of mintable tokens to include in reward
uint256 mintAmount = MathUtils.percOf(currentMintableTokens, _fracNum, _fracDenom);
// Update amount of minted tokens for round
currentMintedTokens = currentMintedTokens.add(mintAmount);
// Minted tokens must not exceed mintable tokens
require(currentMintedTokens <= currentMintableTokens);
// Mint new tokens
livepeerToken().mint(this, mintAmount);
// Reward = minted tokens
return mintAmount;
}
/**
* @dev Transfer tokens to a receipient. Only callable by BondingManager - always trusts BondingManager
* @param _to Recipient address
* @param _amount Amount of tokens
*/
function trustedTransferTokens(address _to, uint256 _amount) external onlyBondingManager whenSystemNotPaused {
livepeerToken().transfer(_to, _amount);
}
/**
* @dev Burn tokens. Only callable by BondingManager - always trusts BondingManager
* @param _amount Amount of tokens to burn
*/
function trustedBurnTokens(uint256 _amount) external onlyBondingManager whenSystemNotPaused {
livepeerToken().burn(_amount);
}
/**
* @dev Withdraw ETH to a recipient. Only callable by BondingManager or JobsManager - always trusts these two contracts
* @param _to Recipient address
* @param _amount Amount of ETH
*/
function trustedWithdrawETH(address _to, uint256 _amount) external onlyBondingManagerOrJobsManager whenSystemNotPaused {
_to.transfer(_amount);
}
/**
* @dev Deposit ETH to this contract. Only callable by the currently registered Minter or JobsManager
*/
function depositETH() external payable onlyMinterOrJobsManager whenSystemNotPaused returns (bool) {
return true;
}
/**
* @dev Set inflation and mintable tokens for the round. Only callable by the RoundsManager
*/
function setCurrentRewardTokens() external onlyRoundsManager whenSystemNotPaused {
setInflation();
// Set mintable tokens based upon current inflation and current total token supply
currentMintableTokens = MathUtils.percOf(livepeerToken().totalSupply(), inflation);
currentMintedTokens = 0;
SetCurrentRewardTokens(currentMintableTokens, inflation);
}
/**
* @dev Returns Controller interface
*/
function getController() public view returns (IController) {
return controller;
}
/**
* @dev Set inflation based upon the current bonding rate and target bonding rate
*/
function setInflation() internal {
uint256 currentBondingRate = 0;
uint256 totalSupply = livepeerToken().totalSupply();
if (totalSupply > 0) {
uint256 totalBonded = bondingManager().getTotalBonded();
currentBondingRate = MathUtils.percPoints(totalBonded, totalSupply);
}
if (currentBondingRate < targetBondingRate) {
// Bonding rate is below the target - increase inflation
inflation = inflation.add(inflationChange);
} else if (currentBondingRate > targetBondingRate) {
// Bonding rate is above the target - decrease inflation
if (inflationChange > inflation) {
inflation = 0;
} else {
inflation = inflation.sub(inflationChange);
}
}
}
/**
* @dev Returns LivepeerToken interface
*/
function livepeerToken() internal view returns (ILivepeerToken) {
return ILivepeerToken(controller.getContract(keccak256("LivepeerToken")));
}
/**
* @dev Returns BondingManager interface
*/
function bondingManager() internal view returns (IBondingManager) {
return IBondingManager(controller.getContract(keccak256("BondingManager")));
}
}
{
"compilationTarget": {
"Minter.sol": "Minter"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"_inflationChange","type":"uint256"}],"name":"setInflationChange","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newMinter","type":"address"}],"name":"migrateToNewMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"trustedWithdrawETH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentMintedTokens","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getController","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_targetBondingRate","type":"uint256"}],"name":"setTargetBondingRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_fracNum","type":"uint256"},{"name":"_fracDenom","type":"uint256"}],"name":"createReward","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"targetBondingRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_controller","type":"address"}],"name":"setController","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentMintableTokens","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"inflationChange","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"inflation","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"trustedBurnTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"trustedTransferTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"setCurrentRewardTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositETH","outputs":[{"name":"","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"controller","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_controller","type":"address"},{"name":"_inflation","type":"uint256"},{"name":"_inflationChange","type":"uint256"},{"name":"_targetBondingRate","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"currentMintableTokens","type":"uint256"},{"indexed":false,"name":"currentInflation","type":"uint256"}],"name":"SetCurrentRewardTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"controller","type":"address"}],"name":"SetController","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"param","type":"string"}],"name":"ParameterUpdate","type":"event"}]