dione/eth-contracts/contracts/DioneStaking.sol

159 lines
6.6 KiB
Solidity

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./DioneToken.sol";
// DioneStaking is the main contract for Dione oracle network Proof-of-Stake mechanism
//
// Note that it's ownable and the owner has power over changing miner rewards and minimum DIONE stake
// The ownership of the contract would be transfered to 24 hours Timelock
contract DioneStaking is Ownable, ReentrancyGuard {
using SafeMath for uint256;
// MinerInfo contains total DIONEs staked by miner, how much tasks been already computed
// and timestamp of first deposit
struct MinerInfo {
uint256 amount; // How many DIONE tokens was staked by the miner.
uint256 firstStakeBlock; // First block for miner DIONE tokens reward.
uint256 lastRewardBlock; // Last block for miner DIONE tokens reward
}
DioneToken public dione;
// DioneOracle contract address.
address public dioneOracleAddress;
// Dispute contract address.
address public disputeContractAddr;
// Miner rewards in DIONE tokens.
uint256 public minerReward;
// The block number when DIONE mining starts.
uint256 public startBlock;
// Minimum amount of staked DIONE tokens required to start mining
uint256 public minimumStake;
// Total amount of DIONE tokens staked
uint256 private _totalStake;
// Info of each miner that stakes DIONE tokens.
mapping (address => MinerInfo) public minerInfo;
event Stake(address indexed miner, uint256 amount);
event Withdraw(address indexed miner, uint256 amount);
event Mine(address indexed miner, uint256 blockNumber);
event RewardChanged(uint256 oldValue, uint256 newValue);
event MinimumStakeChanged(uint256 oldValue, uint256 newValue);
constructor(
DioneToken _dione,
uint256 _minerReward,
uint256 _startBlock,
uint256 _minimumStake
) {
dione = _dione;
minerReward = _minerReward;
startBlock = _startBlock;
minimumStake = _minimumStake;
}
// Mine new dione oracle task, only can be executed by oracle contract
function mine(address _minerAddr) public nonReentrant {
require(msg.sender == dioneOracleAddress, "not oracle contract");
MinerInfo storage miner = minerInfo[_minerAddr];
dione.mint(_minerAddr, minerReward);
miner.lastRewardBlock = block.number;
emit Mine(_minerAddr, block.number);
}
// Mine new dione oracle task and stake miner reward, only can be executed by oracle contract
function mineAndStake(address _minerAddr) public nonReentrant {
require(msg.sender == dioneOracleAddress, "not oracle contract");
MinerInfo storage miner = minerInfo[_minerAddr];
dione.mint(address(this), minerReward);
_totalStake = _totalStake.add(minerReward);
miner.amount = miner.amount.add(minerReward);
miner.lastRewardBlock = block.number;
emit Mine(_minerAddr, block.number);
}
// Deposit DIONE tokens to mine on dione network
function stake(uint256 _amount) public nonReentrant {
require(_amount > 0, "cannot stake zero");
require(_amount >= minimumStake, "actual stake amount is less than minimum stake amount");
MinerInfo storage miner = minerInfo[msg.sender];
dione.transferFrom(address(msg.sender), address(this), _amount);
_totalStake = _totalStake.add(_amount);
miner.amount = miner.amount.add(_amount);
if (miner.firstStakeBlock == 0) {
miner.firstStakeBlock = block.number > startBlock ? block.number : startBlock;
}
emit Stake(msg.sender, _amount);
}
// Withdraw DIONE tokens from DioneStaking
function withdraw(uint256 _amount) public nonReentrant {
MinerInfo storage miner = minerInfo[msg.sender];
require(miner.amount >= _amount, "withdraw: not enough tokens");
require(_amount > 0, "cannot withdraw zero");
_totalStake = _totalStake.sub(_amount);
miner.amount = miner.amount.sub(_amount);
dione.transfer(address(msg.sender), _amount);
emit Withdraw(msg.sender, _amount);
}
// Returns total amount of DIONE tokens in PoS mining
function totalStake() external view returns (uint256) {
return _totalStake;
}
function minerStake(address _minerAddr) external view returns (uint256) {
return minerInfo[_minerAddr].amount;
}
// Update miner reward in DIONE tokens, only can be executed by owner of the contract
function setMinerReward(uint256 _minerReward) public onlyOwner {
require(_minerReward > 0, "reward must not be zero");
uint256 oldRewardValue = minerReward;
minerReward = _minerReward;
emit RewardChanged(oldRewardValue, _minerReward);
}
function isMiner(address _minerAddr) public view returns (bool) {
return minerInfo[_minerAddr].amount >= minimumStake;
}
// Update minimum stake in DIONE tokens for miners, only can be executed by owner of the contract
function setMinimumStake(uint256 _minimumStake) public onlyOwner {
require(_minimumStake > 0, "minimum stake must not be zero");
uint256 oldValue = minimumStake;
minimumStake = _minimumStake;
emit MinimumStakeChanged(oldValue, _minimumStake);
}
function setOracleContractAddress(address _addr) public onlyOwner {
require(_addr != address(0), "address must not be zero");
dioneOracleAddress = _addr;
}
function setDisputeContractAddress(address _addr) public onlyOwner {
require(_addr != address(0), "address must not be zero");
disputeContractAddr = _addr;
}
function slashMiner(address miner, address[] memory receipentMiners) public {
require(msg.sender == disputeContractAddr, "caller is not the dispute contract");
require(miner != address(0), "slashing address must not be zero");
require(isMiner(miner), "slashing address isn't dione miner");
uint256 share = minerInfo[miner].amount.div(receipentMiners.length);
for (uint8 i = 0; i < receipentMiners.length; i++) {
require(receipentMiners[i] != miner, "receipent address must not be slashing address");
require(isMiner(receipentMiners[i]), "receipent address isn't dione miner");
minerInfo[miner].amount = minerInfo[miner].amount.sub(share);
minerInfo[receipentMiners[i]].amount = minerInfo[receipentMiners[i]].amount.add(share);
}
}
}