Skip to content

Instantly share code, notes, and snippets.

@kyriediculous
Created October 1, 2021 11:35
Show Gist options
  • Save kyriediculous/7984ea9965194869a5c98f0a1a55d23f to your computer and use it in GitHub Desktop.
Save kyriediculous/7984ea9965194869a5c98f0a1a55d23f 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.9+commit.e5eed63a.js&optimize=false&runs=200&gist=
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);
}
// 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;
}
}
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 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; // Q : can we remove and use lastUpdateRound ?
uint256 lastFeeRound; // Q: can we remove and use lastUpdateRound ?
uint256 lastUpdateRound;
address owner;
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.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.nextStake -= _amount;
_pool.principle -= _amount;
}
function _addRewards(DelegationPool storage _pool, uint256 _amount) internal {
_updatePool(_pool);
// calculate reward cut
uint256 rewardCutAmount = MathUtils.percOf(_amount, _pool.rewardCut);
uint256 rewardShareAmount = _amount - rewardCutAmount;
uint256 currentRound = roundsManager().currentRound();
uint256 _shares = _pool.shares;
if (_shares > 0) {
_pool.accumulators[currentRound].rewardPerShare += MathUtils.percPoints(rewardShareAmount, _shares);
}
// convert rewardCutAmount to shares for pool owner (orchestrator)
// TODO: double check this works
_updateDelegation(_pool, _pool.delegations[_pool.owner], int256(rewardCutAmount));
_pool.lastRewardRound = currentRound;
_pool.nextStake += _amount;
}
function _addFees(DelegationPool storage _pool, uint256 _amount) internal {
_updatePool(_pool);
// calculate fee cut
uint256 feeCutAmount = MathUtils.percOf(_amount, _pool.feeCut);
uint256 feeShareAmount = _amount - feeCutAmount;
uint256 currentRound = roundsManager().currentRound();
uint256 _shares = _pool.shares;
if (_shares > 0) {
_pool.accumulators[currentRound].feePerShare += MathUtils.percPoints(feeShareAmount, _shares);
}
// add feeCutAmount to fees for Pool owner (orchestrator)
_pool.delegations[_pool.owner].pendingFees += feeCutAmount;
_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.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;
int256 totalSharesDelta = _sharesDelta + int256(sharesDeltaFromRewards);
_delegation.shares = _addDelta(_delegation.shares, totalSharesDelta);
// update nextShares
_pool.nextShares = _addDelta(_pool.nextShares, totalSharesDelta);
}
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) {
// principle * ( share / totalShares ) + rewards
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