编译器
0.8.20+commit.a1b79de6
文件 1 的 5:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 2 的 5:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 3 的 5:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 4 的 5:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
文件 5 的 5:Zk0Staking.sol
pragma solidity 0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface IZk0mni is IERC20 {
function stakingMint(address user, uint amount) external;
function stakingBurn(address user, uint amount) external;
}
contract Zk0Staking is ReentrancyGuard, Ownable {
IZk0mni public zk0mni;
address private feesReceiver;
bool public depositAllowed = false;
bool public claimAllowed = false;
bool public rewardsLive = true;
uint public lastRewardTimestamp;
uint public totalStaked;
uint public totalRewarded;
uint public forfeitCost = 20;
uint private constant PRECISION_DECIMALS = 1e18;
uint private constant MIN_LOCK = 1 days;
uint private constant MAX_LOCK = 7 days;
address[] public stakers;
mapping(address => uint) public stakersIndex;
mapping(address => StakingData) public stakes;
struct StakingData {
bool isStaking;
uint lockEndTimestamp;
uint lastClaimTimestamp;
uint totalAmountStaked;
uint rewardsPerDay;
uint lockPeriod;
}
constructor(address zk0mniAddress) Ownable(msg.sender) {
zk0mni = IZk0mni(zk0mniAddress);
feesReceiver = msg.sender;
}
function setFeesReceiver(address newFeesReceiver) external onlyOwner {
feesReceiver = newFeesReceiver;
}
function setForfeitCost(uint newCost) external onlyOwner {
require(newCost <= 100);
forfeitCost = newCost;
}
function setRewardsLive(bool areRewardsLive) external onlyOwner {
rewardsLive = areRewardsLive;
lastRewardTimestamp = block.timestamp;
}
function setStakingStatus(
bool isDepositAllowed,
bool isClaimAllowed
) external onlyOwner {
depositAllowed = isDepositAllowed;
claimAllowed = isClaimAllowed;
}
function deposit(uint tokenAmount, uint lockDelay) external nonReentrant {
require(depositAllowed && rewardsLive, "Not allowed to enter staking.");
address user = msg.sender;
uint amountToAdd;
zk0mni.stakingBurn(user, tokenAmount);
if (stakes[user].isStaking) {
require(
lockDelay >= stakes[user].lockPeriod && lockDelay <= MAX_LOCK,
"Invalid locking delay."
);
uint claimableRewards = claimAllowed
? getClaimableRewards(user)
: 0;
amountToAdd = claimableRewards + tokenAmount;
totalRewarded += claimableRewards;
}
else {
require(
lockDelay >= MIN_LOCK && lockDelay <= MAX_LOCK,
"Invalid locking delay."
);
stakersIndex[user] = _addStakerArray(user);
amountToAdd = tokenAmount;
}
stakes[user] = StakingData(
true,
block.timestamp + lockDelay,
block.timestamp,
stakes[user].totalAmountStaked + amountToAdd,
getRewardsPerDay(lockDelay),
lockDelay
);
totalStaked += tokenAmount;
}
function withdraw() external nonReentrant {
address user = msg.sender;
require(stakes[user].isStaking, "User is not staking.");
uint amountStaked = stakes[user].totalAmountStaked;
if (claimAllowed) _claim(user);
if (stakes[user].lockEndTimestamp <= block.timestamp) {
zk0mni.stakingMint(user, amountStaked);
} else {
uint fees = (forfeitCost * amountStaked) / 100;
zk0mni.stakingMint(user, amountStaked - fees);
zk0mni.stakingMint(feesReceiver, fees);
}
_removeStakerArray(user);
delete stakes[user];
totalStaked -= amountStaked;
}
function claim() external nonReentrant {
require(claimAllowed, "Not allowed to claim.");
address user = msg.sender;
require(stakes[user].isStaking, "User is not staking.");
_claim(user);
}
function _claim(address user) internal {
uint claimableRewards = getClaimableRewards(user);
stakes[user].lastClaimTimestamp = rewardsLive
? block.timestamp
: lastRewardTimestamp;
zk0mni.stakingMint(user, claimableRewards);
totalRewarded += claimableRewards;
}
function _addStakerArray(address user) private returns (uint index) {
index = stakers.length;
stakers.push(user);
}
function _removeStakerArray(address user) private {
uint index = stakersIndex[user];
stakers[index] = stakers[stakers.length - 1];
stakers.pop();
delete stakersIndex[user];
}
function getRewardsPerDay(uint lockDelay) public pure returns (uint) {
return
((lockDelay * PRECISION_DECIMALS) / (1 days) + PRECISION_DECIMALS) /
100;
}
function getElapsedDelay(address user) public view returns (uint) {
return
(rewardsLive ? block.timestamp : lastRewardTimestamp) -
stakes[user].lastClaimTimestamp;
}
function getClaimableRewards(
address user
) public view returns (uint rewards) {
uint actualizedRate = (getElapsedDelay(user) *
stakes[user].rewardsPerDay) / (1 days);
rewards =
(actualizedRate * stakes[user].totalAmountStaked) /
PRECISION_DECIMALS;
}
function getTotalClaimableRewards(
uint start,
uint end
) external view returns (uint total) {
if (end == 0) end = stakers.length;
for (uint i = start; i < end; i++) {
total += getClaimableRewards(stakers[i]);
}
}
function getStakers(
uint start,
uint end
) external view returns (address[] memory) {
if (end == 0) end = stakers.length;
address[] memory returnedStakers = new address[](end - start);
for (uint i = start; i < end; i++) {
returnedStakers[i] = stakers[i];
}
return returnedStakers;
}
}
{
"compilationTarget": {
"contracts/Zk0Staking.sol": "Zk0Staking"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"zk0mniAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"lockDelay","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositAllowed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forfeitCost","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getClaimableRewards","outputs":[{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getElapsedDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"lockDelay","type":"uint256"}],"name":"getRewardsPerDay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getStakers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getTotalClaimableRewards","outputs":[{"internalType":"uint256","name":"total","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastRewardTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardsLive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newFeesReceiver","type":"address"}],"name":"setFeesReceiver","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newCost","type":"uint256"}],"name":"setForfeitCost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"areRewardsLive","type":"bool"}],"name":"setRewardsLive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"isDepositAllowed","type":"bool"},{"internalType":"bool","name":"isClaimAllowed","type":"bool"}],"name":"setStakingStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakersIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakes","outputs":[{"internalType":"bool","name":"isStaking","type":"bool"},{"internalType":"uint256","name":"lockEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"lastClaimTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalAmountStaked","type":"uint256"},{"internalType":"uint256","name":"rewardsPerDay","type":"uint256"},{"internalType":"uint256","name":"lockPeriod","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRewarded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"zk0mni","outputs":[{"internalType":"contract IZk0mni","name":"","type":"address"}],"stateMutability":"view","type":"function"}]