文件 1 的 4: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);
}
文件 2 的 4:IStaking.sol
pragma solidity ^0.8.4;
interface IStaking{
function getStakedBalance(address staker) external view returns(uint256);
function getUnlockTime(address staker) external view returns(uint256);
function isShutdown() external view returns(bool);
function voted(address voter, uint256 endBlock) external returns(bool);
function stake(uint256 amount) external;
function withdraw(uint256 amount) external;
function emergencyShutdown(address admin) external;
}
文件 3 的 4:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor () {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
文件 4 的 4:Staking.sol
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./IStaking.sol";
contract Staking is IStaking, ReentrancyGuard {
mapping(address => uint256) private _stakedBalances;
mapping(address => uint256) private _unlockTimes;
address private tokenAddress;
address private daoAddress;
uint256 totalStakedBalance;
bool shutdown=false;
event StakeChanged(address staker, uint256 newStakedBalance);
event UnlockTimeIncreased(address staker, uint256 newUnlockBlock);
event EmergencyShutdown(address calledBy, uint256 shutdownBlock);
modifier onlyDao() {
require(msg.sender == daoAddress, "only dao can call this function");
_;
}
modifier notShutdown() {
require(!shutdown, "cannot be called after shutdown");
_;
}
constructor(address _token, address _dao) {
tokenAddress = _token;
daoAddress = _dao;
}
function getTokenAddress() public view returns (address) {
return tokenAddress;
}
function getDaoAddress() public view returns (address) {
return daoAddress;
}
function getStakedBalance(address staker) external view override returns(uint256) {
return _stakedBalances[staker];
}
function getUnlockTime(address staker) external view override returns(uint256) {
return _unlockTimes[staker];
}
function isShutdown() public view override returns(bool) {
return shutdown;
}
function voted(
address voter,
uint256 endBlock
) external onlyDao notShutdown override returns(bool) {
if(_unlockTimes[voter] < endBlock){
_unlockTimes[voter] = endBlock;
emit UnlockTimeIncreased(voter, endBlock);
}
return true;
}
function stake(uint256 amount) external notShutdown override {
IERC20 tokenContract = IERC20(tokenAddress);
require(tokenContract.balanceOf(msg.sender) >= amount, "Amount higher than user's balance");
require(tokenContract.allowance(msg.sender, address(this)) >= amount, 'Approved allowance too low');
require(
tokenContract.transferFrom(msg.sender, address(this), amount),
"staking tokens failed"
);
totalStakedBalance += amount;
_stakedBalances[msg.sender] += amount;
emit StakeChanged(msg.sender, _stakedBalances[msg.sender]);
}
function withdraw(uint256 amount) external override {
if(!shutdown){
require(_unlockTimes[msg.sender] < block.number, "Tokens not unlocked yet");
}
require(
_stakedBalances[msg.sender] >= amount,
"Insufficient staked balance"
);
require(totalStakedBalance >= amount, "insufficient funds in contract");
totalStakedBalance -= amount;
_stakedBalances[msg.sender] -= amount;
IERC20 tokenContract = IERC20(tokenAddress);
require(tokenContract.transfer(msg.sender, amount), "withdraw failed");
}
function emergencyShutdown(address admin) external onlyDao notShutdown nonReentrant override {
shutdown = true;
emit EmergencyShutdown(admin, block.number);
}
}
{
"compilationTarget": {
"contracts/Staking.sol": "Staking"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_dao","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"calledBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"shutdownBlock","type":"uint256"}],"name":"EmergencyShutdown","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newStakedBalance","type":"uint256"}],"name":"StakeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"newUnlockBlock","type":"uint256"}],"name":"UnlockTimeIncreased","type":"event"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"emergencyShutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDaoAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getStakedBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getUnlockTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isShutdown","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"voter","type":"address"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"name":"voted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]