Contract Name:
OlympusStaking
Contract Source Code:
File 1 of 1 : OlympusStaking
// SPDX-License-Identifier: AGPL-3.0-or-later
// File: interfaces/IOlympusAuthority.sol
pragma solidity =0.7.5;
interface IOlympusAuthority {
/* ========== EVENTS ========== */
event GovernorPushed(address indexed from, address indexed to, bool _effectiveImmediately);
event GuardianPushed(address indexed from, address indexed to, bool _effectiveImmediately);
event PolicyPushed(address indexed from, address indexed to, bool _effectiveImmediately);
event VaultPushed(address indexed from, address indexed to, bool _effectiveImmediately);
event GovernorPulled(address indexed from, address indexed to);
event GuardianPulled(address indexed from, address indexed to);
event PolicyPulled(address indexed from, address indexed to);
event VaultPulled(address indexed from, address indexed to);
/* ========== VIEW ========== */
function governor() external view returns (address);
function guardian() external view returns (address);
function policy() external view returns (address);
function vault() external view returns (address);
}
// File: types/OlympusAccessControlled.sol
pragma solidity >=0.7.5;
abstract contract OlympusAccessControlled {
/* ========== EVENTS ========== */
event AuthorityUpdated(IOlympusAuthority indexed authority);
string UNAUTHORIZED = "UNAUTHORIZED"; // save gas
/* ========== STATE VARIABLES ========== */
IOlympusAuthority public authority;
/* ========== Constructor ========== */
constructor(IOlympusAuthority _authority) {
authority = _authority;
emit AuthorityUpdated(_authority);
}
/* ========== MODIFIERS ========== */
modifier onlyGovernor() {
require(msg.sender == authority.governor(), UNAUTHORIZED);
_;
}
modifier onlyGuardian() {
require(msg.sender == authority.guardian(), UNAUTHORIZED);
_;
}
modifier onlyPolicy() {
require(msg.sender == authority.policy(), UNAUTHORIZED);
_;
}
modifier onlyVault() {
require(msg.sender == authority.vault(), UNAUTHORIZED);
_;
}
/* ========== GOV ONLY ========== */
function setAuthority(IOlympusAuthority _newAuthority) external onlyGovernor {
authority = _newAuthority;
emit AuthorityUpdated(_newAuthority);
}
}
// File: interfaces/IDistributor.sol
pragma solidity >=0.7.5;
interface IDistributor {
function distribute() external;
function bounty() external view returns (uint256);
function retrieveBounty() external returns (uint256);
function nextRewardAt(uint256 _rate) external view returns (uint256);
function nextRewardFor(address _recipient) external view returns (uint256);
function setBounty(uint256 _bounty) external;
function addRecipient(address _recipient, uint256 _rewardRate) external;
function removeRecipient(uint256 _index) external;
function setAdjustment(
uint256 _index,
bool _add,
uint256 _rate,
uint256 _target
) external;
}
// File: interfaces/IERC20.sol
pragma solidity >=0.7.5;
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: interfaces/IgOHM.sol
pragma solidity >=0.7.5;
interface IgOHM is IERC20 {
function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external;
function index() external view returns (uint256);
function balanceFrom(uint256 _amount) external view returns (uint256);
function balanceTo(uint256 _amount) external view returns (uint256);
function migrate( address _staking, address _sOHM ) external;
}
// File: interfaces/IsOHM.sol
pragma solidity >=0.7.5;
interface IsOHM is IERC20 {
function rebase( uint256 ohmProfit_, uint epoch_) external returns (uint256);
function circulatingSupply() external view returns (uint256);
function gonsForBalance( uint amount ) external view returns ( uint );
function balanceForGons( uint gons ) external view returns ( uint );
function index() external view returns ( uint );
function toG(uint amount) external view returns (uint);
function fromG(uint amount) external view returns (uint);
function changeDebt(
uint256 amount,
address debtor,
bool add
) external;
function debtBalances(address _address) external view returns (uint256);
}
// File: libraries/SafeERC20.sol
pragma solidity >=0.7.5;
/// @notice Safe IERC20 and ETH transfer library that safely handles missing return values.
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/libraries/TransferHelper.sol)
/// Taken from Solmate
library SafeERC20 {
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");
}
function safeTransfer(
IERC20 token,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(IERC20.transfer.selector, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");
}
function safeApprove(
IERC20 token,
address to,
uint256 amount
) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(IERC20.approve.selector, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
}
function safeTransferETH(address to, uint256 amount) internal {
(bool success, ) = to.call{value: amount}(new bytes(0));
require(success, "ETH_TRANSFER_FAILED");
}
}
// File: libraries/SafeMath.sol
pragma solidity ^0.7.5;
// TODO(zx): Replace all instances of SafeMath with OZ implementation
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
// Only used in the BondingCalculator.sol
function sqrrt(uint256 a) internal pure returns (uint c) {
if (a > 3) {
c = a;
uint b = add( div( a, 2), 1 );
while (b < c) {
c = b;
b = div( add( div( a, b ), b), 2 );
}
} else if (a != 0) {
c = 1;
}
}
}
// File: Staking.sol
pragma solidity ^0.7.5;
contract OlympusStaking is OlympusAccessControlled {
/* ========== DEPENDENCIES ========== */
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for IsOHM;
using SafeERC20 for IgOHM;
/* ========== EVENTS ========== */
event DistributorSet(address distributor);
event WarmupSet(uint256 warmup);
/* ========== DATA STRUCTURES ========== */
struct Epoch {
uint256 length; // in seconds
uint256 number; // since inception
uint256 end; // timestamp
uint256 distribute; // amount
}
struct Claim {
uint256 deposit; // if forfeiting
uint256 gons; // staked balance
uint256 expiry; // end of warmup period
bool lock; // prevents malicious delays for claim
}
/* ========== STATE VARIABLES ========== */
IERC20 public immutable OHM;
IsOHM public immutable sOHM;
IgOHM public immutable gOHM;
Epoch public epoch;
IDistributor public distributor;
mapping(address => Claim) public warmupInfo;
uint256 public warmupPeriod;
uint256 private gonsInWarmup;
/* ========== CONSTRUCTOR ========== */
constructor(
address _ohm,
address _sOHM,
address _gOHM,
uint256 _epochLength,
uint256 _firstEpochNumber,
uint256 _firstEpochTime,
address _authority
) OlympusAccessControlled(IOlympusAuthority(_authority)) {
require(_ohm != address(0), "Zero address: OHM");
OHM = IERC20(_ohm);
require(_sOHM != address(0), "Zero address: sOHM");
sOHM = IsOHM(_sOHM);
require(_gOHM != address(0), "Zero address: gOHM");
gOHM = IgOHM(_gOHM);
epoch = Epoch({length: _epochLength, number: _firstEpochNumber, end: _firstEpochTime, distribute: 0});
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* @notice stake OHM to enter warmup
* @param _to address
* @param _amount uint
* @param _claim bool
* @param _rebasing bool
* @return uint
*/
function stake(
address _to,
uint256 _amount,
bool _rebasing,
bool _claim
) external returns (uint256) {
OHM.safeTransferFrom(msg.sender, address(this), _amount);
_amount = _amount.add(rebase()); // add bounty if rebase occurred
if (_claim && warmupPeriod == 0) {
return _send(_to, _amount, _rebasing);
} else {
Claim memory info = warmupInfo[_to];
if (!info.lock) {
require(_to == msg.sender, "External deposits for account are locked");
}
warmupInfo[_to] = Claim({
deposit: info.deposit.add(_amount),
gons: info.gons.add(sOHM.gonsForBalance(_amount)),
expiry: epoch.number.add(warmupPeriod),
lock: info.lock
});
gonsInWarmup = gonsInWarmup.add(sOHM.gonsForBalance(_amount));
return _amount;
}
}
/**
* @notice retrieve stake from warmup
* @param _to address
* @param _rebasing bool
* @return uint
*/
function claim(address _to, bool _rebasing) public returns (uint256) {
Claim memory info = warmupInfo[_to];
if (!info.lock) {
require(_to == msg.sender, "External claims for account are locked");
}
if (epoch.number >= info.expiry && info.expiry != 0) {
delete warmupInfo[_to];
gonsInWarmup = gonsInWarmup.sub(info.gons);
return _send(_to, sOHM.balanceForGons(info.gons), _rebasing);
}
return 0;
}
/**
* @notice forfeit stake and retrieve OHM
* @return uint
*/
function forfeit() external returns (uint256) {
Claim memory info = warmupInfo[msg.sender];
delete warmupInfo[msg.sender];
gonsInWarmup = gonsInWarmup.sub(info.gons);
OHM.safeTransfer(msg.sender, info.deposit);
return info.deposit;
}
/**
* @notice prevent new deposits or claims from ext. address (protection from malicious activity)
*/
function toggleLock() external {
warmupInfo[msg.sender].lock = !warmupInfo[msg.sender].lock;
}
/**
* @notice redeem sOHM for OHMs
* @param _to address
* @param _amount uint
* @param _trigger bool
* @param _rebasing bool
* @return amount_ uint
*/
function unstake(
address _to,
uint256 _amount,
bool _trigger,
bool _rebasing
) external returns (uint256 amount_) {
amount_ = _amount;
uint256 bounty;
if (_trigger) {
bounty = rebase();
}
if (_rebasing) {
sOHM.safeTransferFrom(msg.sender, address(this), _amount);
amount_ = amount_.add(bounty);
} else {
gOHM.burn(msg.sender, _amount); // amount was given in gOHM terms
amount_ = gOHM.balanceFrom(amount_).add(bounty); // convert amount to OHM terms & add bounty
}
require(amount_ <= OHM.balanceOf(address(this)), "Insufficient OHM balance in contract");
OHM.safeTransfer(_to, amount_);
}
/**
* @notice convert _amount sOHM into gBalance_ gOHM
* @param _to address
* @param _amount uint
* @return gBalance_ uint
*/
function wrap(address _to, uint256 _amount) external returns (uint256 gBalance_) {
sOHM.safeTransferFrom(msg.sender, address(this), _amount);
gBalance_ = gOHM.balanceTo(_amount);
gOHM.mint(_to, gBalance_);
}
/**
* @notice convert _amount gOHM into sBalance_ sOHM
* @param _to address
* @param _amount uint
* @return sBalance_ uint
*/
function unwrap(address _to, uint256 _amount) external returns (uint256 sBalance_) {
gOHM.burn(msg.sender, _amount);
sBalance_ = gOHM.balanceFrom(_amount);
sOHM.safeTransfer(_to, sBalance_);
}
/**
* @notice trigger rebase if epoch over
* @return uint256
*/
function rebase() public returns (uint256) {
uint256 bounty;
if (epoch.end <= block.timestamp) {
sOHM.rebase(epoch.distribute, epoch.number);
epoch.end = epoch.end.add(epoch.length);
epoch.number++;
if (address(distributor) != address(0)) {
distributor.distribute();
bounty = distributor.retrieveBounty(); // Will mint ohm for this contract if there exists a bounty
}
uint256 balance = OHM.balanceOf(address(this));
uint256 staked = sOHM.circulatingSupply();
if (balance <= staked.add(bounty)) {
epoch.distribute = 0;
} else {
epoch.distribute = balance.sub(staked).sub(bounty);
}
}
return bounty;
}
/* ========== INTERNAL FUNCTIONS ========== */
/**
* @notice send staker their amount as sOHM or gOHM
* @param _to address
* @param _amount uint
* @param _rebasing bool
*/
function _send(
address _to,
uint256 _amount,
bool _rebasing
) internal returns (uint256) {
if (_rebasing) {
sOHM.safeTransfer(_to, _amount); // send as sOHM (equal unit as OHM)
return _amount;
} else {
gOHM.mint(_to, gOHM.balanceTo(_amount)); // send as gOHM (convert units from OHM)
return gOHM.balanceTo(_amount);
}
}
/* ========== VIEW FUNCTIONS ========== */
/**
* @notice returns the sOHM index, which tracks rebase growth
* @return uint
*/
function index() public view returns (uint256) {
return sOHM.index();
}
/**
* @notice total supply in warmup
*/
function supplyInWarmup() public view returns (uint256) {
return sOHM.balanceForGons(gonsInWarmup);
}
/**
* @notice seconds until the next epoch begins
*/
function secondsToNextEpoch() external view returns (uint256) {
return epoch.end.sub(block.timestamp);
}
/* ========== MANAGERIAL FUNCTIONS ========== */
/**
* @notice sets the contract address for LP staking
* @param _distributor address
*/
function setDistributor(address _distributor) external onlyGovernor {
distributor = IDistributor(_distributor);
emit DistributorSet(_distributor);
}
/**
* @notice set warmup period for new stakers
* @param _warmupPeriod uint
*/
function setWarmupLength(uint256 _warmupPeriod) external onlyGovernor {
warmupPeriod = _warmupPeriod;
emit WarmupSet(_warmupPeriod);
}
}