/**
* Copyright 2017-2021, bZeroX, LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0.
*/
// SPDX-License-Identifier:MIT
pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;
/**
* @title Interface for contract governance/FeeSharingProxy.sol
* @dev Interfaces are used to cast a contract address into a callable instance.
* */
interface IFeeSharingProxy {
function withdrawFees(address _token) external;
function transferTokens(address _token, uint96 _amount) external;
function withdraw(
address _loanPoolToken,
uint32 _maxCheckpoints,
address _receiver
) external;
}
contract IERC20 {
string public name;
uint8 public decimals;
string public symbol;
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function allowance(address _owner, address _spender) public view returns (uint256);
function approve(address _spender, uint256 _value) public returns (bool);
function transfer(address _to, uint256 _value) public returns (bool);
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title SafeMath96 contract.
* @notice Improved Solidity's arithmetic operations with added overflow checks.
* @dev SafeMath96 uses uint96, unsigned integers of 96 bits length, so every
* integer from 0 to 2^96-1 can be operated.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* SafeMath restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this contract instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
* */
contract SafeMath96 {
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
require(n < 2**32, errorMessage);
return uint32(n);
}
function safe64(uint256 n, string memory errorMessage) internal pure returns (uint64) {
require(n < 2**64, errorMessage);
return uint64(n);
}
function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) {
require(n < 2**96, errorMessage);
return uint96(n);
}
/**
* @notice Adds two unsigned integers, reverting on overflow.
* @dev Counterpart to Solidity's `+` operator.
* @param a First integer.
* @param b Second integer.
* @param errorMessage The revert message on overflow.
* @return The safe addition a+b.
* */
function add96(
uint96 a,
uint96 b,
string memory errorMessage
) internal pure returns (uint96) {
uint96 c = a + b;
require(c >= a, errorMessage);
return c;
}
/**
* @notice Substracts two unsigned integers, reverting on underflow.
* @dev Counterpart to Solidity's `-` operator.
* @param a First integer.
* @param b Second integer.
* @param errorMessage The revert message on underflow.
* @return The safe substraction a-b.
* */
function sub96(
uint96 a,
uint96 b,
string memory errorMessage
) internal pure returns (uint96) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @notice Multiplies two unsigned integers, reverting on overflow.
* @dev Counterpart to Solidity's `*` operator.
* @param a First integer.
* @param b Second integer.
* @param errorMessage The revert message on overflow.
* @return The safe product a*b.
* */
function mul96(
uint96 a,
uint96 b,
string memory errorMessage
) internal pure returns (uint96) {
if (a == 0) {
return 0;
}
uint96 c = a * b;
require(c / a == b, errorMessage);
return c;
}
/**
* @notice Divides two unsigned integers, reverting on overflow.
* @dev Counterpart to Solidity's `/` operator.
* @param a First integer.
* @param b Second integer.
* @param errorMessage The revert message on overflow.
* @return The safe division a/b.
* */
function div96(
uint96 a,
uint96 b,
string memory errorMessage
) internal pure returns (uint96) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint96 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
}
/**
* @title Interface for contract governance/ApprovalReceiver.sol
* @dev Interfaces are used to cast a contract address into a callable instance.
*/
interface IApproveAndCall {
/**
* @notice Receives approval from SOV token.
* @param _sender The sender of SOV.approveAndCall function.
* @param _amount The amount was approved.
* @param _token The address of token.
* @param _data The data will be used for low level call.
* */
function receiveApproval(
address _sender,
uint256 _amount,
address _token,
bytes calldata _data
) external;
}
/**
* @title Base contract to properly handle returned data on failed calls
* @dev On EVM if the return data length of a call is less than 68,
* then the transaction fails silently without a revert message!
*
* As described in the Solidity documentation
* https://solidity.readthedocs.io/en/v0.5.17/control-structures.html#revert
* the revert reason is an ABI-encoded string consisting of:
* 0x08c379a0 // Function selector (method id) for "Error(string)" signature
* 0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
* 0x000000000000000000000000000000000000000000000000000000000000001a // String length
* 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
*
* Another example, debug data from test:
* 0x08c379a0
* 0000000000000000000000000000000000000000000000000000000000000020
* 0000000000000000000000000000000000000000000000000000000000000034
* 54696d656c6f636b3a3a73657444656c61793a2044656c6179206d7573742065
* 7863656564206d696e696d756d2064656c61792e000000000000000000000000
*
* Parsed into:
* Data offset: 20
* Length: 34
* Error message:
* 54696d656c6f636b3a3a73657444656c61793a2044656c6179206d7573742065
* 7863656564206d696e696d756d2064656c61792e000000000000000000000000
*/
contract ErrorDecoder {
uint256 constant ERROR_MESSAGE_SHIFT = 68; // EVM silent revert error string length
/**
* @notice Concats two error strings taking into account ERROR_MESSAGE_SHIFT.
* @param str1 First string, usually a hardcoded context written by dev.
* @param str2 Second string, usually the error message from the reverted call.
* @return The concatenated error string
*/
function _addErrorMessage(string memory str1, string memory str2) internal pure returns (string memory) {
bytes memory bytesStr1 = bytes(str1);
bytes memory bytesStr2 = bytes(str2);
string memory str12 = new string(bytesStr1.length + bytesStr2.length - ERROR_MESSAGE_SHIFT);
bytes memory bytesStr12 = bytes(str12);
uint256 j = 0;
for (uint256 i = 0; i < bytesStr1.length; i++) {
bytesStr12[j++] = bytesStr1[i];
}
for (uint256 i = ERROR_MESSAGE_SHIFT; i < bytesStr2.length; i++) {
bytesStr12[j++] = bytesStr2[i];
}
return string(bytesStr12);
}
}
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*
* _Available since v2.4.0._
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
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;
}
/**
* @dev Integer division of two numbers, rounding up and truncating the quotient
*/
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
return divCeil(a, b, "SafeMath: division by zero");
}
/**
* @dev Integer division of two numbers, rounding up and truncating the quotient
*/
function divCeil(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b != 0, errorMessage);
if (a == 0) {
return 0;
}
uint256 c = ((a - 1) / b) + 1;
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*
* _Available since v2.4.0._
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
return _a < _b ? _a : _b;
}
}
library RSKAddrValidator {
/*
* @param addr it is an address to check that it does not originates from
* signing with PK = ZERO. RSK has a small difference in which @ZERO_PK_ADDR is
* also an address from PK = ZERO. So we check for both of them.
* */
function checkPKNotZero(address addr) internal pure returns (bool) {
return (addr != 0xdcc703c0E500B653Ca82273B7BFAd8045D85a470 && addr != address(0));
}
/*
* Safely compares two addresses, checking they do not originate from
* a zero private key.
* */
function safeEquals(address addr1, address addr2) internal pure returns (bool) {
return (addr1 == addr2 && addr1 != 0xdcc703c0E500B653Ca82273B7BFAd8045D85a470 && addr1 != address(0));
}
}
/**
* @title Interface for contract governance/Staking/Staking.sol
* @dev Interfaces are used to cast a contract address into a callable instance.
*/
interface IStaking {
function stakesBySchedule(
uint256 amount,
uint256 cliff,
uint256 duration,
uint256 intervalLength,
address stakeFor,
address delegatee
) external;
function stake(
uint96 amount,
uint256 until,
address stakeFor,
address delegatee
) external;
function getPriorVotes(
address account,
uint256 blockNumber,
uint256 date
) external view returns (uint96);
function getPriorTotalVotingPower(uint32 blockNumber, uint256 time) external view returns (uint96);
function getPriorWeightedStake(
address account,
uint256 blockNumber,
uint256 date
) external view returns (uint96);
function timestampToLockDate(uint256 timestamp) external view returns (uint256 lockDate);
}
interface ITimelock {
function delay() external view returns (uint256);
function GRACE_PERIOD() external view returns (uint256);
function acceptAdmin() external;
function queuedTransactions(bytes32 hash) external view returns (bool);
function queueTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external returns (bytes32);
function cancelTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external;
function executeTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external payable returns (bytes memory);
}
/**
* @title Sovryn Protocol Timelock contract, based on Compound system.
*
* @notice This contract lets Sovryn governance system set up its
* own Time Lock instance to execute transactions proposed through the
* GovernorAlpha contract instance.
*
* The Timelock contract allows its admin (Sovryn governance on
* GovernorAlpha contract) to add arbitrary function calls to a
* queue. This contract can only execute a function call if the
* function call has been in the queue for at least 3 hours.
*
* Anytime the Timelock contract makes a function call, it must be the
* case that the function call was first made public by having been publicly
* added to the queue at least 3 hours prior.
*
* The intention is to provide GovernorAlpha contract the functionality to
* queue proposal actions. This would mean that any changes made by Sovryn
* governance of any contract would necessarily come with at least an
* advanced warning. This makes the Sovryn system follow a “time-delayed,
* opt-out” upgrade pattern (rather than an “instant, forced” upgrade pattern).
*
* Time-delaying admin actions gives users a chance to exit system if its
* admins become malicious or compromised (or make a change that the users
* do not like). Downside is that honest admins would be unable
* to lock down functionality to protect users if a critical bug was found.
*
* Delayed transactions reduce the amount of trust required by users of Sovryn
* and the overall risk for contracts building on top of it, as GovernorAlpha.
* */
contract Timelock is ErrorDecoder, ITimelock {
using SafeMath for uint256;
uint256 public constant GRACE_PERIOD = 14 days;
uint256 public constant MINIMUM_DELAY = 3 hours;
uint256 public constant MAXIMUM_DELAY = 30 days;
address public admin;
address public pendingAdmin;
uint256 public delay;
mapping(bytes32 => bool) public queuedTransactions;
event NewAdmin(address indexed newAdmin);
event NewPendingAdmin(address indexed newPendingAdmin);
event NewDelay(uint256 indexed newDelay);
event CancelTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);
event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);
event QueueTransaction(bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta);
/**
* @notice Function called on instance deployment of the contract.
* @param admin_ Governance contract address.
* @param delay_ Time to wait for queued transactions to be executed.
* */
constructor(address admin_, uint256 delay_) public {
require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay.");
require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
admin = admin_;
delay = delay_;
}
/**
* @notice Fallback function is to react to receiving value (rBTC).
* */
function() external payable {}
/**
* @notice Set a new delay when executing the contract calls.
* @param delay_ The amount of time to wait until execution.
* */
function setDelay(uint256 delay_) public {
require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
delay = delay_;
emit NewDelay(delay);
}
/**
* @notice Accept a new admin for the timelock.
* */
function acceptAdmin() public {
require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
/**
* @notice Set a new pending admin for the timelock.
* @param pendingAdmin_ The new pending admin address.
* */
function setPendingAdmin(address pendingAdmin_) public {
require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
/**
* @notice Queue a new transaction from the governance contract.
* @param target The contract to call.
* @param value The amount to send in the transaction.
* @param signature The stanndard representation of the function called.
* @param data The ethereum transaction input data payload.
* @param eta Estimated Time of Accomplishment. The timestamp that the
* proposal will be available for execution, set once the vote succeeds.
* */
function queueTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public returns (bytes32) {
require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
/**
* @notice Cancel a transaction.
* @param target The contract to call.
* @param value The amount to send in the transaction.
* @param signature The stanndard representation of the function called.
* @param data The ethereum transaction input data payload.
* @param eta Estimated Time of Accomplishment. The timestamp that the
* proposal will be available for execution, set once the vote succeeds.
* */
function cancelTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public {
require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
/**
* @notice Executes a previously queued transaction from the governance.
* @param target The contract to call.
* @param value The amount to send in the transaction.
* @param signature The stanndard representation of the function called.
* @param data The ethereum transaction input data payload.
* @param eta Estimated Time of Accomplishment. The timestamp that the
* proposal will be available for execution, set once the vote succeeds.
* */
function executeTransaction(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) public payable returns (bytes memory) {
require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call.value(value)(callData);
if (!success) {
if (returnData.length <= ERROR_MESSAGE_SHIFT) {
revert("Timelock::executeTransaction: Transaction execution reverted.");
} else {
revert(_addErrorMessage("Timelock::executeTransaction: ", string(returnData)));
}
}
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
/**
* @notice A function used to get the current Block Timestamp.
* @dev Timestamp of the current block in seconds since the epoch.
* It is a Unix time stamp. So, it has the complete information about
* the date, hours, minutes, and seconds (in UTC) when the block was
* created.
* */
function getBlockTimestamp() internal view returns (uint256) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
}
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with GSN meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
contract Context {
// Empty internal constructor, to prevent people from mistakenly deploying
// an instance of this contract, which should be used via inheritance.
constructor() internal {}
// solhint-disable-previous-line no-empty-blocks
function _msgSender() internal view returns (address payable) {
return msg.sender;
}
function _msgData() internal view returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "unauthorized");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return _msgSender() == _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
/**
* @title Staking Storage contact.
* @notice Just the storage part of stacking contract, no functions,
* only constant, variables and required structures (mappings).
* Used by StackingProxy and Checkpoints contracts.
*
* What is SOV staking?
* The purpose of the SOV token is to provide a pseudonymous,
* censorship-resistant mechanism for governing the parameters of the Sovryn
* protocol, while aligning the incentives of protocol governors with the
* long-term success of the protocol. Any SOV token holder can choose to
* stake (lock up) their tokens for a fixed period of time in return for
* voting rights in the Bitocracy. Stakers are further incentivised through
* fee and slashing rewards.
* */
contract StakingStorage is Ownable {
/// @notice 2 weeks in seconds.
uint256 constant TWO_WEEKS = 1209600;
/// @notice The maximum possible voting weight before adding +1 (actually 10, but need 9 for computation).
uint96 public constant MAX_VOTING_WEIGHT = 9;
/// @notice weight is multiplied with this factor (for allowing decimals, like 1.2x).
/// @dev MAX_VOTING_WEIGHT * WEIGHT_FACTOR needs to be < 792, because there are 100,000,000 SOV with 18 decimals
uint96 public constant WEIGHT_FACTOR = 10;
/// @notice The maximum duration to stake tokens for.
uint256 public constant MAX_DURATION = 1092 days;
/// @notice The maximum duration ^2
uint96 constant MAX_DURATION_POW_2 = 1092 * 1092;
/// @notice Default weight scaling.
uint96 constant DEFAULT_WEIGHT_SCALING = 3;
/// @notice Range for weight scaling.
uint96 constant MIN_WEIGHT_SCALING = 1;
uint96 constant MAX_WEIGHT_SCALING = 9;
/// @notice The timestamp of contract creation. Base for the staking period calculation.
uint256 public kickoffTS;
string name = "SOVStaking";
/// @notice The token to be staked.
IERC20 public SOVToken;
/// @notice A record of each accounts delegate.
mapping(address => mapping(uint256 => address)) public delegates;
/// @notice If this flag is set to true, all tokens are unlocked immediately.
bool public allUnlocked = false;
/// @notice The EIP-712 typehash for the contract's domain.
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
/// @notice The EIP-712 typehash for the delegation struct used by the contract.
bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 lockDate,uint256 nonce,uint256 expiry)");
/// @notice Used for stake migrations to a new staking contract with a different storage structure.
address public newStakingContract;
/*************************** Checkpoints *******************************/
/// @notice A checkpoint for marking the stakes from a given block
struct Checkpoint {
uint32 fromBlock;
uint96 stake;
}
/// @notice A record of tokens to be unstaked at a given time in total.
/// For total voting power computation. Voting weights get adjusted bi-weekly.
/// @dev totalStakingCheckpoints[date][index] is a checkpoint.
mapping(uint256 => mapping(uint32 => Checkpoint)) public totalStakingCheckpoints;
/// @notice The number of total staking checkpoints for each date.
/// @dev numTotalStakingCheckpoints[date] is a number.
mapping(uint256 => uint32) public numTotalStakingCheckpoints;
/// @notice A record of tokens to be unstaked at a given time which were delegated to a certain address.
/// For delegatee voting power computation. Voting weights get adjusted bi-weekly.
/// @dev delegateStakingCheckpoints[delegatee][date][index] is a checkpoint.
mapping(address => mapping(uint256 => mapping(uint32 => Checkpoint))) public delegateStakingCheckpoints;
/// @notice The number of total staking checkpoints for each date per delegate.
/// @dev numDelegateStakingCheckpoints[delegatee][date] is a number.
mapping(address => mapping(uint256 => uint32)) public numDelegateStakingCheckpoints;
/// @notice A record of tokens to be unstaked at a given time which per user address (address -> lockDate -> stake checkpoint)
/// @dev userStakingCheckpoints[user][date][index] is a checkpoint.
mapping(address => mapping(uint256 => mapping(uint32 => Checkpoint))) public userStakingCheckpoints;
/// @notice The number of total staking checkpoints for each date per user.
/// @dev numUserStakingCheckpoints[user][date] is a number.
mapping(address => mapping(uint256 => uint32)) public numUserStakingCheckpoints;
/// @notice A record of states for signing / validating signatures
/// @dev nonces[user] is a number.
mapping(address => uint256) public nonces;
/*************************** Slashing *******************************/
/// @notice the address of FeeSharingProxy contract, we need it for unstaking with slashing.
IFeeSharingProxy public feeSharing;
/// @notice used for weight scaling when unstaking with slashing.
uint96 public weightScaling = DEFAULT_WEIGHT_SCALING;
/// @notice List of vesting contracts, tokens for these contracts won't be slashed if unstaked by governance.
/// @dev vestingWhitelist[contract] is true/false.
mapping(address => bool) public vestingWhitelist;
/// @dev user => flag whether user has admin role.
/// @dev multisig should be an admin, admin can invoke only governanceWithdrawVesting function,
/// this function works only with Team Vesting contracts
mapping(address => bool) public admins;
/// @dev vesting contract code hash => flag whether it's registered code hash
mapping(bytes32 => bool) public vestingCodeHashes;
}
/**
* @title Checkpoints contract.
* @notice Increases and decreases storage values for users, delegatees and
* total daily stake.
* */
contract Checkpoints is StakingStorage, SafeMath96 {
/// @notice An event emitted when an account changes its delegate.
event DelegateChanged(address indexed delegator, uint256 lockedUntil, address indexed fromDelegate, address indexed toDelegate);
/// @notice An event emitted when a delegate account's stake balance changes.
event DelegateStakeChanged(address indexed delegate, uint256 lockedUntil, uint256 previousBalance, uint256 newBalance);
/// @notice An event emitted when tokens get staked.
event TokensStaked(address indexed staker, uint256 amount, uint256 lockedUntil, uint256 totalStaked);
/// @notice An event emitted when tokens get withdrawn.
event TokensWithdrawn(address indexed staker, address receiver, uint256 amount);
/// @notice An event emitted when vesting tokens get withdrawn.
event VestingTokensWithdrawn(address vesting, address receiver);
/// @notice An event emitted when the owner unlocks all tokens.
event TokensUnlocked(uint256 amount);
/// @notice An event emitted when a staking period gets extended.
event ExtendedStakingDuration(address indexed staker, uint256 previousDate, uint256 newDate);
event AdminAdded(address admin);
event AdminRemoved(address admin);
event ContractCodeHashAdded(bytes32 hash);
event ContractCodeHashRemoved(bytes32 hash);
/**
* @notice Increases the user's stake for a giving lock date and writes a checkpoint.
* @param account The user address.
* @param lockedTS The lock date.
* @param value The value to add to the staked balance.
* */
function _increaseUserStake(
address account,
uint256 lockedTS,
uint96 value
) internal {
uint32 nCheckpoints = numUserStakingCheckpoints[account][lockedTS];
uint96 staked = userStakingCheckpoints[account][lockedTS][nCheckpoints - 1].stake;
uint96 newStake = add96(staked, value, "Staking::_increaseUserStake: staked amount overflow");
_writeUserCheckpoint(account, lockedTS, nCheckpoints, newStake);
}
/**
* @notice Decreases the user's stake for a giving lock date and writes a checkpoint.
* @param account The user address.
* @param lockedTS The lock date.
* @param value The value to substract to the staked balance.
* */
function _decreaseUserStake(
address account,
uint256 lockedTS,
uint96 value
) internal {
uint32 nCheckpoints = numUserStakingCheckpoints[account][lockedTS];
uint96 staked = userStakingCheckpoints[account][lockedTS][nCheckpoints - 1].stake;
uint96 newStake = sub96(staked, value, "Staking::_decreaseUserStake: staked amount underflow");
_writeUserCheckpoint(account, lockedTS, nCheckpoints, newStake);
}
/**
* @notice Writes on storage the user stake.
* @param account The user address.
* @param lockedTS The lock date.
* @param nCheckpoints The number of checkpoints, to find out the last one index.
* @param newStake The new staked balance.
* */
function _writeUserCheckpoint(
address account,
uint256 lockedTS,
uint32 nCheckpoints,
uint96 newStake
) internal {
uint32 blockNumber = safe32(block.number, "Staking::_writeStakingCheckpoint: block number exceeds 32 bits");
if (nCheckpoints > 0 && userStakingCheckpoints[account][lockedTS][nCheckpoints - 1].fromBlock == blockNumber) {
userStakingCheckpoints[account][lockedTS][nCheckpoints - 1].stake = newStake;
} else {
userStakingCheckpoints[account][lockedTS][nCheckpoints] = Checkpoint(blockNumber, newStake);
numUserStakingCheckpoints[account][lockedTS] = nCheckpoints + 1;
}
}
/**
* @notice Increases the delegatee's stake for a giving lock date and writes a checkpoint.
* @param delegatee The delegatee address.
* @param lockedTS The lock date.
* @param value The value to add to the staked balance.
* */
function _increaseDelegateStake(
address delegatee,
uint256 lockedTS,
uint96 value
) internal {
uint32 nCheckpoints = numDelegateStakingCheckpoints[delegatee][lockedTS];
uint96 staked = delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints - 1].stake;
uint96 newStake = add96(staked, value, "Staking::_increaseDelegateStake: staked amount overflow");
_writeDelegateCheckpoint(delegatee, lockedTS, nCheckpoints, newStake);
}
/**
* @notice Decreases the delegatee's stake for a giving lock date and writes a checkpoint.
* @param delegatee The delegatee address.
* @param lockedTS The lock date.
* @param value The value to substract to the staked balance.
* */
function _decreaseDelegateStake(
address delegatee,
uint256 lockedTS,
uint96 value
) internal {
uint32 nCheckpoints = numDelegateStakingCheckpoints[delegatee][lockedTS];
uint96 staked = delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints - 1].stake;
uint96 newStake = 0;
// @dev We need to check delegate checkpoint value here,
// because we had an issue in `stake` function:
// delegate checkpoint wasn't updating for the second and next stakes for the same date
// if first stake was withdrawn completely and stake was delegated to the staker
// (no delegation to another address).
// @dev It can be greater than 0, but inconsistent after 3 transactions
if (staked > value) {
newStake = sub96(staked, value, "Staking::_decreaseDelegateStake: staked amount underflow");
}
_writeDelegateCheckpoint(delegatee, lockedTS, nCheckpoints, newStake);
}
/**
* @notice Writes on storage the delegate stake.
* @param delegatee The delegate address.
* @param lockedTS The lock date.
* @param nCheckpoints The number of checkpoints, to find out the last one index.
* @param newStake The new staked balance.
* */
function _writeDelegateCheckpoint(
address delegatee,
uint256 lockedTS,
uint32 nCheckpoints,
uint96 newStake
) internal {
uint32 blockNumber = safe32(block.number, "Staking::_writeStakingCheckpoint: block number exceeds 32 bits");
uint96 oldStake = delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints - 1].stake;
if (nCheckpoints > 0 && delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints - 1].fromBlock == blockNumber) {
delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints - 1].stake = newStake;
} else {
delegateStakingCheckpoints[delegatee][lockedTS][nCheckpoints] = Checkpoint(blockNumber, newStake);
numDelegateStakingCheckpoints[delegatee][lockedTS] = nCheckpoints + 1;
}
emit DelegateStakeChanged(delegatee, lockedTS, oldStake, newStake);
}
/**
* @notice Increases the total stake for a giving lock date and writes a checkpoint.
* @param lockedTS The lock date.
* @param value The value to add to the staked balance.
* */
function _increaseDailyStake(uint256 lockedTS, uint96 value) internal {
uint32 nCheckpoints = numTotalStakingCheckpoints[lockedTS];
uint96 staked = totalStakingCheckpoints[lockedTS][nCheckpoints - 1].stake;
uint96 newStake = add96(staked, value, "Staking::_increaseDailyStake: staked amount overflow");
_writeStakingCheckpoint(lockedTS, nCheckpoints, newStake);
}
/**
* @notice Decreases the total stake for a giving lock date and writes a checkpoint.
* @param lockedTS The lock date.
* @param value The value to substract to the staked balance.
* */
function _decreaseDailyStake(uint256 lockedTS, uint96 value) internal {
uint32 nCheckpoints = numTotalStakingCheckpoints[lockedTS];
uint96 staked = totalStakingCheckpoints[lockedTS][nCheckpoints - 1].stake;
uint96 newStake = sub96(staked, value, "Staking::_decreaseDailyStake: staked amount underflow");
_writeStakingCheckpoint(lockedTS, nCheckpoints, newStake);
}
/**
* @notice Writes on storage the total stake.
* @param lockedTS The lock date.
* @param nCheckpoints The number of checkpoints, to find out the last one index.
* @param newStake The new staked balance.
* */
function _writeStakingCheckpoint(
uint256 lockedTS,
uint32 nCheckpoints,
uint96 newStake
) internal {
uint32 blockNumber = safe32(block.number, "Staking::_writeStakingCheckpoint: block number exceeds 32 bits");
if (nCheckpoints > 0 && totalStakingCheckpoints[lockedTS][nCheckpoints - 1].fromBlock == blockNumber) {
totalStakingCheckpoints[lockedTS][nCheckpoints - 1].stake = newStake;
} else {
totalStakingCheckpoints[lockedTS][nCheckpoints] = Checkpoint(blockNumber, newStake);
numTotalStakingCheckpoints[lockedTS] = nCheckpoints + 1;
}
}
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*
* _Available since v2.4.0._
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html
* #use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*
* _Available since v2.4.0._
*/
function sendValue(address recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
/**
* @title Weighted Staking contract.
* @notice Computation of power and votes used by FeeSharingProxy and
* GovernorAlpha and Staking contracts w/ mainly 3 public functions:
* + getPriorTotalVotingPower => Total voting power.
* + getPriorVotes => Delegatee voting power.
* + getPriorWeightedStake => User Weighted Stake.
* Staking contract inherits WeightedStaking.
* FeeSharingProxy and GovernorAlpha invoke Staking instance functions.
* */
contract WeightedStaking is Checkpoints {
using Address for address payable;
/**
* @dev Throws if called by any account other than the owner or admin.
*/
modifier onlyAuthorized() {
require(isOwner() || admins[msg.sender], "unauthorized");
_;
}
/************* TOTAL VOTING POWER COMPUTATION ************************/
/**
* @notice Compute the total voting power at a given time.
* @param time The timestamp for which to calculate the total voting power.
* @return The total voting power at the given time.
* */
function getPriorTotalVotingPower(uint32 blockNumber, uint256 time) public view returns (uint96 totalVotingPower) {
/// @dev Start the computation with the exact or previous unlocking date (voting weight remians the same until the next break point).
uint256 start = timestampToLockDate(time);
uint256 end = start + MAX_DURATION;
/// @dev Max 78 iterations.
for (uint256 i = start; i <= end; i += TWO_WEEKS) {
totalVotingPower = add96(
totalVotingPower,
_totalPowerByDate(i, start, blockNumber),
"WeightedStaking::getPriorTotalVotingPower: overflow on total voting power computation"
);
}
}
/**
* @notice Compute the voting power for a specific date.
* Power = stake * weight
* @param date The staking date to compute the power for.
* @param startDate The date for which we need to know the power of the stake.
* @param blockNumber The block number, needed for checkpointing.
* */
function _totalPowerByDate(
uint256 date,
uint256 startDate,
uint256 blockNumber
) internal view returns (uint96 power) {
uint96 weight = computeWeightByDate(date, startDate);
uint96 staked = getPriorTotalStakesForDate(date, blockNumber);
/// @dev weight is multiplied by some factor to allow decimals.
power = mul96(staked, weight, "WeightedStaking::_totalPowerByDate: multiplication overflow") / WEIGHT_FACTOR;
}
/**
* @notice Determine the prior number of stake for an unlocking date as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* TODO: WeightedStaking::getPriorTotalStakesForDate should probably better
* be internal instead of a public function.
* @param date The date to check the stakes for.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the account had as of the given block.
* */
function getPriorTotalStakesForDate(uint256 date, uint256 blockNumber) public view returns (uint96) {
require(blockNumber < block.number, "WeightedStaking::getPriorTotalStakesForDate: not yet determined");
uint32 nCheckpoints = numTotalStakingCheckpoints[date];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (totalStakingCheckpoints[date][nCheckpoints - 1].fromBlock <= blockNumber) {
return totalStakingCheckpoints[date][nCheckpoints - 1].stake;
}
// Next check implicit zero balance
if (totalStakingCheckpoints[date][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Checkpoint memory cp = totalStakingCheckpoints[date][center];
if (cp.fromBlock == blockNumber) {
return cp.stake;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return totalStakingCheckpoints[date][lower].stake;
}
/****************************** DELEGATED VOTING POWER COMPUTATION ************************/
/**
* @notice Determine the prior number of votes for a delegatee as of a block number.
* Iterate through checkpoints adding up voting power.
* @dev Block number must be a finalized block or else this function will revert
* to prevent misinformation.
* Used for Voting, not for fee sharing.
* @param account The address of the account to check.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the delegatee had as of the given block.
* */
function getPriorVotes(
address account,
uint256 blockNumber,
uint256 date
) public view returns (uint96 votes) {
/// @dev If date is not an exact break point, start weight computation from the previous break point (alternative would be the next).
uint256 start = timestampToLockDate(date);
uint256 end = start + MAX_DURATION;
/// @dev Max 78 iterations.
for (uint256 i = start; i <= end; i += TWO_WEEKS) {
votes = add96(
votes,
_totalPowerByDateForDelegatee(account, i, start, blockNumber),
"WeightedStaking::getPriorVotes: overflow on total voting power computation"
);
}
}
/**
* @notice Compute the voting power for a specific date.
* Power = stake * weight
* @param date The staking date to compute the power for.
* @param startDate The date for which we need to know the power of the stake.
* @param blockNumber The block number, needed for checkpointing.
* */
function _totalPowerByDateForDelegatee(
address account,
uint256 date,
uint256 startDate,
uint256 blockNumber
) internal view returns (uint96 power) {
uint96 weight = computeWeightByDate(date, startDate);
uint96 staked = getPriorStakeByDateForDelegatee(account, date, blockNumber);
power = mul96(staked, weight, "WeightedStaking::_totalPowerByDateForDelegatee: multiplication overflow") / WEIGHT_FACTOR;
}
/**
* @notice Determine the prior number of stake for an account as of a block number.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* TODO: WeightedStaking::getPriorStakeByDateForDelegatee should probably better
* be internal instead of a public function.
* @param account The address of the account to check.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the account had as of the given block.
* */
function getPriorStakeByDateForDelegatee(
address account,
uint256 date,
uint256 blockNumber
) public view returns (uint96) {
require(blockNumber < block.number, "WeightedStaking::getPriorStakeByDateForDelegatee: not yet determined");
uint32 nCheckpoints = numDelegateStakingCheckpoints[account][date];
if (nCheckpoints == 0) {
return 0;
}
/// @dev First check most recent balance.
if (delegateStakingCheckpoints[account][date][nCheckpoints - 1].fromBlock <= blockNumber) {
return delegateStakingCheckpoints[account][date][nCheckpoints - 1].stake;
}
/// @dev Next check implicit zero balance.
if (delegateStakingCheckpoints[account][date][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; /// @dev ceil, avoiding overflow.
Checkpoint memory cp = delegateStakingCheckpoints[account][date][center];
if (cp.fromBlock == blockNumber) {
return cp.stake;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return delegateStakingCheckpoints[account][date][lower].stake;
}
/*************************** User Weighted Stake computation for fee sharing *******************************/
/**
* @notice Determine the prior weighted stake for an account as of a block number.
* Iterate through checkpoints adding up voting power.
* @dev Block number must be a finalized block or else this function will
* revert to prevent misinformation.
* Used for fee sharing, not voting.
* TODO: WeightedStaking::getPriorWeightedStake is using the variable name "votes"
* to add up token stake, and that could be misleading.
*
* @param account The address of the account to check.
* @param blockNumber The block number to get the vote balance at.
* @return The weighted stake the account had as of the given block.
* */
function getPriorWeightedStake(
address account,
uint256 blockNumber,
uint256 date
) public view returns (uint96 votes) {
/// @dev If date is not an exact break point, start weight computation from the previous break point (alternative would be the next).
uint256 start = timestampToLockDate(date);
uint256 end = start + MAX_DURATION;
/// @dev Max 78 iterations.
for (uint256 i = start; i <= end; i += TWO_WEEKS) {
uint96 weightedStake = weightedStakeByDate(account, i, start, blockNumber);
if (weightedStake > 0) {
votes = add96(votes, weightedStake, "WeightedStaking::getPriorWeightedStake: overflow on total weight computation");
}
}
}
/**
* @notice Compute the voting power for a specific date.
* Power = stake * weight
* TODO: WeightedStaking::weightedStakeByDate should probably better
* be internal instead of a public function.
* @param date The staking date to compute the power for.
* @param startDate The date for which we need to know the power of the stake.
* @param blockNumber The block number, needed for checkpointing.
* */
function weightedStakeByDate(
address account,
uint256 date,
uint256 startDate,
uint256 blockNumber
) public view returns (uint96 power) {
uint96 staked = _getPriorUserStakeByDate(account, date, blockNumber);
if (staked > 0) {
uint96 weight = computeWeightByDate(date, startDate);
power = mul96(staked, weight, "WeightedStaking::weightedStakeByDate: multiplication overflow") / WEIGHT_FACTOR;
} else {
power = 0;
}
}
/**
* @notice Determine the prior number of stake for an account until a
* certain lock date as of a block number.
* @dev Block number must be a finalized block or else this function
* will revert to prevent misinformation.
* @param account The address of the account to check.
* @param date The lock date.
* @param blockNumber The block number to get the vote balance at.
* @return The number of votes the account had as of the given block.
* */
function getPriorUserStakeByDate(
address account,
uint256 date,
uint256 blockNumber
) external view returns (uint96) {
uint96 priorStake = _getPriorUserStakeByDate(account, date, blockNumber);
// @dev we need to modify function in order to workaround issue with Vesting.withdrawTokens:
// return 1 instead of 0 if message sender is a contract.
if (priorStake == 0 && _isVestingContract()) {
priorStake = 1;
}
return priorStake;
}
/**
* @notice Determine the prior number of stake for an account until a
* certain lock date as of a block number.
* @dev All functions of Staking contract use this internal version,
* we need to modify public function in order to workaround issue with Vesting.withdrawTokens:
* return 1 instead of 0 if message sender is a contract.
* */
function _getPriorUserStakeByDate(
address account,
uint256 date,
uint256 blockNumber
) internal view returns (uint96) {
require(blockNumber < block.number, "WeightedStaking::getPriorUserStakeAndDate: not yet determined");
date = _adjustDateForOrigin(date);
uint32 nCheckpoints = numUserStakingCheckpoints[account][date];
if (nCheckpoints == 0) {
return 0;
}
/// @dev First check most recent balance.
if (userStakingCheckpoints[account][date][nCheckpoints - 1].fromBlock <= blockNumber) {
return userStakingCheckpoints[account][date][nCheckpoints - 1].stake;
}
/// @dev Next check implicit zero balance.
if (userStakingCheckpoints[account][date][0].fromBlock > blockNumber) {
return 0;
}
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
while (upper > lower) {
uint32 center = upper - (upper - lower) / 2; /// @dev ceil, avoiding overflow.
Checkpoint memory cp = userStakingCheckpoints[account][date][center];
if (cp.fromBlock == blockNumber) {
return cp.stake;
} else if (cp.fromBlock < blockNumber) {
lower = center;
} else {
upper = center - 1;
}
}
return userStakingCheckpoints[account][date][lower].stake;
}
/**************** SHARED FUNCTIONS *********************/
/**
* @notice Compute the weight for a specific date.
* @param date The unlocking date.
* @param startDate We compute the weight for the tokens staked until 'date' on 'startDate'.
* */
function computeWeightByDate(uint256 date, uint256 startDate) public pure returns (uint96 weight) {
require(date >= startDate, "WeightedStaking::computeWeightByDate: date needs to be bigger than startDate");
uint256 remainingTime = (date - startDate);
require(MAX_DURATION >= remainingTime, "Staking::computeWeightByDate:remaining time can't be bigger than max duration");
/// @dev x = max days - remaining days
uint96 x = uint96(MAX_DURATION - remainingTime) / (1 days);
/// @dev w = (m^2 - x^2)/m^2 +1 (multiplied by the weight factor)
weight = add96(
WEIGHT_FACTOR,
mul96(
MAX_VOTING_WEIGHT * WEIGHT_FACTOR,
sub96(MAX_DURATION_POW_2, x * x, "underflow on weight calculation"),
"multiplication overflow on weight computation"
) / MAX_DURATION_POW_2,
"overflow on weight computation"
);
}
/**
* @notice Unstaking is possible every 2 weeks only. This means, to
* calculate the key value for the staking checkpoints, we need to
* map the intended timestamp to the closest available date.
* @param timestamp The unlocking timestamp.
* @return The actual unlocking date (might be up to 2 weeks shorter than intended).
* */
function timestampToLockDate(uint256 timestamp) public view returns (uint256 lockDate) {
require(timestamp >= kickoffTS, "WeightedStaking::timestampToLockDate: timestamp lies before contract creation");
/**
* @dev If staking timestamp does not match any of the unstaking dates
* , set the lockDate to the closest one before the timestamp.
* E.g. Passed timestamps lies 7 weeks after kickoff -> only stake for 6 weeks.
* */
uint256 periodFromKickoff = (timestamp - kickoffTS) / TWO_WEEKS;
lockDate = periodFromKickoff * TWO_WEEKS + kickoffTS;
}
function _adjustDateForOrigin(uint256 date) internal view returns (uint256) {
uint256 adjustedDate = timestampToLockDate(date);
//origin vesting contracts have different dates
//we need to add 2 weeks to get end of period (by default, it's start)
if (adjustedDate != date) {
date = adjustedDate + TWO_WEEKS;
}
return date;
}
/**
* @notice Add account to ACL.
* @param _admin The addresses of the account to grant permissions.
* */
function addAdmin(address _admin) public onlyOwner {
admins[_admin] = true;
emit AdminAdded(_admin);
}
/**
* @notice Remove account from ACL.
* @param _admin The addresses of the account to revoke permissions.
* */
function removeAdmin(address _admin) public onlyOwner {
admins[_admin] = false;
emit AdminRemoved(_admin);
}
/**
* @notice Add vesting contract's code hash to a map of code hashes.
* @param vesting The address of Vesting contract.
* @dev We need it to use _isVestingContract() function instead of isContract()
*/
function addContractCodeHash(address vesting) public onlyAuthorized {
bytes32 codeHash = _getCodeHash(vesting);
vestingCodeHashes[codeHash] = true;
emit ContractCodeHashAdded(codeHash);
}
/**
* @notice Add vesting contract's code hash to a map of code hashes.
* @param vesting The address of Vesting contract.
* @dev We need it to use _isVestingContract() function instead of isContract()
*/
function removeContractCodeHash(address vesting) public onlyAuthorized {
bytes32 codeHash = _getCodeHash(vesting);
vestingCodeHashes[codeHash] = false;
emit ContractCodeHashRemoved(codeHash);
}
/**
* @notice Return flag whether message sender is a registered vesting contract.
*/
function _isVestingContract() internal view returns (bool) {
bytes32 codeHash = _getCodeHash(msg.sender);
return vestingCodeHashes[codeHash];
}
/**
* @notice Return hash of contract code
*/
function _getCodeHash(address _contract) internal view returns (bytes32) {
bytes32 codeHash;
assembly {
codeHash := extcodehash(_contract)
}
return codeHash;
}
}
/**
* @title Interface for TeamVesting contract.
* @dev Interfaces are used to cast a contract address into a callable instance.
* This interface is used by Staking contract to call governanceWithdrawTokens
* function having the vesting contract instance address.
*/
interface ITeamVesting {
function governanceWithdrawTokens(address receiver) external;
}
/**
* @title Interface for Vesting contract.
* @dev Interfaces are used to cast a contract address into a callable instance.
* This interface is used by VestingLogic contract to implement stakeTokens function
* and on VestingRegistry contract to call IVesting(vesting).stakeTokens function
* at a vesting instance.
*/
interface IVesting {
function duration() external returns (uint256);
function endDate() external returns (uint256);
function stakeTokens(uint256 amount) external;
}
/**
* @title Base contract for receiving approval from SOV token.
*/
contract ApprovalReceiver is ErrorDecoder, IApproveAndCall {
modifier onlyThisContract() {
// Accepts calls only from receiveApproval function.
require(msg.sender == address(this), "unauthorized");
_;
}
/**
* @notice Receives approval from SOV token.
* @param _data The data will be used for low level call.
*/
function receiveApproval(
address _sender,
uint256 _amount,
address _token,
bytes calldata _data
) external {
// Accepts calls only from SOV token.
require(msg.sender == _getToken(), "unauthorized");
require(msg.sender == _token, "unauthorized");
// Only allowed methods.
bool isAllowed = false;
bytes4[] memory selectors = _getSelectors();
bytes4 sig = _getSig(_data);
for (uint256 i = 0; i < selectors.length; i++) {
if (sig == selectors[i]) {
isAllowed = true;
break;
}
}
require(isAllowed, "method is not allowed");
// Check sender and amount.
address sender;
uint256 amount;
(, sender, amount) = abi.decode(abi.encodePacked(bytes28(0), _data), (bytes32, address, uint256));
require(sender == _sender, "sender mismatch");
require(amount == _amount, "amount mismatch");
_call(_data);
}
/**
* @notice Returns token address, only this address can be a sender for receiveApproval.
* @dev Should be overridden in child contracts, otherwise error will be thrown.
* @return By default, 0x. When overriden, the token address making the call.
*/
function _getToken() internal view returns (address) {
return address(0);
}
/**
* @notice Returns list of function selectors allowed to be invoked.
* @dev Should be overridden in child contracts, otherwise error will be thrown.
* @return By default, empty array. When overriden, allowed selectors.
*/
function _getSelectors() internal view returns (bytes4[] memory) {
return new bytes4[](0);
}
/**
* @notice Makes call and reverts w/ enhanced error message.
* @param _data Error message as bytes.
*/
function _call(bytes memory _data) internal {
(bool success, bytes memory returnData) = address(this).call(_data);
if (!success) {
if (returnData.length <= ERROR_MESSAGE_SHIFT) {
revert("receiveApproval: Transaction execution reverted.");
} else {
revert(_addErrorMessage("receiveApproval: ", string(returnData)));
}
}
}
/**
* @notice Extracts the called function selector, a hash of the signature.
* @dev The first four bytes of the call data for a function call specifies
* the function to be called. It is the first (left, high-order in big-endian)
* four bytes of the Keccak-256 (SHA-3) hash of the signature of the function.
* Solidity doesn't yet support a casting of byte[4] to bytes4.
* Example:
* msg.data:
* 0xcdcd77c000000000000000000000000000000000000000000000000000000000000
* 000450000000000000000000000000000000000000000000000000000000000000001
* selector (or method ID): 0xcdcd77c0
* signature: baz(uint32,bool)
* @param _data The msg.data from the low level call.
* @return sig First 4 bytes of msg.data i.e. the selector, hash of the signature.
*/
function _getSig(bytes memory _data) internal pure returns (bytes4 sig) {
assembly {
sig := mload(add(_data, 32))
}
}
}
/**
* @title Staking contract.
* @notice Pay-in and pay-out function for staking and withdrawing tokens.
* Staking is delegated and vested: To gain voting power, SOV holders must
* stake their SOV for a given period of time. Aside from Bitocracy
* participation, there's a financially-rewarding reason for staking SOV.
* Tokenholders who stake their SOV receive staking rewards, a pro-rata share
* of the revenue that the platform generates from various transaction fees
* plus revenues from stakers who have a portion of their SOV slashed for
* early unstaking.
* */
contract Staking is IStaking, WeightedStaking, ApprovalReceiver {
using SafeMath for uint256;
/// @notice Constant used for computing the vesting dates.
uint256 constant FOUR_WEEKS = 4 weeks;
/**
* @notice Stake the given amount for the given duration of time.
* @param amount The number of tokens to stake.
* @param until Timestamp indicating the date until which to stake.
* @param stakeFor The address to stake the tokens for or 0x0 if staking for oneself.
* @param delegatee The address of the delegatee or 0x0 if there is none.
* */
function stake(
uint96 amount,
uint256 until,
address stakeFor,
address delegatee
) external {
_stake(msg.sender, amount, until, stakeFor, delegatee, false);
}
/**
* @notice Stake the given amount for the given duration of time.
* @dev This function will be invoked from receiveApproval
* @dev SOV.approveAndCall -> this.receiveApproval -> this.stakeWithApproval
* @param sender The sender of SOV.approveAndCall
* @param amount The number of tokens to stake.
* @param until Timestamp indicating the date until which to stake.
* @param stakeFor The address to stake the tokens for or 0x0 if staking for oneself.
* @param delegatee The address of the delegatee or 0x0 if there is none.
* */
function stakeWithApproval(
address sender,
uint96 amount,
uint256 until,
address stakeFor,
address delegatee
) public onlyThisContract {
_stake(sender, amount, until, stakeFor, delegatee, false);
}
/**
* @notice Send sender's tokens to this contract and update its staked balance.
* @param sender The sender of the tokens.
* @param amount The number of tokens to send.
* @param until The date until which the tokens will be staked.
* @param stakeFor The beneficiary whose stake will be increased.
* @param delegatee The address of the delegatee or stakeFor if default 0x0.
* @param timeAdjusted Whether fixing date to stacking periods or not.
* */
function _stake(
address sender,
uint96 amount,
uint256 until,
address stakeFor,
address delegatee,
bool timeAdjusted
) internal {
require(amount > 0, "Staking::stake: amount of tokens to stake needs to be bigger than 0");
if (!timeAdjusted) {
until = timestampToLockDate(until);
}
require(until > block.timestamp, "Staking::timestampToLockDate: staking period too short");
/// @dev Stake for the sender if not specified otherwise.
if (stakeFor == address(0)) {
stakeFor = sender;
}
/// @dev Delegate for stakeFor if not specified otherwise.
if (delegatee == address(0)) {
delegatee = stakeFor;
}
/// @dev Do not stake longer than the max duration.
if (!timeAdjusted) {
uint256 latest = timestampToLockDate(block.timestamp + MAX_DURATION);
if (until > latest) until = latest;
}
uint96 previousBalance = currentBalance(stakeFor, until);
/// @dev Increase stake.
_increaseStake(sender, amount, stakeFor, until);
// @dev Previous version wasn't working properly for the following case:
// delegate checkpoint wasn't updating for the second and next stakes for the same date
// if first stake was withdrawn completely and stake was delegated to the staker
// (no delegation to another address).
address previousDelegatee = delegates[stakeFor][until];
if (previousDelegatee != delegatee) {
/// @dev Update delegatee.
delegates[stakeFor][until] = delegatee;
/// @dev Decrease stake on previous balance for previous delegatee.
_decreaseDelegateStake(previousDelegatee, until, previousBalance);
/// @dev Add previousBalance to amount.
amount = add96(previousBalance, amount, "Staking::stake: balance overflow");
}
/// @dev Increase stake.
_increaseDelegateStake(delegatee, until, amount);
emit DelegateChanged(stakeFor, until, previousDelegatee, delegatee);
}
/**
* @notice Extend the staking duration until the specified date.
* @param previousLock The old unlocking timestamp.
* @param until The new unlocking timestamp in seconds.
* */
function extendStakingDuration(uint256 previousLock, uint256 until) public {
until = timestampToLockDate(until);
require(previousLock <= until, "Staking::extendStakingDuration: cannot reduce the staking duration");
/// @dev Do not exceed the max duration, no overflow possible.
uint256 latest = timestampToLockDate(block.timestamp + MAX_DURATION);
if (until > latest) until = latest;
/// @dev Update checkpoints.
/// @dev TODO James: Can reading stake at block.number -1 cause trouble with multiple tx in a block?
uint96 amount = _getPriorUserStakeByDate(msg.sender, previousLock, block.number - 1);
require(amount > 0, "Staking::extendStakingDuration: nothing staked until the previous lock date");
_decreaseUserStake(msg.sender, previousLock, amount);
_increaseUserStake(msg.sender, until, amount);
_decreaseDailyStake(previousLock, amount);
_increaseDailyStake(until, amount);
/// @dev Delegate might change: if there is already a delegate set for the until date, it will remain the delegate for this position
address delegateFrom = delegates[msg.sender][previousLock];
address delegateTo = delegates[msg.sender][until];
if (delegateTo == address(0)) {
delegateTo = delegateFrom;
delegates[msg.sender][until] = delegateFrom;
}
delegates[msg.sender][previousLock] = address(0);
_decreaseDelegateStake(delegateFrom, previousLock, amount);
_increaseDelegateStake(delegateTo, until, amount);
emit ExtendedStakingDuration(msg.sender, previousLock, until);
}
/**
* @notice Send sender's tokens to this contract and update its staked balance.
* @param sender The sender of the tokens.
* @param amount The number of tokens to send.
* @param stakeFor The beneficiary whose stake will be increased.
* @param until The date until which the tokens will be staked.
* */
function _increaseStake(
address sender,
uint96 amount,
address stakeFor,
uint256 until
) internal {
/// @dev Retrieve the SOV tokens.
bool success = SOVToken.transferFrom(sender, address(this), amount);
require(success);
/// @dev Increase staked balance.
uint96 balance = currentBalance(stakeFor, until);
balance = add96(balance, amount, "Staking::increaseStake: balance overflow");
/// @dev Update checkpoints.
_increaseDailyStake(until, amount);
_increaseUserStake(stakeFor, until, amount);
emit TokensStaked(stakeFor, amount, until, balance);
}
/**
* @notice Stake tokens according to the vesting schedule.
* @param amount The amount of tokens to stake.
* @param cliff The time interval to the first withdraw.
* @param duration The staking duration.
* @param intervalLength The length of each staking interval when cliff passed.
* @param stakeFor The address to stake the tokens for or 0x0 if staking for oneself.
* @param delegatee The address of the delegatee or 0x0 if there is none.
* */
function stakesBySchedule(
uint256 amount,
uint256 cliff,
uint256 duration,
uint256 intervalLength,
address stakeFor,
address delegatee
) public {
/**
* @dev Stake them until lock dates according to the vesting schedule.
* Note: because staking is only possible in periods of 2 weeks,
* the total duration might end up a bit shorter than specified
* depending on the date of staking.
* */
uint256 start = timestampToLockDate(block.timestamp + cliff);
if (duration > MAX_DURATION) {
duration = MAX_DURATION;
}
uint256 end = timestampToLockDate(block.timestamp + duration);
uint256 numIntervals = (end - start) / intervalLength + 1;
uint256 stakedPerInterval = amount / numIntervals;
/// @dev stakedPerInterval might lose some dust on rounding. Add it to the first staking date.
if (numIntervals >= 1) {
_stake(msg.sender, uint96(amount - stakedPerInterval * (numIntervals - 1)), start, stakeFor, delegatee, true);
}
/// @dev Stake the rest in 4 week intervals.
for (uint256 i = start + intervalLength; i <= end; i += intervalLength) {
/// @dev Stakes for itself, delegates to the owner.
_stake(msg.sender, uint96(stakedPerInterval), i, stakeFor, delegatee, true);
}
}
/**
* @notice Withdraw the given amount of tokens if they are unlocked.
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* @param receiver The receiver of the tokens. If not specified, send to the msg.sender
* */
function withdraw(
uint96 amount,
uint256 until,
address receiver
) public {
_withdraw(amount, until, receiver, false);
// @dev withdraws tokens for lock date 2 weeks later than given lock date if sender is a contract
// we need to check block.timestamp here
_withdrawNext(amount, until, receiver, false);
}
/**
* @notice Withdraw the given amount of tokens.
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* @param receiver The receiver of the tokens. If not specified, send to the msg.sender
* @dev Can be invoked only by whitelisted contract passed to governanceWithdrawVesting
* */
function governanceWithdraw(
uint96 amount,
uint256 until,
address receiver
) public {
require(vestingWhitelist[msg.sender], "unauthorized");
_withdraw(amount, until, receiver, true);
// @dev withdraws tokens for lock date 2 weeks later than given lock date if sender is a contract
// we don't need to check block.timestamp here
_withdrawNext(amount, until, receiver, true);
}
/**
* @notice Withdraw tokens for vesting contract.
* @param vesting The address of Vesting contract.
* @param receiver The receiver of the tokens. If not specified, send to the msg.sender
* @dev Can be invoked only by whitelisted contract passed to governanceWithdrawVesting.
* */
function governanceWithdrawVesting(address vesting, address receiver) public onlyAuthorized {
vestingWhitelist[vesting] = true;
ITeamVesting(vesting).governanceWithdrawTokens(receiver);
vestingWhitelist[vesting] = false;
emit VestingTokensWithdrawn(vesting, receiver);
}
/**
* @notice Send user' staked tokens to a receiver taking into account punishments.
* Sovryn encourages long-term commitment and thinking. When/if you unstake before
* the end of the staking period, a percentage of the original staking amount will
* be slashed. This amount is also added to the reward pool and is distributed
* between all other stakers.
*
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* @param receiver The receiver of the tokens. If not specified, send to the msg.sender
* @param isGovernance Whether all tokens (true)
* or just unlocked tokens (false).
* */
function _withdraw(
uint96 amount,
uint256 until,
address receiver,
bool isGovernance
) internal {
// @dev it's very unlikely some one will have 1/10**18 SOV staked in Vesting contract
// this check is a part of workaround for Vesting.withdrawTokens issue
if (amount == 1 && _isVestingContract()) {
return;
}
until = _adjustDateForOrigin(until);
_validateWithdrawParams(amount, until);
/// @dev Determine the receiver.
if (receiver == address(0)) receiver = msg.sender;
/// @dev Update the checkpoints.
_decreaseDailyStake(until, amount);
_decreaseUserStake(msg.sender, until, amount);
_decreaseDelegateStake(delegates[msg.sender][until], until, amount);
/// @dev Early unstaking should be punished.
if (block.timestamp < until && !allUnlocked && !isGovernance) {
uint96 punishedAmount = _getPunishedAmount(amount, until);
amount -= punishedAmount;
/// @dev punishedAmount can be 0 if block.timestamp are very close to 'until'
if (punishedAmount > 0) {
require(address(feeSharing) != address(0), "Staking::withdraw: FeeSharing address wasn't set");
/// @dev Move punished amount to fee sharing.
/// @dev Approve transfer here and let feeSharing do transfer and write checkpoint.
SOVToken.approve(address(feeSharing), punishedAmount);
feeSharing.transferTokens(address(SOVToken), punishedAmount);
}
}
/// @dev transferFrom
bool success = SOVToken.transfer(receiver, amount);
require(success, "Staking::withdraw: Token transfer failed");
emit TokensWithdrawn(msg.sender, receiver, amount);
}
// @dev withdraws tokens for lock date 2 weeks later than given lock date
function _withdrawNext(
uint96 amount,
uint256 until,
address receiver,
bool isGovernance
) internal {
if (_isVestingContract()) {
uint256 nextLock = until.add(TWO_WEEKS);
if (isGovernance || block.timestamp >= nextLock) {
uint96 stake = _getPriorUserStakeByDate(msg.sender, nextLock, block.number - 1);
if (stake > 0) {
_withdraw(stake, nextLock, receiver, isGovernance);
}
}
}
}
/**
* @notice Get available and punished amount for withdrawing.
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* */
function getWithdrawAmounts(uint96 amount, uint256 until) public view returns (uint96, uint96) {
_validateWithdrawParams(amount, until);
uint96 punishedAmount = _getPunishedAmount(amount, until);
return (amount - punishedAmount, punishedAmount);
}
/**
* @notice Get punished amount for withdrawing.
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* */
function _getPunishedAmount(uint96 amount, uint256 until) internal view returns (uint96) {
uint256 date = timestampToLockDate(block.timestamp);
uint96 weight = computeWeightByDate(until, date); /// @dev (10 - 1) * WEIGHT_FACTOR
weight = weight * weightScaling;
return (amount * weight) / WEIGHT_FACTOR / 100;
}
/**
* @notice Validate withdraw parameters.
* @param amount The number of tokens to withdraw.
* @param until The date until which the tokens were staked.
* */
function _validateWithdrawParams(uint96 amount, uint256 until) internal view {
require(amount > 0, "Staking::withdraw: amount of tokens to be withdrawn needs to be bigger than 0");
uint96 balance = _getPriorUserStakeByDate(msg.sender, until, block.number - 1);
require(amount <= balance, "Staking::withdraw: not enough balance");
}
/**
* @notice Get the current balance of an account locked until a certain date.
* @param account The user address.
* @param lockDate The lock date.
* @return The stake amount.
* */
function currentBalance(address account, uint256 lockDate) internal view returns (uint96) {
return userStakingCheckpoints[account][lockDate][numUserStakingCheckpoints[account][lockDate] - 1].stake;
}
/**
* @notice Get the number of staked tokens held by the user account.
* @dev Iterate checkpoints adding up stakes.
* @param account The address of the account to get the balance of.
* @return The number of tokens held.
* */
function balanceOf(address account) public view returns (uint96 balance) {
for (uint256 i = kickoffTS; i <= block.timestamp + MAX_DURATION; i += TWO_WEEKS) {
balance = add96(balance, currentBalance(account, i), "Staking::balanceOf: overflow");
}
}
/**
* @notice Delegate votes from `msg.sender` which are locked until lockDate to `delegatee`.
* @param delegatee The address to delegate votes to.
* @param lockDate the date if the position to delegate.
* */
function delegate(address delegatee, uint256 lockDate) public {
_delegate(msg.sender, delegatee, lockDate);
// @dev delegates tokens for lock date 2 weeks later than given lock date
// if message sender is a contract
_delegateNext(msg.sender, delegatee, lockDate);
}
/**
* @notice Delegates votes from signatory to a delegatee account.
* Voting with EIP-712 Signatures.
*
* Voting power can be delegated to any address, and then can be used to
* vote on proposals. A key benefit to users of by-signature functionality
* is that they can create a signed vote transaction for free, and have a
* trusted third-party spend rBTC(or ETH) on gas fees and write it to the
* blockchain for them.
*
* The third party in this scenario, submitting the SOV-holder’s signed
* transaction holds a voting power that is for only a single proposal.
* The signatory still holds the power to vote on their own behalf in
* the proposal if the third party has not yet published the signed
* transaction that was given to them.
*
* @dev The signature needs to be broken up into 3 parameters, known as
* v, r and s:
* const r = '0x' + sig.substring(2).substring(0, 64);
* const s = '0x' + sig.substring(2).substring(64, 128);
* const v = '0x' + sig.substring(2).substring(128, 130);
*
* @param delegatee The address to delegate votes to.
* @param lockDate The date until which the position is locked.
* @param nonce The contract state required to match the signature.
* @param expiry The time at which to expire the signature.
* @param v The recovery byte of the signature.
* @param r Half of the ECDSA signature pair.
* @param s Half of the ECDSA signature pair.
* */
function delegateBySig(
address delegatee,
uint256 lockDate,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) public {
/**
* @dev The DOMAIN_SEPARATOR is a hash that uniquely identifies a
* smart contract. It is built from a string denoting it as an
* EIP712 Domain, the name of the token contract, the version,
* the chainId in case it changes, and the address that the
* contract is deployed at.
* */
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
/// @dev GovernorAlpha uses BALLOT_TYPEHASH, while Staking uses DELEGATION_TYPEHASH
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, lockDate, nonce, expiry));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
/// @dev Verify address is not null and PK is not null either.
require(RSKAddrValidator.checkPKNotZero(signatory), "Staking::delegateBySig: invalid signature");
require(nonce == nonces[signatory]++, "Staking::delegateBySig: invalid nonce");
require(now <= expiry, "Staking::delegateBySig: signature expired");
_delegate(signatory, delegatee, lockDate);
// @dev delegates tokens for lock date 2 weeks later than given lock date
// if message sender is a contract
_delegateNext(signatory, delegatee, lockDate);
}
/**
* @notice Get the current votes balance for a user account.
* @param account The address to get votes balance.
* @dev This is a wrapper to simplify arguments. The actual computation is
* performed on WeightedStaking parent contract.
* @return The number of current votes for a user account.
* */
function getCurrentVotes(address account) external view returns (uint96) {
return getPriorVotes(account, block.number - 1, block.timestamp);
}
/**
* @notice Get the current number of tokens staked for a day.
* @param lockedTS The timestamp to get the staked tokens for.
* */
function getCurrentStakedUntil(uint256 lockedTS) external view returns (uint96) {
uint32 nCheckpoints = numTotalStakingCheckpoints[lockedTS];
return nCheckpoints > 0 ? totalStakingCheckpoints[lockedTS][nCheckpoints - 1].stake : 0;
}
/**
* @notice Set new delegatee. Move from user's current delegate to a new
* delegatee the stake balance.
* @param delegator The user address to move stake balance from its current delegatee.
* @param delegatee The new delegatee. The address to move stake balance to.
* @param lockedTS The lock date.
* */
function _delegate(
address delegator,
address delegatee,
uint256 lockedTS
) internal {
address currentDelegate = delegates[delegator][lockedTS];
uint96 delegatorBalance = currentBalance(delegator, lockedTS);
delegates[delegator][lockedTS] = delegatee;
emit DelegateChanged(delegator, lockedTS, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance, lockedTS);
}
// @dev delegates tokens for lock date 2 weeks later than given lock date
// if message sender is a contract
function _delegateNext(
address delegator,
address delegatee,
uint256 lockedTS
) internal {
if (_isVestingContract()) {
uint256 nextLock = lockedTS.add(TWO_WEEKS);
address currentDelegate = delegates[delegator][nextLock];
if (currentDelegate != delegatee) {
_delegate(delegator, delegatee, nextLock);
}
// @dev workaround for the issue with a delegation of the latest stake
uint256 endDate = IVesting(msg.sender).endDate();
nextLock = lockedTS.add(FOUR_WEEKS);
if (nextLock == endDate) {
currentDelegate = delegates[delegator][nextLock];
if (currentDelegate != delegatee) {
_delegate(delegator, delegatee, nextLock);
}
}
}
}
/**
* @notice Move an amount of delegate stake from a source address to a
* destination address.
* @param srcRep The address to get the staked amount from.
* @param dstRep The address to send the staked amount to.
* @param amount The staked amount to move.
* @param lockedTS The lock date.
* */
function _moveDelegates(
address srcRep,
address dstRep,
uint96 amount,
uint256 lockedTS
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) _decreaseDelegateStake(srcRep, lockedTS, amount);
if (dstRep != address(0)) _increaseDelegateStake(dstRep, lockedTS, amount);
}
}
/**
* @notice Retrieve CHAIN_ID of the executing chain.
*
* Chain identifier (chainID) introduced in EIP-155 protects transaction
* included into one chain from being included into another chain.
* Basically, chain identifier is an integer number being used in the
* processes of signing transactions and verifying transaction signatures.
*
* @dev As of version 0.5.12, Solidity includes an assembly function
* chainid() that provides access to the new CHAINID opcode.
*
* TODO: chainId is included in block. So you can get chain id like
* block timestamp or block number: block.chainid;
* */
function getChainId() internal pure returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
/**
* @notice Allow the owner to set a new staking contract.
* As a consequence it allows the stakers to migrate their positions
* to the new contract.
* @dev Doesn't have any influence as long as migrateToNewStakingContract
* is not implemented.
* @param _newStakingContract The address of the new staking contract.
* */
function setNewStakingContract(address _newStakingContract) public onlyOwner {
require(_newStakingContract != address(0), "can't reset the new staking contract to 0");
newStakingContract = _newStakingContract;
}
/**
* @notice Allow the owner to set a fee sharing proxy contract.
* We need it for unstaking with slashing.
* @param _feeSharing The address of FeeSharingProxy contract.
* */
function setFeeSharing(address _feeSharing) public onlyOwner {
require(_feeSharing != address(0), "FeeSharing address shouldn't be 0");
feeSharing = IFeeSharingProxy(_feeSharing);
}
/**
* @notice Allow the owner to set weight scaling.
* We need it for unstaking with slashing.
* @param _weightScaling The weight scaling.
* */
function setWeightScaling(uint96 _weightScaling) public onlyOwner {
require(
MIN_WEIGHT_SCALING <= _weightScaling && _weightScaling <= MAX_WEIGHT_SCALING,
"weight scaling doesn't belong to range [1, 9]"
);
weightScaling = _weightScaling;
}
/**
* @notice Allow a staker to migrate his positions to the new staking contract.
* @dev Staking contract needs to be set before by the owner.
* Currently not implemented, just needed for the interface.
* In case it's needed at some point in the future,
* the implementation needs to be changed first.
* */
function migrateToNewStakingContract() public {
require(newStakingContract != address(0), "there is no new staking contract set");
/// @dev implementation:
/// @dev Iterate over all possible lock dates from now until now + MAX_DURATION.
/// @dev Read the stake & delegate of the msg.sender
/// @dev If stake > 0, stake it at the new contract until the lock date with the current delegate.
}
/**
* @notice Allow the owner to unlock all tokens in case the staking contract
* is going to be replaced
* Note: Not reversible on purpose. once unlocked, everything is unlocked.
* The owner should not be able to just quickly unlock to withdraw his own
* tokens and lock again.
* @dev Last resort.
* */
function unlockAllTokens() public onlyOwner {
allUnlocked = true;
emit TokensUnlocked(SOVToken.balanceOf(address(this)));
}
/**
* @notice Get list of stakes for a user account.
* @param account The address to get stakes.
* @return The arrays of dates and stakes.
* */
function getStakes(address account) public view returns (uint256[] memory dates, uint96[] memory stakes) {
uint256 latest = timestampToLockDate(block.timestamp + MAX_DURATION);
/// @dev Calculate stakes.
uint256 count = 0;
/// @dev We need to iterate from first possible stake date after deployment to the latest from current time.
for (uint256 i = kickoffTS + TWO_WEEKS; i <= latest; i += TWO_WEEKS) {
if (currentBalance(account, i) > 0) {
count++;
}
}
dates = new uint256[](count);
stakes = new uint96[](count);
/// @dev We need to iterate from first possible stake date after deployment to the latest from current time.
uint256 j = 0;
for (uint256 i = kickoffTS + TWO_WEEKS; i <= latest; i += TWO_WEEKS) {
uint96 balance = currentBalance(account, i);
if (balance > 0) {
dates[j] = i;
stakes[j] = balance;
j++;
}
}
}
/**
* @notice Overrides default ApprovalReceiver._getToken function to
* register SOV token on this contract.
* @return The address of SOV token.
* */
function _getToken() internal view returns (address) {
return address(SOVToken);
}
/**
* @notice Overrides default ApprovalReceiver._getSelectors function to
* register stakeWithApproval selector on this contract.
* @return The array of registered selectors on this contract.
* */
function _getSelectors() internal view returns (bytes4[] memory) {
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = this.stakeWithApproval.selector;
return selectors;
}
}
/* Interfaces */
interface TimelockInterface {
function delay() external view returns (uint256);
function GRACE_PERIOD() external view returns (uint256);
function acceptAdmin() external;
function queuedTransactions(bytes32 hash) external view returns (bool);
function queueTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external returns (bytes32);
function cancelTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external;
function executeTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external payable returns (bytes memory);
}
interface StakingInterface {
function getPriorVotes(
address account,
uint256 blockNumber,
uint256 date
) external view returns (uint96);
function getPriorTotalVotingPower(uint32 blockNumber, uint256 time) external view returns (uint96);
}
/**
* @title Governance Contract.
* @notice This is an adapted clone of compound’s governance model. In general,
* the process is the same: Token holders can make (executable) proposals if
* they possess enough voting power, vote on proposals during a predefined
* voting period and in the end evaluate the outcome. If successful, the
* proposal will be scheduled on the timelock contract. Only after sufficient
* time passed, it can be executed. A minimum voting power is required for
* making a proposal as well as a minimum quorum.
*
* Voting power in the Bitocracy:
* Stakers will receive voting power in the Bitocracy in return for their
* staking commitment. This voting power is weighted by how much SOV is staked
* and for how long the staking period is - staking more SOV over longer staking
* periods results in higher voting power. With this voting power, users can
* vote for or against any SIP in bitocracy.sovryn.app.
*
* mainnet: 0xce3a21a69c05cfe638b722f53593a047bdd6e9de
* testnet:
*
* */
contract GovernorAlpha is SafeMath96 {
/* Storage */
/// @notice The name of this contract.
string public constant NAME = "Sovryn Governor Alpha";
/// @notice The maximum number of actions that can be included in a proposal.
function proposalMaxOperations() public pure returns (uint256) {
return 10;
} // 10 actions
/// @notice The delay before voting on a proposal may take place, once proposed.
function votingDelay() public pure returns (uint256) {
return 1;
} // 1 block
/// @notice The duration of voting on a proposal, in blocks.
function votingPeriod() public pure returns (uint256) {
return 2880;
} // ~1 day in blocks (assuming 30s blocks)
/// @notice The address of the Sovryn Protocol Timelock.
ITimelock public timelock;
/// @notice The address of the Sovryn staking contract.
IStaking public staking;
/// @notice The address of the Governor Guardian.
address public guardian;
/// @notice The total number of proposals.
uint256 public proposalCount;
/// @notice Percentage of current total voting power require to vote.
// the query reverts
uint96 public quorumPercentageVotes;
// @notice Majority percentage.
// the quey reverts
uint96 public majorityPercentageVotes;
struct Proposal {
/// @notice Unique id for looking up a proposal.
uint256 id;
/// @notice The block at which voting begins: holders must delegate their votes prior to this block.
uint32 startBlock;
/// @notice The block at which voting ends: votes must be cast prior to this block.
uint32 endBlock;
/// @notice Current number of votes in favor of this proposal.
uint96 forVotes;
/// @notice Current number of votes in opposition to this proposal.
uint96 againstVotes;
///@notice the quorum required for this proposal.
uint96 quorum;
///@notice the majority percentage required for this proposal.
uint96 majorityPercentage;
/// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds.
uint64 eta;
/// @notice the start time is required for the staking contract.
uint64 startTime;
/// @notice Flag marking whether the proposal has been canceled.
bool canceled;
/// @notice Flag marking whether the proposal has been executed.
bool executed;
/// @notice Creator of the proposal.
address proposer;
/// @notice the ordered list of target addresses for calls to be made.
address[] targets;
/// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made.
uint256[] values;
/// @notice The ordered list of function signatures to be called.
string[] signatures;
/// @notice The ordered list of calldata to be passed to each call.
bytes[] calldatas;
/// @notice Receipts of ballots for the entire set of voters.
mapping(address => Receipt) receipts;
}
/// @notice Ballot receipt record for a voter
struct Receipt {
/// @notice Whether or not a vote has been cast.
bool hasVoted;
/// @notice Whether or not the voter supports the proposal.
bool support;
/// @notice The number of votes the voter had, which were cast.
uint96 votes;
}
/// @notice Possible states that a proposal may be in.
enum ProposalState { Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, Executed }
/// @notice The official record of all proposals ever proposed.
mapping(uint256 => Proposal) public proposals;
/// @notice The latest proposal for each proposer.
mapping(address => uint256) public latestProposalIds;
/// @notice The EIP-712 typehash for the contract's domain.
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
/// @notice The EIP-712 typehash for the ballot struct used by the contract.
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)");
/* Events */
/// @notice An event emitted when a new proposal is created.
event ProposalCreated(
uint256 id,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
string description
);
/// @notice An event emitted when a vote has been cast on a proposal.
event VoteCast(address voter, uint256 proposalId, bool support, uint256 votes);
/// @notice An event emitted when a proposal has been canceled.
event ProposalCanceled(uint256 id);
/// @notice An event emitted when a proposal has been queued in the Timelock.
event ProposalQueued(uint256 id, uint256 eta);
/// @notice An event emitted when a proposal has been executed in the Timelock.
event ProposalExecuted(uint256 id);
/* Functions */
constructor(
address timelock_,
address staking_,
address guardian_,
uint96 _quorumPercentageVotes, // not seen in the constructor
uint96 _majorityPercentageVotes // not seen in the constructor
) public {
timelock = ITimelock(timelock_);
staking = IStaking(staking_);
guardian = guardian_;
quorumPercentageVotes = _quorumPercentageVotes;
majorityPercentageVotes = _majorityPercentageVotes;
}
/// @notice The number of votes required in order for a voter to become a proposer.
function proposalThreshold() public view returns (uint96) {
uint96 totalVotingPower =
staking.getPriorTotalVotingPower(
safe32(block.number - 1, "GovernorAlpha::proposalThreshold: block number overflow"),
block.timestamp
);
// 1% of current total voting power.
return totalVotingPower / 100;
}
/// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed.
function quorumVotes() public view returns (uint96) {
uint96 totalVotingPower =
staking.getPriorTotalVotingPower(
safe32(block.number - 1, "GovernorAlpha::quorumVotes: block number overflow"),
block.timestamp
);
// 4% of current total voting power.
return mul96(quorumPercentageVotes, totalVotingPower, "GovernorAlpha::quorumVotes:multiplication overflow") / 100;
}
/**
* @notice Create a new proposal.
* @param targets Array of contract addresses to perform proposal execution.
* @param values Array of rBTC amounts to send on proposal execution.
* @param signatures Array of function signatures to call on proposal execution.
* @param calldatas Array of payloads for the calls on proposal execution.
* @param description Text describing the purpose of the proposal.
* */
function propose(
address[] memory targets,
uint256[] memory values,
string[] memory signatures,
bytes[] memory calldatas,
string memory description
) public returns (uint256) {
// note: passing this block's timestamp, but the number of the previous block.
// todo: think if it would be better to pass block.timestamp - 30 (average block time)
// (probably not because proposal starts in 1 block from now).
uint96 threshold = proposalThreshold();
require(
staking.getPriorVotes(msg.sender, sub256(block.number, 1), block.timestamp) > threshold,
"GovernorAlpha::propose: proposer votes below proposal threshold"
);
require(
targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,
"GovernorAlpha::propose: proposal function information arity mismatch"
);
require(targets.length != 0, "GovernorAlpha::propose: must provide actions");
require(targets.length <= proposalMaxOperations(), "GovernorAlpha::propose: too many actions");
uint256 latestProposalId = latestProposalIds[msg.sender];
if (latestProposalId != 0) {
ProposalState proposersLatestProposalState = state(latestProposalId);
require(
proposersLatestProposalState != ProposalState.Active,
"GovernorAlpha::propose: one live proposal per proposer, found an already active proposal"
);
require(
proposersLatestProposalState != ProposalState.Pending,
"GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal"
);
}
uint256 startBlock = add256(block.number, votingDelay());
uint256 endBlock = add256(startBlock, votingPeriod());
proposalCount++;
/// @dev quorum: proposalThreshold is 1% of total votes, we can save gas using this pre calculated value.
/// @dev startTime: Required by the staking contract. not used by the governance contract itself.
Proposal memory newProposal =
Proposal({
id: proposalCount,
startBlock: safe32(startBlock, "GovernorAlpha::propose: start block number overflow"),
endBlock: safe32(endBlock, "GovernorAlpha::propose: end block number overflow"),
forVotes: 0,
againstVotes: 0,
quorum: mul96(quorumPercentageVotes, threshold, "GovernorAlpha::propose: overflow on quorum computation"),
majorityPercentage: mul96(
majorityPercentageVotes,
threshold,
"GovernorAlpha::propose: overflow on majorityPercentage computation"
),
eta: 0,
startTime: safe64(block.timestamp, "GovernorAlpha::propose: startTime overflow"),
canceled: false,
executed: false,
proposer: msg.sender,
targets: targets,
values: values,
signatures: signatures,
calldatas: calldatas
});
proposals[newProposal.id] = newProposal;
latestProposalIds[newProposal.proposer] = newProposal.id;
emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description);
return newProposal.id;
}
/**
* @notice Enqueue a proposal and everyone of its calls.
* @param proposalId Proposal index to access the list proposals[] from storage.
* */
function queue(uint256 proposalId) public {
require(state(proposalId) == ProposalState.Succeeded, "GovernorAlpha::queue: proposal can only be queued if it is succeeded");
Proposal storage proposal = proposals[proposalId];
uint256 eta = add256(block.timestamp, timelock.delay());
for (uint256 i = 0; i < proposal.targets.length; i++) {
_queueOrRevert(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta);
}
proposal.eta = safe64(eta, "GovernorAlpha::queue: ETA overflow");
emit ProposalQueued(proposalId, eta);
}
/**
* @notice Tries to enqueue a proposal, verifying it has not been previously queued.
* @param target Contract addresses to perform proposal execution.
* @param value rBTC amount to send on proposal execution.
* @param signature Function signature to call on proposal execution.
* @param data Payload for the call on proposal execution.
* @param eta Estimated Time of Accomplishment. The timestamp that the
* proposal will be available for execution, set once the vote succeeds.
* */
function _queueOrRevert(
address target,
uint256 value,
string memory signature,
bytes memory data,
uint256 eta
) internal {
require(
!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))),
"GovernorAlpha::_queueOrRevert: proposal action already queued at eta"
);
timelock.queueTransaction(target, value, signature, data, eta);
}
/**
* @notice Execute a proposal by looping and performing everyone of its calls.
* @param proposalId Proposal index to access the list proposals[] from storage.
* */
function execute(uint256 proposalId) public payable {
require(state(proposalId) == ProposalState.Queued, "GovernorAlpha::execute: proposal can only be executed if it is queued");
Proposal storage proposal = proposals[proposalId];
proposal.executed = true;
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.executeTransaction.value(proposal.values[i])(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalExecuted(proposalId);
}
/**
* @notice Cancel a proposal by looping and cancelling everyone of its calls.
* @param proposalId Proposal index to access the list proposals[] from storage.
* */
function cancel(uint256 proposalId) public {
ProposalState state = state(proposalId);
require(state != ProposalState.Executed, "GovernorAlpha::cancel: cannot cancel executed proposal");
Proposal storage proposal = proposals[proposalId];
/// @notice Cancel only if sent by the guardian.
require(msg.sender == guardian, "GovernorAlpha::cancel: sender isn't a guardian");
proposal.canceled = true;
for (uint256 i = 0; i < proposal.targets.length; i++) {
timelock.cancelTransaction(
proposal.targets[i],
proposal.values[i],
proposal.signatures[i],
proposal.calldatas[i],
proposal.eta
);
}
emit ProposalCanceled(proposalId);
}
/**
* @notice Get a proposal list of its calls.
* @param proposalId Proposal index to access the list proposals[] from storage.
* @return Arrays of the 4 call parameters: targets, values, signatures, calldatas.
* */
function getActions(uint256 proposalId)
public
view
returns (
address[] memory targets,
uint256[] memory values,
string[] memory signatures,
bytes[] memory calldatas
)
{
Proposal storage p = proposals[proposalId];
return (p.targets, p.values, p.signatures, p.calldatas);
}
/**
* @notice Get a proposal receipt.
* @param proposalId Proposal index to access the list proposals[] from storage.
* @param voter A governance stakeholder with voting power.
* @return The voter receipt of the proposal.
* */
function getReceipt(uint256 proposalId, address voter) public view returns (Receipt memory) {
return proposals[proposalId].receipts[voter];
}
/**
* @notice Casts a vote by sender.
* @param proposalId Proposal index to access the list proposals[] from storage.
* @param support Vote value, yes or no.
* */
function castVote(uint256 proposalId, bool support) public {
return _castVote(msg.sender, proposalId, support);
}
/**
* @notice Voting with EIP-712 Signatures.
*
* Voting power can be delegated to any address, and then can be used to
* vote on proposals. A key benefit to users of by-signature functionality
* is that they can create a signed vote transaction for free, and have a
* trusted third-party spend rBTC(or ETH) on gas fees and write it to the
* blockchain for them.
*
* The third party in this scenario, submitting the SOV-holder’s signed
* transaction holds a voting power that is for only a single proposal.
* The signatory still holds the power to vote on their own behalf in
* the proposal if the third party has not yet published the signed
* transaction that was given to them.
*
* @dev The signature needs to be broken up into 3 parameters, known as
* v, r and s:
* const r = '0x' + sig.substring(2).substring(0, 64);
* const s = '0x' + sig.substring(2).substring(64, 128);
* const v = '0x' + sig.substring(2).substring(128, 130);
*
* @param proposalId Proposal index to access the list proposals[] from storage.
* @param support Vote value, yes or no.
* @param v The recovery byte of the signature.
* @param r Half of the ECDSA signature pair.
* @param s Half of the ECDSA signature pair.
* */
function castVoteBySig(
uint256 proposalId,
bool support,
uint8 v,
bytes32 r,
bytes32 s
) public {
/**
* @dev The DOMAIN_SEPARATOR is a hash that uniquely identifies a
* smart contract. It is built from a string denoting it as an
* EIP712 Domain, the name of the token contract, the version,
* the chainId in case it changes, and the address that the
* contract is deployed at.
* */
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(NAME)), getChainId(), address(this)));
/// @dev GovernorAlpha uses BALLOT_TYPEHASH, while Staking uses DELEGATION_TYPEHASH
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
/// @dev Verify address is not null and PK is not null either.
require(RSKAddrValidator.checkPKNotZero(signatory), "GovernorAlpha::castVoteBySig: invalid signature");
return _castVote(signatory, proposalId, support);
}
/**
* @notice Cast a vote, adding it to the total counting.
* @param voter A governance stakeholder with voting power that is casting the vote.
* @param proposalId Proposal index to access the list proposals[] from storage.
* @param support Vote value, yes or no.
* */
function _castVote(
address voter,
uint256 proposalId,
bool support
) internal {
require(state(proposalId) == ProposalState.Active, "GovernorAlpha::_castVote: voting is closed");
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
require(receipt.hasVoted == false, "GovernorAlpha::_castVote: voter already voted");
uint96 votes = staking.getPriorVotes(voter, proposal.startBlock, proposal.startTime);
if (support) {
proposal.forVotes = add96(proposal.forVotes, votes, "GovernorAlpha::_castVote: vote overflow");
} else {
proposal.againstVotes = add96(proposal.againstVotes, votes, "GovernorAlpha::_castVote: vote overflow");
}
receipt.hasVoted = true;
receipt.support = support;
receipt.votes = votes;
emit VoteCast(voter, proposalId, support, votes);
}
/// @dev Timelock wrapper w/ sender check.
function __acceptAdmin() public {
require(msg.sender == guardian, "GovernorAlpha::__acceptAdmin: sender must be gov guardian");
timelock.acceptAdmin();
}
/// @notice Sets guardian address to zero.
function __abdicate() public {
require(msg.sender == guardian, "GovernorAlpha::__abdicate: sender must be gov guardian");
guardian = address(0);
}
/// @dev Timelock wrapper w/ sender check.
function __queueSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) public {
require(msg.sender == guardian, "GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian");
timelock.queueTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
}
/// @dev Timelock wrapper w/ sender check.
function __executeSetTimelockPendingAdmin(address newPendingAdmin, uint256 eta) public {
require(msg.sender == guardian, "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian");
timelock.executeTransaction(address(timelock), 0, "setPendingAdmin(address)", abi.encode(newPendingAdmin), eta);
}
/**
* @notice Get a proposal state.
* @param proposalId Proposal index to access the list proposals[] from storage.
* @return The state of the proposal: Canceled, Pending, Active, Defeated,
* Succeeded, Executed, Expired.
* */
function state(uint256 proposalId) public view returns (ProposalState) {
require(proposalCount >= proposalId && proposalId > 0, "GovernorAlpha::state: invalid proposal id");
Proposal storage proposal = proposals[proposalId];
if (proposal.canceled) {
return ProposalState.Canceled;
}
if (block.number <= proposal.startBlock) {
return ProposalState.Pending;
}
if (block.number <= proposal.endBlock) {
return ProposalState.Active;
}
uint96 totalVotes = add96(proposal.forVotes, proposal.againstVotes, "GovernorAlpha:: state: forVotes + againstVotes > uint96");
uint96 totalVotesMajorityPercentage = div96(totalVotes, 100, "GovernorAlpha:: state: division error");
totalVotesMajorityPercentage = mul96(
totalVotesMajorityPercentage,
majorityPercentageVotes,
"GovernorAlpha:: state: totalVotes * majorityPercentage > uint96"
);
if (proposal.forVotes <= totalVotesMajorityPercentage || totalVotes < proposal.quorum) {
return ProposalState.Defeated;
}
if (proposal.eta == 0) {
return ProposalState.Succeeded;
}
if (proposal.executed) {
return ProposalState.Executed;
}
if (block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())) {
return ProposalState.Expired;
}
return ProposalState.Queued;
}
/// @dev TODO: use OpenZeppelin's SafeMath function instead.
function add256(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "addition overflow");
return c;
}
/// @dev TODO: use OpenZeppelin's SafeMath function instead.
function sub256(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "subtraction underflow");
return a - b;
}
/**
* @notice Retrieve CHAIN_ID of the executing chain.
*
* Chain identifier (chainID) introduced in EIP-155 protects transaction
* included into one chain from being included into another chain.
* Basically, chain identifier is an integer number being used in the
* processes of signing transactions and verifying transaction signatures.
*
* @dev As of version 0.5.12, Solidity includes an assembly function
* chainid() that provides access to the new CHAINID opcode.
*
* TODO: chainId is included in block. So you can get chain id like
* block timestamp or block number: block.chainid;
* */
function getChainId() internal pure returns (uint256) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId;
}
}