Created
October 1, 2021 03:04
-
-
Save kyriediculous/83f1f454d95c8c8c8cabf366cbd0dbae to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.8+commit.dddeac2f.js&optimize=false&runs=200&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.8.8; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
interface IRoundsManager { | |
// Events | |
event NewRound(uint256 indexed round, bytes32 blockHash); | |
// Deprecated events | |
// These event signatures can be used to construct the appropriate topic hashes to filter for past logs corresponding | |
// to these deprecated events. | |
// event NewRound(uint256 round) | |
// External functions | |
function initializeRound() external; | |
function lipUpgradeRound(uint256 _lip) external view returns (uint256); | |
// Public functions | |
function blockNum() external view returns (uint256); | |
function blockHash(uint256 _block) external view returns (bytes32); | |
function blockHashForRound(uint256 _round) external view returns (bytes32); | |
function currentRound() external view returns (uint256); | |
function currentRoundStartBlock() external view returns (uint256); | |
function currentRoundInitialized() external view returns (bool); | |
function currentRoundLocked() external view returns (bool); | |
} | |
interface IMinter { | |
// 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 payable _to, uint256 _amount) external; | |
function depositETH() external payable returns (bool); | |
function setCurrentRewardTokens() external; | |
function currentMintableTokens() external view returns (uint256); | |
function currentMintedTokens() external view returns (uint256); | |
function getController() external view returns (IController); | |
} | |
interface ILivepeerToken is IERC20 { | |
function mint(address _to, uint256 _amount) external returns (bool); | |
function burn(uint256 _amount) external; | |
function transferOwnership(address newOwner) external; | |
} | |
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() public onlyOwner whenNotPaused { | |
paused = true; | |
emit Pause(); | |
} | |
/** | |
* @dev called by the owner to unpause, returns to normal state | |
*/ | |
function unpause() public onlyOwner whenPaused { | |
paused = false; | |
emit Unpause(); | |
} | |
} | |
abstract contract IController is Pausable { | |
event SetContractInfo(bytes32 id, address contractAddress, bytes20 gitCommitHash); | |
function setContractInfo( | |
bytes32 _id, | |
address _contractAddress, | |
bytes20 _gitCommitHash | |
) external virtual; | |
function updateController(bytes32 _id, address _controller) external virtual; | |
function getContract(bytes32 _id) external view virtual returns (address); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.8; | |
library MathUtils { | |
// Divisor used for representing percentages | |
uint256 public constant PERC_DIVISOR = 10**27; | |
/** | |
* @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 * percPoints(_fracNum, _fracDenom)) / 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 * _fracNum) / 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 * PERC_DIVISOR) / _fracDenom; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.8.8; | |
import "./MathUtils.sol"; | |
import "./Interfaces.sol"; | |
contract StakingManager { | |
IController controller; | |
struct Accumulators { | |
uint256 rewardPerShare; | |
uint256 feePerShare; | |
} | |
struct Delegation { | |
uint256 shares; | |
uint256 pendingRewards; | |
uint256 pendingFees; | |
uint256 lastUpdateRound; | |
uint256 rewardPerShareCheckpoint; | |
uint256 feePerShareCheckpoint; | |
uint256 lookbackShares; | |
} | |
struct DelegationPool { | |
uint256 shares; | |
uint256 nextShares; | |
uint256 principle; | |
uint256 stake; | |
uint256 nextStake; | |
uint128 rewardCut; | |
uint128 feeCut; | |
uint256 lastRewardRound; | |
uint256 lastFeeRound; | |
uint256 lastUpdateRound; | |
mapping(uint256 => Accumulators) accumulators; | |
mapping (address => Delegation) delegations; | |
} | |
function _delegate(DelegationPool storage _pool, Delegation storage _delegation, uint256 _amount) internal { | |
_updatePool(_pool); | |
uint256 shares = _tokensToShares(_pool, _amount); | |
_updateDelegation(_pool, _delegation, int256(shares)); | |
_pool.nextShares += shares; | |
_pool.nextStake += _amount; | |
_pool.principle += _amount; | |
} | |
function _undelegate(DelegationPool storage _pool, Delegation storage _delegation, uint256 _amount) internal { | |
_updatePool(_pool); | |
uint256 shares = _tokensToShares(_pool, _amount); | |
_updateDelegation(_pool, _delegation, -int256(shares)); | |
_pool.nextShares -= shares; | |
_pool.nextStake -= _amount; | |
_pool.principle -= _amount; | |
} | |
function _addRewards(DelegationPool storage _pool, uint256 _amount) internal { | |
_updatePool(_pool); | |
uint256 currentRound = roundsManager().currentRound(); | |
uint256 _shares = _pool.shares; | |
if (_shares > 0) { | |
_pool.accumulators[currentRound].rewardPerShare += MathUtils.percPoints(_amount, _shares); | |
} | |
_pool.lastRewardRound = currentRound; | |
_pool.nextStake += _amount; | |
} | |
function _addFees(DelegationPool storage _pool, uint256 _amount) internal { | |
_updatePool(_pool); | |
uint256 currentRound = roundsManager().currentRound(); | |
uint256 _shares = _pool.shares; | |
if (_shares > 0) { | |
_pool.accumulators[currentRound].feePerShare += MathUtils.percPoints(_amount, _shares); | |
} | |
_pool.lastFeeRound = currentRound; | |
} | |
function _updatePool(DelegationPool storage _pool) internal { | |
uint256 currentRound = roundsManager().currentRound(); | |
uint256 _lastUpdateRound = _pool.lastUpdateRound; | |
if (_lastUpdateRound >= currentRound) { | |
return; | |
} | |
_pool.lastUpdateRound = currentRound; | |
_pool.shares = _pool.nextShares; | |
_pool.stake = _pool.nextStake; | |
_pool.accumulators[currentRound] = _pool.accumulators[_lastUpdateRound]; | |
} | |
function _updateDelegation(DelegationPool storage _pool, Delegation storage _delegation, int256 _sharesDelta) internal { | |
uint256 currentRound = roundsManager().currentRound(); | |
// convert outstanding rewards to shares | |
uint256 rewards = _rewards(_pool, _delegation); | |
uint256 sharesDeltaFromRewards = _tokensToShares(_pool, rewards); | |
_pool.nextShares += sharesDeltaFromRewards; | |
_pool.principle += rewards; | |
// add to pending fees | |
_delegation.pendingFees += _fees(_pool, _delegation); | |
// checkpoint accumulators | |
_delegation.rewardPerShareCheckpoint = _pool.accumulators[_pool.lastRewardRound].rewardPerShare; | |
_delegation.feePerShareCheckpoint = _pool.accumulators[_pool.lastFeeRound].feePerShare; | |
// set eligible shares for current round | |
_delegation.lookbackShares = _delegation.shares; | |
// | |
_delegation.lastUpdateRound = currentRound; | |
_delegation.shares = _addDelta(_delegation.shares, _sharesDelta + int256(sharesDeltaFromRewards)); | |
} | |
function _rewards(DelegationPool storage _pool, Delegation storage _delegation) internal view returns (uint256 rewards) { | |
uint256 lookback = _pool.accumulators[_delegation.lastUpdateRound].rewardPerShare; | |
uint256 checkpoint = _delegation.rewardPerShareCheckpoint; | |
uint256 lookbackShares = _delegation.lookbackShares; | |
uint256 lookbackRewards; | |
if (lookbackShares > 0 ) { | |
lookbackRewards = MathUtils.percOf(lookbackShares, lookback - checkpoint); | |
} | |
uint256 otherRewards = MathUtils.percOf(_delegation.shares, _pool.accumulators[_pool.lastRewardRound].rewardPerShare - lookback); | |
rewards = lookbackRewards + otherRewards; | |
} | |
function _fees(DelegationPool storage _pool, Delegation storage _delegation) internal view returns (uint256 fees) { | |
uint256 lookback = _pool.accumulators[_delegation.lastUpdateRound].feePerShare; | |
uint256 checkpoint = _delegation.feePerShareCheckpoint; | |
uint256 lookbackShares = _delegation.lookbackShares; | |
uint256 lookbackFees; | |
if (lookbackShares > 0 ) { | |
lookbackFees = MathUtils.percOf(lookbackShares, lookback - checkpoint); | |
} | |
uint256 otherFees = MathUtils.percOf(_delegation.shares, _pool.accumulators[_pool.lastFeeRound].feePerShare - lookback); | |
fees = lookbackFees + otherFees; | |
} | |
function _stake(DelegationPool storage _pool, Delegation storage _delegation) internal view returns (uint256 stake) { | |
return MathUtils.percOf(_pool.principle, _delegation.shares, _pool.nextShares) + _rewards(_pool, _delegation); | |
} | |
function _tokensToShares(DelegationPool storage _pool, uint256 _tokens) internal view returns (uint256 shares) { | |
uint256 totalStake = _pool.nextStake; | |
uint256 totalShares = _pool.nextShares; | |
if (totalShares == 0) { | |
return _tokens; | |
} else if (totalStake == 0) { | |
return 0; | |
} else { | |
shares = MathUtils.percOf(_tokens, totalShares, totalStake); | |
} | |
} | |
function _sharesToTokens(DelegationPool storage _pool, uint256 _shares) internal view returns (uint256 tokens) { | |
uint256 totalShares = _pool.nextShares; | |
if (totalShares == 0) { | |
return 0; | |
} | |
tokens = MathUtils.percOf(_pool.nextStake, _shares, totalShares); | |
} | |
/** | |
* @dev Return LivepeerToken interface | |
* @return Livepeer token contract registered with Controller | |
*/ | |
function livepeerToken() internal view returns (ILivepeerToken) { | |
return ILivepeerToken(controller.getContract(keccak256("LivepeerToken"))); | |
} | |
/** | |
* @dev Return Minter interface | |
* @return Minter contract registered with Controller | |
*/ | |
function minter() internal view returns (IMinter) { | |
return IMinter(controller.getContract(keccak256("Minter"))); | |
} | |
/** | |
* @dev Return RoundsManager interface | |
* @return RoundsManager contract registered with Controller | |
*/ | |
function roundsManager() internal view returns (IRoundsManager) { | |
return IRoundsManager(controller.getContract(keccak256("RoundsManager"))); | |
} | |
function _onlyTicketBroker() internal view { | |
require(msg.sender == controller.getContract(keccak256("TicketBroker")), "caller must be TicketBroker"); | |
} | |
function _onlyRoundsManager() internal view { | |
require(msg.sender == controller.getContract(keccak256("RoundsManager")), "caller must be RoundsManager"); | |
} | |
function _onlyVerifier() internal view { | |
require(msg.sender == controller.getContract(keccak256("Verifier")), "caller must be Verifier"); | |
} | |
function _currentRoundInitialized() internal view { | |
require(roundsManager().currentRoundInitialized(), "current round is not initialized"); | |
} | |
function _addDelta(uint256 _x, int256 _y) internal pure returns (uint256 z) { | |
if (_y < 0) { | |
z = _x - uint256(-_y); | |
} else { | |
z = _x + uint256(_y); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment