文件 1 的 5:Address.sol
pragma solidity ^0.8.0;
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
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");
}
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");
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return _verifyCallResult(success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(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 {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 2 的 5:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 3 的 5:Math.sol
pragma solidity ^0.8.0;
library Math {
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
}
}
文件 4 的 5:SafeERC20.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
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));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
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));
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 5 的 5:Staking.sol
pragma solidity ^0.8.0;
pragma abicoder v2;
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";
contract Staking {
using SafeERC20 for IERC20;
uint256 public constant STAKE_LOCKTIME = 30 days;
uint256 public constant SNAPSHOT_INTERVAL = 1 days;
IERC20 public stakingToken;
uint256 public immutable DEPLOY_TIME = block.timestamp;
event Stake(address indexed account, uint256 indexed stakeID, uint256 amount);
event Unlock(address indexed account, uint256 indexed stakeID);
event Claim(address indexed account, uint256 indexed stakeID);
event Delegate(address indexed owner, address indexed _from, address indexed to, uint256 stakeID, uint256 amount);
uint256 public totalStaked = 0;
struct GlobalsSnapshot {
uint256 interval;
uint256 totalVotingPower;
uint256 totalStaked;
}
GlobalsSnapshot[] private globalsSnapshots;
struct StakeStruct {
address delegate;
uint256 amount;
uint256 staketime;
uint256 locktime;
uint256 claimedTime;
}
mapping(address => StakeStruct[]) public stakes;
mapping(address => uint256) public votingPower;
struct AccountSnapshot {
uint256 interval;
uint256 votingPower;
}
mapping(address => AccountSnapshot[]) private accountSnapshots;
constructor(IERC20 _stakingToken) {
stakingToken = _stakingToken;
votingPower[address(0)] = type(uint256).max;
}
function totalVotingPower() public view returns (uint256) {
return ~votingPower[address(0)];
}
function stakesLength(address _account) external view returns (uint256) {
return stakes[_account].length;
}
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;
}
function currentInterval() public view returns (uint256) {
return intervalAtTime(block.timestamp);
}
function latestGlobalsSnapshotInterval() public view returns (uint256) {
if (globalsSnapshots.length > 0) {
return globalsSnapshots[globalsSnapshots.length - 1].interval;
} else {
return 0;
}
}
function latestAccountSnapshotInterval(address _account) public view returns (uint256) {
if (accountSnapshots[_account].length > 0) {
return accountSnapshots[_account][accountSnapshots[_account].length - 1].interval;
} else {
return 0;
}
}
function accountSnapshotLength(address _account) external view returns (uint256) {
return accountSnapshots[_account].length;
}
function globalsSnapshotLength() external view returns (uint256) {
return globalsSnapshots.length;
}
function globalsSnapshot(uint256 _index) external view returns (GlobalsSnapshot memory) {
return globalsSnapshots[_index];
}
function accountSnapshot(address _account, uint256 _index) external view returns (AccountSnapshot memory) {
return accountSnapshots[_account][_index];
}
function snapshot(address _account) internal {
uint256 _currentInterval = currentInterval();
if(latestGlobalsSnapshotInterval() < _currentInterval) {
globalsSnapshots.push(GlobalsSnapshot(
_currentInterval,
totalVotingPower(),
totalStaked
));
}
if(_account != address(0) && latestAccountSnapshotInterval(_account) < _currentInterval) {
accountSnapshots[_account].push(AccountSnapshot(
_currentInterval,
votingPower[_account]
));
}
}
function moveVotingPower(address _from, address _to, uint256 _amount) internal {
votingPower[_from] -= _amount;
votingPower[_to] += _amount;
}
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) {
snapshot(_stake.delegate);
snapshot(_to);
moveVotingPower(
_stake.delegate,
_to,
_stake.amount
);
emit Delegate(msg.sender, _stake.delegate, _to, _stakeID, _stake.amount);
_stake.delegate = _to;
}
}
function undelegate(uint256 _stakeID) external {
delegate(_stakeID, msg.sender);
}
function globalsSnapshotAtSearch(uint256 _interval) internal view returns (GlobalsSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
uint256 index;
uint256 low = 0;
uint256 high = globalsSnapshots.length;
while (low < high) {
uint256 mid = Math.average(low, high);
if (globalsSnapshots[mid].interval > _interval) {
high = mid;
} else {
low = mid + 1;
}
}
if (low > 0 && globalsSnapshots[low - 1].interval == _interval) {
return globalsSnapshots[low - 1];
} else {
index = low;
}
if (index == globalsSnapshots.length) {
return GlobalsSnapshot(
_interval,
totalVotingPower(),
totalStaked
);
} else {
return globalsSnapshots[index];
}
}
function globalsSnapshotAt(uint256 _interval, uint256 _hint) external view returns (GlobalsSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
if (
_hint <= globalsSnapshots.length
&& (_hint == 0 || globalsSnapshots[_hint - 1].interval < _interval)
&& (_hint == globalsSnapshots.length || globalsSnapshots[_hint].interval >= _interval)
) {
if (_hint < globalsSnapshots.length)
return globalsSnapshots[_hint];
else
return GlobalsSnapshot (_interval, totalVotingPower(), totalStaked);
} else return globalsSnapshotAtSearch (_interval);
}
function accountSnapshotAtSearch(address _account, uint256 _interval) internal view returns (AccountSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
AccountSnapshot[] storage snapshots = accountSnapshots[_account];
uint256 index;
uint256 low = 0;
uint256 high = snapshots.length;
while (low < high) {
uint256 mid = Math.average(low, high);
if (snapshots[mid].interval > _interval) {
high = mid;
} else {
low = mid + 1;
}
}
if (low > 0 && snapshots[low - 1].interval == _interval) {
return snapshots[low - 1];
} else {
index = low;
}
if (index == snapshots.length) {
return AccountSnapshot(
_interval,
votingPower[_account]
);
} else {
return snapshots[index];
}
}
function accountSnapshotAt(address _account, uint256 _interval, uint256 _hint) external view returns (AccountSnapshot memory) {
require(_interval <= currentInterval(), "Staking: Interval out of bounds");
AccountSnapshot[] storage snapshots = accountSnapshots[_account];
if (
_hint <= snapshots.length
&& (_hint == 0 || snapshots[_hint - 1].interval < _interval)
&& (_hint == snapshots.length || snapshots[_hint].interval >= _interval)
) {
if (_hint < snapshots.length)
return snapshots[_hint];
else
return AccountSnapshot(_interval, votingPower[_account]);
} else return accountSnapshotAtSearch(_account, _interval);
}
function stake(uint256 _amount) public returns (uint256) {
require(_amount > 0, "Staking: Amount not set");
snapshot(msg.sender);
uint256 stakeID = stakes[msg.sender].length;
stakes[msg.sender].push(StakeStruct(
msg.sender,
_amount,
block.timestamp,
0,
0
));
totalStaked += _amount;
moveVotingPower(
address(0),
msg.sender,
_amount
);
stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
emit Stake(msg.sender, stakeID, _amount);
return stakeID;
}
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"
);
snapshot(msg.sender);
stakes[msg.sender][_stakeID].locktime = block.timestamp + STAKE_LOCKTIME;
moveVotingPower(
stakes[msg.sender][_stakeID].delegate,
address(0),
stakes[msg.sender][_stakeID].amount
);
emit Unlock(msg.sender, _stakeID);
}
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"
);
snapshot(msg.sender);
stakes[msg.sender][_stakeID].claimedTime = block.timestamp;
totalStaked -= stakes[msg.sender][_stakeID].amount;
stakingToken.safeTransfer(
msg.sender,
stakes[msg.sender][_stakeID].amount
);
emit Claim(msg.sender, _stakeID);
}
}
{
"compilationTarget": {
"contracts/governance/Staking.sol": "Staking"
},
"evmVersion": "berlin",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1600
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IERC20","name":"_stakingToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakeID","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"stakeID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Delegate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakeID","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Stake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"stakeID","type":"uint256"}],"name":"Unlock","type":"event"},{"inputs":[],"name":"DEPLOY_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNAPSHOT_INTERVAL","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_LOCKTIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"accountSnapshot","outputs":[{"components":[{"internalType":"uint256","name":"interval","type":"uint256"},{"internalType":"uint256","name":"votingPower","type":"uint256"}],"internalType":"struct Staking.AccountSnapshot","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"},{"internalType":"uint256","name":"_interval","type":"uint256"},{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"accountSnapshotAt","outputs":[{"components":[{"internalType":"uint256","name":"interval","type":"uint256"},{"internalType":"uint256","name":"votingPower","type":"uint256"}],"internalType":"struct Staking.AccountSnapshot","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"accountSnapshotLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeID","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeID","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"globalsSnapshot","outputs":[{"components":[{"internalType":"uint256","name":"interval","type":"uint256"},{"internalType":"uint256","name":"totalVotingPower","type":"uint256"},{"internalType":"uint256","name":"totalStaked","type":"uint256"}],"internalType":"struct Staking.GlobalsSnapshot","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_interval","type":"uint256"},{"internalType":"uint256","name":"_hint","type":"uint256"}],"name":"globalsSnapshotAt","outputs":[{"components":[{"internalType":"uint256","name":"interval","type":"uint256"},{"internalType":"uint256","name":"totalVotingPower","type":"uint256"},{"internalType":"uint256","name":"totalStaked","type":"uint256"}],"internalType":"struct Staking.GlobalsSnapshot","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalsSnapshotLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_time","type":"uint256"}],"name":"intervalAtTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"latestAccountSnapshotInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestGlobalsSnapshotInterval","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakes","outputs":[{"internalType":"address","name":"delegate","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"staketime","type":"uint256"},{"internalType":"uint256","name":"locktime","type":"uint256"},{"internalType":"uint256","name":"claimedTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"stakesLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalVotingPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeID","type":"uint256"}],"name":"undelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeID","type":"uint256"}],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"votingPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]