编译器
0.8.12+commit.f00d7308
文件 1 的 8:Context.sol
pragma solidity ^0.8.0;
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 的 8:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 3 的 8:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
}
文件 4 的 8:IERC721Enumerable.sol
pragma solidity ^0.8.0;
import "../IERC721.sol";
interface IERC721Enumerable is IERC721 {
function totalSupply() external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
function tokenByIndex(uint256 index) external view returns (uint256);
}
文件 5 的 8:IERC721Receiver.sol
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
文件 6 的 8:KCGStaking.sol
pragma solidity 0.8.12;
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract KCGStaking is Ownable, ReentrancyGuard {
uint256 public constant DAY = 24 * 60 * 60;
uint256 public constant FOURTY_FIVE_DAYS = 45 * DAY;
uint256 public constant NINETY_DAYS = 90 * DAY;
uint256 public constant ONE_HUNDREDS_EIGHTY_DAYS = 180 * DAY;
address public KCGAddress = 0xA302F0d51A365B18e86c291056dC265a73F19419;
bool public emergencyUnstakePaused = true;
struct stakeRecord {
address tokenOwner;
uint256 tokenId;
uint256 endingTimestamp;
}
mapping(uint256 => stakeRecord) public stakingRecords;
mapping(address => uint256) public numOfTokenStaked;
event Staked(address owner, uint256 amount, uint256 timeframe);
event Unstaked(address owner, uint256 amount);
event EmergencyUnstake(address indexed user, uint256 tokenId);
constructor() {}
modifier checkArgsLength(
uint256[] calldata tokenIds,
uint256[] calldata timeframe
) {
require(
tokenIds.length == timeframe.length,
"Token IDs and timeframes must have the same length."
);
_;
}
modifier checkStakingTimeframe(uint256[] calldata timeframe) {
for (uint256 i = 0; i < timeframe.length; i++) {
uint256 period = timeframe[i];
require(
period == FOURTY_FIVE_DAYS ||
period == NINETY_DAYS ||
period == ONE_HUNDREDS_EIGHTY_DAYS,
"Invalid staking timeframes."
);
}
_;
}
function batchStake(
uint256[] calldata tokenIds,
uint256[] calldata timeframe
)
external
checkStakingTimeframe(timeframe)
checkArgsLength(tokenIds, timeframe)
{
for (uint256 i = 0; i < tokenIds.length; i++) {
_stake(msg.sender, tokenIds[i], timeframe[i]);
}
}
function _stake(
address _user,
uint256 _tokenId,
uint256 _timeframe
) internal {
require(
IERC721Enumerable(KCGAddress).ownerOf(_tokenId) == msg.sender,
"You must own the NFT."
);
uint256 endingTimestamp = block.timestamp + _timeframe;
stakingRecords[_tokenId] = stakeRecord(
_user,
_tokenId,
endingTimestamp
);
numOfTokenStaked[_user] = numOfTokenStaked[_user] + 1;
IERC721Enumerable(KCGAddress).safeTransferFrom(
_user,
address(this),
_tokenId
);
emit Staked(_user, _tokenId, _timeframe);
}
function batchRestake(
uint256[] calldata tokenIds,
uint256[] calldata timeframe
)
external
checkStakingTimeframe(timeframe)
checkArgsLength(tokenIds, timeframe)
{
for (uint256 i = 0; i < tokenIds.length; i++) {
_restake(msg.sender, tokenIds[i], timeframe[i]);
}
}
function _restake(
address _user,
uint256 _tokenId,
uint256 _timeframe
) internal {
require(
block.timestamp >= stakingRecords[_tokenId].endingTimestamp,
"NFT is locked."
);
require(
stakingRecords[_tokenId].tokenOwner == msg.sender,
"Token does not belong to you."
);
uint256 endingTimestamp = block.timestamp + _timeframe;
stakingRecords[_tokenId].endingTimestamp = endingTimestamp;
emit Staked(_user, _tokenId, _timeframe);
}
function batchUnstake(uint256[] calldata tokenIds) external nonReentrant {
for (uint256 i = 0; i < tokenIds.length; i++) {
_unstake(msg.sender, tokenIds[i]);
}
}
function _unstake(address _user, uint256 _tokenId) internal {
require(
block.timestamp >= stakingRecords[_tokenId].endingTimestamp,
"NFT is locked."
);
require(
stakingRecords[_tokenId].tokenOwner == msg.sender,
"Token does not belong to you."
);
delete stakingRecords[_tokenId];
numOfTokenStaked[_user]--;
IERC721Enumerable(KCGAddress).safeTransferFrom(
address(this),
_user,
_tokenId
);
emit Unstaked(_user, _tokenId);
}
function getStakingRecords(address user)
public
view
returns (uint256[] memory, uint256[] memory)
{
uint256[] memory tokenIds = new uint256[](numOfTokenStaked[user]);
uint256[] memory expiries = new uint256[](numOfTokenStaked[user]);
uint256 counter = 0;
for (
uint256 i = 0;
i < IERC721Enumerable(KCGAddress).totalSupply();
i++
) {
if (stakingRecords[i].tokenOwner == user) {
tokenIds[counter] = stakingRecords[i].tokenId;
expiries[counter] = stakingRecords[i].endingTimestamp;
counter++;
}
}
return (tokenIds, expiries);
}
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
function setKCGNFTContract(address operator) public onlyOwner {
KCGAddress = operator;
}
function setEmergencyUnstakePaused(bool paused) public onlyOwner {
emergencyUnstakePaused = paused;
}
function emergencyUnstake(uint256 tokenId) external nonReentrant {
require(!emergencyUnstakePaused, "No emergency unstake.");
require(
stakingRecords[tokenId].tokenOwner == msg.sender,
"Token does not belong to you."
);
delete stakingRecords[tokenId];
numOfTokenStaked[msg.sender]--;
IERC721Enumerable(KCGAddress).safeTransferFrom(
address(this),
msg.sender,
tokenId
);
emit EmergencyUnstake(msg.sender, tokenId);
}
function emergencyUnstakeByOwner(uint256[] calldata tokenIds)
external
onlyOwner
nonReentrant
{
require(!emergencyUnstakePaused, "No emergency unstake.");
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
address user = stakingRecords[tokenId].tokenOwner;
require(user != address(0x0), "Need owner exists.");
delete stakingRecords[tokenId];
numOfTokenStaked[user]--;
IERC721Enumerable(KCGAddress).safeTransferFrom(
address(this),
user,
tokenId
);
emit EmergencyUnstake(user, tokenId);
}
}
}
文件 7 的 8:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 8 的 8: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;
}
}
{
"compilationTarget": {
"contracts/KCGStaking.sol": "KCGStaking"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"EmergencyUnstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeframe","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"DAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FOURTY_FIVE_DAYS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"KCGAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NINETY_DAYS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_HUNDREDS_EIGHTY_DAYS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"timeframe","type":"uint256[]"}],"name":"batchRestake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"timeframe","type":"uint256[]"}],"name":"batchStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"batchUnstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"emergencyUnstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"emergencyUnstakeByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyUnstakePaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getStakingRecords","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"numOfTokenStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"paused","type":"bool"}],"name":"setEmergencyUnstakePaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"setKCGNFTContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakingRecords","outputs":[{"internalType":"address","name":"tokenOwner","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"endingTimestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]