Address Contract Verified
Address
0xEE6A649Aa3766bD117e12C161726b693A1B2Ee20
Balance
0 ETH
Nonce
1
Code Size
7657 bytes
Creator
0x76EB574E...4624 at tx 0x81d86b83...bd4a26
Indexed Transactions
0
Contract Bytecode
7657 bytes
0x608060405234801561001057600080fd5b50600436106101985760003560e01c8063681c637a116100e357806376992ec61161008c578063a694fc3a11610066578063a694fc3a146103f2578063bed3b60c14610405578063c07473f61461040f57600080fd5b806376992ec6146103af578063817b1cd2146103c257806390d92e76146103cb57600080fd5b80636d2beef1116100bd5780636d2beef1146103725780636d4361871461037c57806372f702f31461038457600080fd5b8063681c637a146103395780636c68c0e11461034c5780636ce0a99f1461035f57600080fd5b8063363487bc11610145578063584b62a11161011f578063584b62a1146102b05780636198e339146102f5578063671b37931461030857600080fd5b8063363487bc1461026c578063379607f51461027457806349fc9df71461028757600080fd5b806308d465e61161017657806308d465e6146101e057806309fe7dd81461020e57806324d63ba31461024357600080fd5b8063011de7aa1461019d578063074bc01d146101c357806308bbb824146101cb575b600080fd5b6101b06101ab366004611c38565b61042f565b6040519081526020015b60405180910390f35b6002546101b0565b6101de6101d9366004611c51565b61050a565b005b6101f36101ee366004611be3565b610732565b604080518251815260209283015192810192909252016101ba565b61022161021c366004611c38565b6108d1565b60408051825181526020808401519082015291810151908201526060016101ba565b6101b0610251366004611b9e565b6001600160a01b031660009081526005602052604090205490565b6101b0610946565b6101de610282366004611c38565b610956565b6101b0610295366004611b9e565b6001600160a01b031660009081526003602052604090205490565b6102c36102be366004611bb9565b610ba3565b604080516001600160a01b0390961686526020860194909452928401919091526060830152608082015260a0016101ba565b6101de610303366004611c38565b610bfb565b6000805260046020527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec54196101b0565b610221610347366004611c7d565b610df5565b6101de61035a366004611c38565b610fb0565b6101f361036d366004611bb9565b610fbd565b6101b06201518081565b6101b0611030565b600054610397906001600160a01b031681565b6040516001600160a01b0390911681526020016101ba565b6101b06103bd366004611b9e565b611078565b6101b060015481565b6101b07f0000000000000000000000000000000000000000000000000000000060e5c26081565b6101b0610400366004611c38565b6110f2565b6101b062278d0081565b6101b061041d366004611b9e565b60046020526000908152604090205481565b60007f0000000000000000000000000000000000000000000000000000000060e5c2608210156104cc5760405162461bcd60e51b815260206004820152603760248201527f5374616b696e673a205265717565737465642074696d65206973206265666f7260448201527f6520636f6e747261637420776173206465706c6f79656400000000000000000060648201526084015b60405180910390fd5b620151806104fa7f0000000000000000000000000000000000000000000000000000000060e5c26084611d1a565b6105049190611d06565b92915050565b33600090815260036020526040812080548490811061052b5761052b611d9d565b906000526020600020906005020190508060020154600014156105905760405162461bcd60e51b815260206004820152601c60248201527f5374616b696e673a205374616b6520646f65736e27742065786973740000000060448201526064016104c3565b6003810154156105e25760405162461bcd60e51b815260206004820152601760248201527f5374616b696e673a205374616b6520756e6c6f636b656400000000000000000060448201526064016104c3565b6001600160a01b03821661065d5760405162461bcd60e51b8152602060048201526024808201527f5374616b696e673a2043616e27742064656c656761746520746f20302061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016104c3565b80546001600160a01b0383811691161461072d578054610685906001600160a01b0316611269565b61068e82611269565b805460018201546106aa916001600160a01b0316908490611384565b805460018201546040805186815260208101929092526001600160a01b0385811693169133917f086d57859b1d11780b2ef086bf84cef0e295c4cec8900b8e1620c6666b667542910160405180910390a480547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383161781555b505050565b604080518082019091526000808252602082015261074e610946565b83111561079d5760405162461bcd60e51b815260206004820152601f60248201527f5374616b696e673a20496e74657276616c206f7574206f6620626f756e64730060448201526064016104c3565b6001600160a01b0384166000908152600560205260409020805483118015906107fa57508215806107fa575083816107d6600186611d1a565b815481106107e6576107e6611d9d565b906000526020600020906002020160000154105b80156108335750805483148061083357508381848154811061081e5761081e611d9d565b90600052602060002090600202016000015410155b156108bc5780548310156108885780838154811061085357610853611d9d565b9060005260206000209060020201604051806040016040529081600082015481526020016001820154815250509150506108ca565b50506040805180820182528381526001600160a01b03851660009081526004602090815292902054918101919091526108ca565b6108c685856113e3565b9150505b9392505050565b6108f560405180606001604052806000815260200160008152602001600081525090565b6002828154811061090857610908611d9d565b906000526020600020906003020160405180606001604052908160008201548152602001600182015481526020016002820154815250509050919050565b60006109514261042f565b905090565b33600090815260036020526040902080548290811061097757610977611d9d565b9060005260206000209060050201600301546000141580156109cc57503360009081526003602052604090208054429190839081106109b8576109b8611d9d565b906000526020600020906005020160030154105b610a185760405162461bcd60e51b815260206004820152601b60248201527f5374616b696e673a205374616b65206e6f7420756e6c6f636b6564000000000060448201526064016104c3565b336000908152600360205260409020805482908110610a3957610a39611d9d565b906000526020600020906005020160040154600014610a9a5760405162461bcd60e51b815260206004820152601e60248201527f5374616b696e673a205374616b6520616c726561647920636c61696d6564000060448201526064016104c3565b610aa333611269565b336000908152600360205260409020805442919083908110610ac757610ac7611d9d565b6000918252602080832060046005909302019190910192909255338152600390915260409020805482908110610aff57610aff611d9d565b90600052602060002090600502016001015460016000828254610b229190611d1a565b90915550503360008181526003602052604090208054610b7392919084908110610b4e57610b4e611d9d565b6000918252602082206001600590920201015490546001600160a01b031691906115c3565b604051819033907f47cee97cb7acd717b3c0aa1435d004cd5b3c8c57d70dbceb4e4458bbd60e39d490600090a350565b60036020528160005260406000208181548110610bbf57600080fd5b6000918252602090912060059091020180546001820154600283015460038401546004909401546001600160a01b039093169550909350919085565b336000908152600360205260409020805482908110610c1c57610c1c611d9d565b90600052602060002090600502016002015460001415610c7e5760405162461bcd60e51b815260206004820152601c60248201527f5374616b696e673a205374616b6520646f65736e27742065786973740000000060448201526064016104c3565b336000908152600360205260409020805482908110610c9f57610c9f611d9d565b906000526020600020906005020160030154600014610d005760405162461bcd60e51b815260206004820152601f60248201527f5374616b696e673a205374616b6520616c726561647920756e6c6f636b65640060448201526064016104c3565b610d0933611269565b610d1662278d0042611cee565b336000908152600360205260409020805483908110610d3757610d37611d9d565b600091825260208083206003600590930201820193909355338252909152604090208054610dc5919083908110610d7057610d70611d9d565b6000918252602080832060059092029091015433835260039091526040822080546001600160a01b03909216929185908110610dae57610dae611d9d565b906000526020600020906005020160010154611384565b604051819033907f6381d9813cabeb57471b5a7e05078e64845ccdb563146a6911d536f24ce960f190600090a350565b610e1960405180606001604052806000815260200160008152602001600081525090565b610e21610946565b831115610e705760405162461bcd60e51b815260206004820152601f60248201527f5374616b696e673a20496e74657276616c206f7574206f6620626f756e64730060448201526064016104c3565b6002548211801590610eb75750811580610eb75750826002610e93600185611d1a565b81548110610ea357610ea3611d9d565b906000526020600020906003020160000154105b8015610ef25750600254821480610ef257508260028381548110610edd57610edd611d9d565b90600052602060002090600302016000015410155b15610fa757600254821015610f525760028281548110610f1457610f14611d9d565b906000526020600020906003020160405180606001604052908160008201548152602001600182015481526020016002820154815250509050610504565b6040518060600160405280848152602001610f956000805260046020527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec541990565b81526020016001548152509050610504565b6108ca8361166c565b610fba813361050a565b50565b60408051808201909152600080825260208201526001600160a01b0383166000908152600560205260409020805483908110610ffb57610ffb611d9d565b906000526020600020906002020160405180604001604052908160008201548152602001600182015481525050905092915050565b60025460009015611072576002805461104b90600190611d1a565b8154811061105b5761105b611d9d565b906000526020600020906003020160000154905090565b50600090565b6001600160a01b038116600090815260056020526040812054156110e5576001600160a01b038216600090815260056020526040902080546110bc90600190611d1a565b815481106110cc576110cc611d9d565b9060005260206000209060020201600001549050919050565b506000919050565b919050565b60008082116111435760405162461bcd60e51b815260206004820152601760248201527f5374616b696e673a20416d6f756e74206e6f742073657400000000000000000060448201526064016104c3565b61114c33611269565b3360008181526003602081815260408084208054825160a0810184529687528684018981524293880193845260608801878152608089018881526001808501865594895295882098516005840290990180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b03909a169990991789559051888401559251600288015591519386019390935590516004909401939093558054859290611202908490611cee565b90915550611214905060003385611384565b60005461122c906001600160a01b0316333086611865565b604051838152819033907f5af417134f72a9d41143ace85b0a26dce6f550f894f2cbc1eeee8810603d91b69060200160405180910390a392915050565b6000611273610946565b90508061127e611030565b101561130357600260405180606001604052808381526020016112c96000805260046020527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec541990565b8152600180546020928301528354808201855560009485529382902083516003909502019384559082015190830155604001516002909101555b6001600160a01b0382161580159061132257508061132083611078565b105b15611380576001600160a01b0382166000818152600560209081526040808320815180830183528681529484526004835290832054848301908152815460018181018455928552929093209351600290920290930190815590519101555b5050565b6001600160a01b038316600090815260046020526040812080548392906113ac908490611d1a565b90915550506001600160a01b038216600090815260046020526040812080548392906113d9908490611cee565b9091555050505050565b60408051808201909152600080825260208201526113ff610946565b82111561144e5760405162461bcd60e51b815260206004820152601f60248201527f5374616b696e673a20496e74657276616c206f7574206f6620626f756e64730060448201526064016104c3565b6001600160a01b0383166000908152600560205260408120805490919081905b808210156114cb57600061148283836118bc565b90508685828154811061149757611497611d9d565b90600052602060002090600202016000015411156114b7578091506114c5565b6114c2816001611cee565b92505b5061146e565b600082118015611507575085846114e3600185611d1a565b815481106114f3576114f3611d9d565b906000526020600020906002020160000154145b156115605783611518600184611d1a565b8154811061152857611528611d9d565b906000526020600020906002020160405180604001604052908160008201548152602001600182015481525050945050505050610504565b81925083548314156115b1576040518060400160405280878152602001600460008a6001600160a01b03166001600160a01b0316815260200190815260200160002054815250945050505050610504565b83838154811061152857611528611d9d565b6040516001600160a01b03831660248201526044810182905261072d9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152611913565b61169060405180606001604052806000815260200160008152602001600081525090565b611698610946565b8211156116e75760405162461bcd60e51b815260206004820152601f60248201527f5374616b696e673a20496e74657276616c206f7574206f6620626f756e64730060448201526064016104c3565b60025460009081905b8082101561174e57600061170483836118bc565b9050856002828154811061171a5761171a611d9d565b906000526020600020906003020160000154111561173a57809150611748565b611745816001611cee565b92505b506116f0565b60008211801561178b5750846002611767600185611d1a565b8154811061177757611777611d9d565b906000526020600020906003020160000154145b156117ee57600261179d600184611d1a565b815481106117ad576117ad611d9d565b906000526020600020906003020160405180606001604052908160008201548152602001600182015481526020016002820154815250509350505050919050565b81925060025483141561185257604051806060016040528086815260200161183e6000805260046020527f17ef568e3e12ab5b9c7254a8d58478811de00f9e6eb34345acd53bf8fd09d3ec541990565b815260015460209091015295945050505050565b600283815481106117ad576117ad611d9d565b6040516001600160a01b03808516602483015283166044820152606481018290526118b69085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401611608565b50505050565b600060026118ca8184611d5d565b6118d5600286611d5d565b6118df9190611cee565b6118e99190611d06565b6118f4600284611d06565b6118ff600286611d06565b6119099190611cee565b6108ca9190611cee565b6000611968826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166119f89092919063ffffffff16565b80519091501561072d57808060200190518101906119869190611c16565b61072d5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016104c3565b6060611a078484600085611a0f565b949350505050565b606082471015611a875760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016104c3565b843b611ad55760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016104c3565b600080866001600160a01b03168587604051611af19190611c9f565b60006040518083038185875af1925050503d8060008114611b2e576040519150601f19603f3d011682016040523d82523d6000602084013e611b33565b606091505b5091509150611b43828286611b4e565b979650505050505050565b60608315611b5d5750816108ca565b825115611b6d5782518084602001fd5b8160405162461bcd60e51b81526004016104c39190611cbb565b80356001600160a01b03811681146110ed57600080fd5b600060208284031215611bb057600080fd5b6108ca82611b87565b60008060408385031215611bcc57600080fd5b611bd583611b87565b946020939093013593505050565b600080600060608486031215611bf857600080fd5b611c0184611b87565b95602085013595506040909401359392505050565b600060208284031215611c2857600080fd5b815180151581146108ca57600080fd5b600060208284031215611c4a57600080fd5b5035919050565b60008060408385031215611c6457600080fd5b82359150611c7460208401611b87565b90509250929050565b60008060408385031215611c9057600080fd5b50508035926020909101359150565b60008251611cb1818460208701611d31565b9190910192915050565b6020815260008251806020840152611cda816040850160208701611d31565b601f01601f19169190910160400192915050565b60008219821115611d0157611d01611d71565b500190565b600082611d1557611d15611d87565b500490565b600082821015611d2c57611d2c611d71565b500390565b60005b83811015611d4c578181015183820152602001611d34565b838111156118b65750506000910152565b600082611d6c57611d6c611d87565b500690565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b634e487b7160e01b600052603260045260246000fdfea26469706673582212200bc76e8c31b7c2ef9cbf1207c5a43e3ce6501c7503264a343ed138f63e0aed1e64736f6c63430008060033
Verified Source Code Full Match
Compiler: v0.8.6+commit.11564f7e
EVM: berlin
Optimization: Yes (1600 runs)
Staking.sol 554 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
pragma abicoder v2;
// OpenZeppelin v4
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title Snapshot
* @author Railgun Contributors
* @notice Governance contract for railgun, handles staking, voting power, and snapshotting
* @dev Snapshots cannot be taken during interval 0
* wait till interval 1 before utilising snapshots
*/
contract Staking {
using SafeERC20 for IERC20;
// Constants
uint256 public constant STAKE_LOCKTIME = 30 days;
uint256 public constant SNAPSHOT_INTERVAL = 1 days;
// Staking token
IERC20 public stakingToken;
// Time of deployment
// solhint-disable-next-line var-name-mixedcase
uint256 public immutable DEPLOY_TIME = block.timestamp;
// New stake screated
event Stake(address indexed account, uint256 indexed stakeID, uint256 amount);
// Stake unlocked (coins removed from voting pool, 30 day delay before claiming is allowed)
event Unlock(address indexed account, uint256 indexed stakeID);
// Stake claimed
event Claim(address indexed account, uint256 indexed stakeID);
// Delegate claimed
event Delegate(address indexed owner, address indexed _from, address indexed to, uint256 stakeID, uint256 amount);
// Total staked
uint256 public totalStaked = 0;
// Snapshots for globals
struct GlobalsSnapshot {
uint256 interval;
uint256 totalVotingPower;
uint256 totalStaked;
}
GlobalsSnapshot[] private globalsSnapshots;
// Stake
struct StakeStruct {
address delegate; // Address stake voting power is delegated to
uint256 amount; // Amount of tokens on this stake
uint256 staketime; // Time this stake was created
uint256 locktime; // Time this stake can be claimed (if 0, unlock hasn't been initiated)
uint256 claimedTime; // Time this stake was claimed (if 0, stake hasn't been claimed)
}
// Stake mapping
// address => stakeID => stake
mapping(address => StakeStruct[]) public stakes;
// Voting power for each account
mapping(address => uint256) public votingPower;
// Snapshots for accounts
struct AccountSnapshot {
uint256 interval;
uint256 votingPower;
}
mapping(address => AccountSnapshot[]) private accountSnapshots;
/**
* @notice Sets staking token
* @param _stakingToken - time to get interval of
*/
constructor(IERC20 _stakingToken) {
stakingToken = _stakingToken;
// Use address 0 to store inverted totalVotingPower
votingPower[address(0)] = type(uint256).max;
}
/**
* @notice Gets total voting power in system
* @return totalVotingPower
*/
function totalVotingPower() public view returns (uint256) {
return ~votingPower[address(0)];
}
/**
* @notice Gets length of stakes array for address
* @param _account - address to retrieve stakes array of
* @return length
*/
function stakesLength(address _account) external view returns (uint256) {
return stakes[_account].length;
}
/**
* @notice Gets interval at time
* @param _time - time to get interval of
* @return interval
*/
function intervalAtTime(uint256 _time) public view returns (uint256) {
require(_time >= DEPLOY_TIME, "Staking: Requested time is before contract was deployed");
return (_time - DEPLOY_TIME) / SNAPSHOT_INTERVAL;
}
/**
* @notice Gets current interval
* @return interval
*/
function currentInterval() public view returns (uint256) {
return intervalAtTime(block.timestamp);
}
/**
* @notice Returns interval of latest global snapshot
* @return Latest global snapshot interval
*/
function latestGlobalsSnapshotInterval() public view returns (uint256) {
if (globalsSnapshots.length > 0) {
// If a snapshot exists return the interval it was taken
return globalsSnapshots[globalsSnapshots.length - 1].interval;
} else {
// Else default to 0
return 0;
}
}
/**
* @notice Returns interval of latest account snapshot
* @param _account - account to get latest snapshot of
* @return Latest account snapshot interval
*/
function latestAccountSnapshotInterval(address _account) public view returns (uint256) {
if (accountSnapshots[_account].length > 0) {
// If a snapshot exists return the interval it was taken
return accountSnapshots[_account][accountSnapshots[_account].length - 1].interval;
} else {
// Else default to 0
return 0;
}
}
/**
* @notice Returns length of snapshot array
* @param _account - account to get snapshot array length of
* @return Snapshot array length
*/
function accountSnapshotLength(address _account) external view returns (uint256) {
return accountSnapshots[_account].length;
}
/**
* @notice Returns length of snapshot array
* @return Snapshot array length
*/
function globalsSnapshotLength() external view returns (uint256) {
return globalsSnapshots.length;
}
/**
* @notice Returns global snapshot at index
* @param _index - account to get latest snapshot of
* @return Globals snapshot
*/
function globalsSnapshot(uint256 _index) external view returns (GlobalsSnapshot memory) {
return globalsSnapshots[_index];
}
/**
* @notice Returns account snapshot at index
* @param _account - account to get snapshot of
* @param _index - index to get snapshot at
* @return Account snapshot
*/
function accountSnapshot(address _account, uint256 _index) external view returns (AccountSnapshot memory) {
return accountSnapshots[_account][_index];
}
/**
* @notice Checks if accoutn and globals snapshots need updating and updates
* @param _account - Account to take snapshot for
*/
function snapshot(address _account) internal {
uint256 _currentInterval = currentInterval();
// If latest global snapshot is less than current interval, push new snapshot
if(latestGlobalsSnapshotInterval() < _currentInterval) {
globalsSnapshots.push(GlobalsSnapshot(
_currentInterval,
totalVotingPower(),
totalStaked
));
}
// If latest account snapshot is less than current interval, push new snapshot
// Skip if account is 0 address
if(_account != address(0) && latestAccountSnapshotInterval(_account) < _currentInterval) {
accountSnapshots[_account].push(AccountSnapshot(
_currentInterval,
votingPower[_account]
));
}
}
/**
* @notice Moves voting power in response to delegation or stake/unstake
* @param _from - account to move voting power fom
* @param _to - account to move voting power to
* @param _amount - amount of voting power to move
*/
function moveVotingPower(address _from, address _to, uint256 _amount) internal {
votingPower[_from] -= _amount;
votingPower[_to] += _amount;
}
/**
* @notice Updates vote delegation
* @param _stakeID - stake to delegate
* @param _to - address to delegate to
*/
function delegate(uint256 _stakeID, address _to) public {
StakeStruct storage _stake = stakes[msg.sender][_stakeID];
require(
_stake.staketime != 0,
"Staking: Stake doesn't exist"
);
require(
_stake.locktime == 0,
"Staking: Stake unlocked"
);
require(
_to != address(0),
"Staking: Can't delegate to 0 address"
);
if (_stake.delegate != _to) {
// Check if snapshot needs to be taken
snapshot(_stake.delegate); // From
snapshot(_to); // To
// Move voting power to delegatee
moveVotingPower(
_stake.delegate,
_to,
_stake.amount
);
// Emit event
emit Delegate(msg.sender, _stake.delegate, _to, _stakeID, _stake.amount);
// Update delegation
_stake.delegate = _to;
}
}
/**
* @notice Delegates voting power of stake back to self
* @param _stakeID - stake to delegate back to self
*/
function undelegate(uint256 _stakeID) external {
delegate(_stakeID, msg.sender);
}
/**
* @notice Gets global state at interval
* @param _interval - interval to get state at
* @return state
*/
function globalsSnapshotAtSearch(uint256 _interval) internal view returns (GlobalsSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
// Index of element
uint256 index;
// High/low for binary serach to find index
// https://en.wikipedia.org/wiki/Binary_search_algorithm
uint256 low = 0;
uint256 high = globalsSnapshots.length;
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds down (it does integer division with truncation).
if (globalsSnapshots[mid].interval > _interval) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. Find the inclusive upper bounds and set to index
if (low > 0 && globalsSnapshots[low - 1].interval == _interval) {
return globalsSnapshots[low - 1];
} else {
index = low;
}
// If index is equal to snapshot array length, then no update was made after the requested
// snapshot interval. This means the latest value is the right one.
if (index == globalsSnapshots.length) {
return GlobalsSnapshot(
_interval,
totalVotingPower(),
totalStaked
);
} else {
return globalsSnapshots[index];
}
}
/**
* @notice Gets global state at interval
* @param _interval - interval to get state at
* @param _hint - off-chain computed index of interval
* @return state
*/
function globalsSnapshotAt(uint256 _interval, uint256 _hint) external view returns (GlobalsSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
// Check if hint is correct, else fall back to binary search
if (
_hint <= globalsSnapshots.length
&& (_hint == 0 || globalsSnapshots[_hint - 1].interval < _interval)
&& (_hint == globalsSnapshots.length || globalsSnapshots[_hint].interval >= _interval)
) {
// The hint is correct
if (_hint < globalsSnapshots.length)
return globalsSnapshots[_hint];
else
return GlobalsSnapshot (_interval, totalVotingPower(), totalStaked);
} else return globalsSnapshotAtSearch (_interval);
}
/**
* @notice Gets account state at interval
* @param _account - account to get state for
* @param _interval - interval to get state at
* @return state
*/
function accountSnapshotAtSearch(address _account, uint256 _interval) internal view returns (AccountSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
// Get account snapshots array
AccountSnapshot[] storage snapshots = accountSnapshots[_account];
// Index of element
uint256 index;
// High/low for binary serach to find index
// https://en.wikipedia.org/wiki/Binary_search_algorithm
uint256 low = 0;
uint256 high = snapshots.length;
while (low < high) {
uint256 mid = Math.average(low, high);
// Note that mid will always be strictly less than high (i.e. it will be a valid array index)
// because Math.average rounds down (it does integer division with truncation).
if (snapshots[mid].interval > _interval) {
high = mid;
} else {
low = mid + 1;
}
}
// At this point `low` is the exclusive upper bound. Find the inclusive upper bounds and set to index
if (low > 0 && snapshots[low - 1].interval == _interval) {
return snapshots[low - 1];
} else {
index = low;
}
// If index is equal to snapshot array length, then no update was made after the requested
// snapshot interval. This means the latest value is the right one.
if (index == snapshots.length) {
return AccountSnapshot(
_interval,
votingPower[_account]
);
} else {
return snapshots[index];
}
}
/**
* @notice Gets account state at interval
* @param _account - account to get state for
* @param _interval - interval to get state at
* @param _hint - off-chain computed index of interval
* @return state
*/
function accountSnapshotAt(address _account, uint256 _interval, uint256 _hint) external view returns (AccountSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
// Get account snapshots array
AccountSnapshot[] storage snapshots = accountSnapshots[_account];
// Check if hint is correct, else fall back to binary search
if (
_hint <= snapshots.length
&& (_hint == 0 || snapshots[_hint - 1].interval < _interval)
&& (_hint == snapshots.length || snapshots[_hint].interval >= _interval)
) {
// The hint is correct
if (_hint < snapshots.length)
return snapshots[_hint];
else
return AccountSnapshot(_interval, votingPower[_account]);
} else return accountSnapshotAtSearch(_account, _interval);
}
/**
* @notice Stake tokens
* @dev This contract should be approve()'d for _amount
* @param _amount - Amount to stake
* @return stake ID
*/
function stake(uint256 _amount) public returns (uint256) {
// Check if amount is not 0
require(_amount > 0, "Staking: Amount not set");
// Check if snapshot needs to be taken
snapshot(msg.sender);
// Get stakeID
uint256 stakeID = stakes[msg.sender].length;
// Set stake values
stakes[msg.sender].push(StakeStruct(
msg.sender,
_amount,
block.timestamp,
0,
0
));
// Increment global staked
totalStaked += _amount;
// Add voting power
moveVotingPower(
address(0),
msg.sender,
_amount
);
// Transfer tokens
stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
// Emit event
emit Stake(msg.sender, stakeID, _amount);
return stakeID;
}
/**
* @notice Unlock stake tokens
* @param _stakeID - Stake to unlock
*/
function unlock(uint256 _stakeID) public {
require(
stakes[msg.sender][_stakeID].staketime != 0,
"Staking: Stake doesn't exist"
);
require(
stakes[msg.sender][_stakeID].locktime == 0,
"Staking: Stake already unlocked"
);
// Check if snapshot needs to be taken
snapshot(msg.sender);
// Set stake locktime
stakes[msg.sender][_stakeID].locktime = block.timestamp + STAKE_LOCKTIME;
// Remove voting power
moveVotingPower(
stakes[msg.sender][_stakeID].delegate,
address(0),
stakes[msg.sender][_stakeID].amount
);
// Emit event
emit Unlock(msg.sender, _stakeID);
}
/**
* @notice Claim stake token
* @param _stakeID - Stake to claim
*/
function claim(uint256 _stakeID) public {
require(
stakes[msg.sender][_stakeID].locktime != 0
&& stakes[msg.sender][_stakeID].locktime < block.timestamp,
"Staking: Stake not unlocked"
);
require(
stakes[msg.sender][_stakeID].claimedTime == 0,
"Staking: Stake already claimed"
);
// Check if snapshot needs to be taken
snapshot(msg.sender);
// Set stake claimed time
stakes[msg.sender][_stakeID].claimedTime = block.timestamp;
// Decrement global staked
totalStaked -= stakes[msg.sender][_stakeID].amount;
// Transfer tokens
stakingToken.safeTransfer(
msg.sender,
stakes[msg.sender][_stakeID].amount
);
// Emit event
emit Claim(msg.sender, _stakeID);
}
}
Address.sol 189 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @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) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
/**
* @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].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain`call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.delegatecall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Math.sol 31 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow, so we distribute
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
IERC20.sol 77 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
SafeERC20.sol 77 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
Read Contract
DEPLOY_TIME 0x90d92e76 → uint256
SNAPSHOT_INTERVAL 0x6d2beef1 → uint256
STAKE_LOCKTIME 0xbed3b60c → uint256
accountSnapshot 0x6ce0a99f → tuple
accountSnapshotAt 0x08d465e6 → tuple
accountSnapshotLength 0x24d63ba3 → uint256
currentInterval 0x363487bc → uint256
globalsSnapshot 0x09fe7dd8 → tuple
globalsSnapshotAt 0x681c637a → tuple
globalsSnapshotLength 0x074bc01d → uint256
intervalAtTime 0x011de7aa → uint256
latestAccountSnapshotInterval 0x76992ec6 → uint256
latestGlobalsSnapshotInterval 0x6d436187 → uint256
stakes 0x584b62a1 → address, uint256, uint256, uint256, uint256
stakesLength 0x49fc9df7 → uint256
stakingToken 0x72f702f3 → address
totalStaked 0x817b1cd2 → uint256
totalVotingPower 0x671b3793 → uint256
votingPower 0xc07473f6 → uint256
Write Contract 5 functions
These functions modify contract state and require a wallet transaction to execute.
claim 0x379607f5
uint256 _stakeID
delegate 0x08bbb824
uint256 _stakeID
address _to
stake 0xa694fc3a
uint256 _amount
returns: uint256
undelegate 0x6c68c0e1
uint256 _stakeID
unlock 0x6198e339
uint256 _stakeID
Recent Transactions
No transactions found for this address