Description:
ERC20 token contract with Pausable capabilities. Standard implementation for fungible tokens on Ethereum.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/* --------------------------------------------------------- */
/* --------------------- Minimal Interfaces ---------------- */
/* --------------------------------------------------------- */
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address a) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address to, uint256 v) external returns (bool);
function approve(address spender, uint256 v) external returns (bool);
function transferFrom(address from, address to, uint256 v) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 v);
event Approval(address indexed owner, address indexed spender, uint256 v);
}
/* --------------------------------------------------------- */
/* --------------------- SafeERC20 Lite -------------------- */
/* --------------------------------------------------------- */
library SafeERC20 {
function safeTransfer(IERC20 t, address to, uint256 v) internal {
require(t.transfer(to, v), "SafeERC20: transfer failed");
}
function safeTransferFrom(IERC20 t, address from, address to, uint256 v) internal {
require(t.transferFrom(from, to, v), "SafeERC20: transferFrom failed");
}
function safeApprove(IERC20 t, address s, uint256 v) internal {
require(t.approve(s, v), "SafeERC20: approve failed");
}
}
/* --------------------------------------------------------- */
/* ------------------------ Context ------------------------ */
/* --------------------------------------------------------- */
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
}
/* --------------------------------------------------------- */
/* --------------------- Ownable2Step Lite ----------------- */
/* --------------------------------------------------------- */
abstract contract Ownable2Step is Context {
address private _owner;
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
error NotOwner();
error NotPendingOwner();
constructor(address initialOwner) {
require(initialOwner != address(0), "Owner=0");
_owner = initialOwner;
emit OwnershipTransferred(address(0), initialOwner);
}
modifier onlyOwner() {
if (_owner != _msgSender()) revert NotOwner();
_;
}
function owner() public view returns (address) { return _owner; }
function pendingOwner() public view returns (address) { return _pendingOwner; }
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "newOwner=0");
_pendingOwner = newOwner;
emit OwnershipTransferStarted(_owner, newOwner);
}
function acceptOwnership() public {
if (_msgSender() != _pendingOwner) revert NotPendingOwner();
address old = _owner;
_owner = _pendingOwner;
_pendingOwner = address(0);
emit OwnershipTransferred(old, _owner);
}
}
/* --------------------------------------------------------- */
/* --------------------- ReentrancyGuard ------------------- */
/* --------------------------------------------------------- */
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, "Reentrancy");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
/* --------------------------------------------------------- */
/* ------------------------- Pausable ---------------------- */
/* --------------------------------------------------------- */
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
modifier whenNotPaused() {
require(!_paused, "Paused");
_;
}
modifier whenPaused() {
require(_paused, "Not paused");
_;
}
function paused() public view returns (bool) { return _paused; }
function _pause() internal whenNotPaused { _paused = true; emit Paused(_msgSender()); }
function _unpause() internal whenPaused { _paused = false; emit Unpaused(_msgSender()); }
}
/* --------------------------------------------------------- */
/* ----------------------- KTTY Staking -------------------- */
/* --------------------------------------------------------- */
contract KTTYStaking is Ownable2Step, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
/* -------------------- Immutable config -------------------- */
IERC20 public immutable stakingToken; // KTTY
IERC20 public immutable rewardsToken; // KTTY (same as staking)
uint256 public immutable rewardDuration; // seconds for a reward cycle
/* -------------------- Reward accounting ------------------- */
uint256 public periodFinish; // timestamp when rewards end
uint256 public rewardRate; // tokens per second
uint256 public lastUpdateTime; // last reward calc timestamp
uint256 public rewardPerTokenStored; // scaled by 1e18
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
/* -------------------- Staking balances -------------------- */
uint256 public totalStaked;
mapping(address => uint256) public balances;
/* ------------------------- Events ------------------------- */
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardNotified(uint256 amount, uint256 newRate, uint256 periodFinish);
event PausedByOwner(address indexed by);
event UnpausedByOwner(address indexed by);
/* ------------------------- Errors ------------------------- */
error ZeroAmount();
error InsufficientBalance();
error RecoverInvalidToken();
error AmountExceedsExcess();
/* ----------------------- Constructor ---------------------- */
/// @param _token KTTY token address (staking & rewards)
/// @param _rewardDuration Reward duration in seconds (e.g., 14 days = 1209600)
constructor(address _token, uint256 _rewardDuration)
Ownable2Step(msg.sender)
{
require(_token != address(0), "token=0");
require(_rewardDuration > 0, "duration=0");
stakingToken = IERC20(_token);
rewardsToken = IERC20(_token);
rewardDuration = _rewardDuration;
}
/* -------------------- View helpers -------------------- */
function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
}
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) return rewardPerTokenStored;
uint256 timeDelta = lastTimeRewardApplicable() - lastUpdateTime;
return rewardPerTokenStored + (timeDelta * rewardRate * 1e18) / totalStaked;
}
function earned(address account) public view returns (uint256) {
uint256 rpt = rewardPerToken();
return ((balances[account] * (rpt - userRewardPerTokenPaid[account])) / 1e18) + rewards[account];
}
/* ---------------- Internal reward update ------------------ */
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
/* ----------------------- User actions --------------------- */
function stake(uint256 amount)
external
nonReentrant
whenNotPaused
updateReward(msg.sender)
{
if (amount == 0) revert ZeroAmount();
totalStaked += amount;
balances[msg.sender] += amount;
SafeERC20.safeTransferFrom(stakingToken, msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount)
public
nonReentrant
updateReward(msg.sender)
{
if (amount == 0) revert ZeroAmount();
if (balances[msg.sender] < amount) revert InsufficientBalance();
totalStaked -= amount;
balances[msg.sender] -= amount;
SafeERC20.safeTransfer(stakingToken, msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function getReward()
public
nonReentrant
updateReward(msg.sender)
{
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
SafeERC20.safeTransfer(rewardsToken, msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(balances[msg.sender]);
getReward();
}
/* ------------------- Owner: reward funding ----------------- */
/// @notice Owner funds rewards. Must approve this contract to pull `amount` first.
/// If called mid-period, leftover is rolled into the new rate.
function notifyRewardAmount(uint256 amount)
external
onlyOwner
updateReward(address(0))
{
if (amount == 0) revert ZeroAmount();
uint256 newRate;
if (block.timestamp >= periodFinish) {
newRate = amount / rewardDuration;
} else {
uint256 remaining = periodFinish - block.timestamp;
uint256 leftover = remaining * rewardRate;
newRate = (amount + leftover) / rewardDuration;
}
// Pull fresh rewards from owner → contract
SafeERC20.safeTransferFrom(rewardsToken, owner(), address(this), amount);
rewardRate = newRate;
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp + rewardDuration;
emit RewardNotified(amount, newRate, periodFinish);
}
/* ------------------------ Admin ops ------------------------ */
function pause() external onlyOwner {
_pause();
emit PausedByOwner(msg.sender);
}
function unpause() external onlyOwner {
_unpause();
emit UnpausedByOwner(msg.sender);
}
/// @notice Emergency: withdraw stake without claiming rewards (when paused).
function emergencyWithdraw() external nonReentrant whenPaused {
uint256 bal = balances[msg.sender];
if (bal == 0) revert InsufficientBalance();
totalStaked -= bal;
balances[msg.sender] = 0;
// DO NOT update/zero rewards → forfeits pending rewards by design
SafeERC20.safeTransfer(stakingToken, msg.sender, bal);
emit Withdrawn(msg.sender, bal);
}
/// @notice Recover non-staking tokens accidentally sent to this contract.
function recoverMistakenToken(address token, uint256 amount) external onlyOwner {
if (token == address(stakingToken)) {
// Only allow withdrawing EXCESS beyond user stakes
uint256 excess = IERC20(token).balanceOf(address(this)) - totalStaked;
if (amount > excess) revert AmountExceedsExcess();
SafeERC20.safeTransfer(IERC20(token), owner(), amount);
} else {
// Allow recovery of any other token
SafeERC20.safeTransfer(IERC20(token), owner(), amount);
}
}
}
Submitted on: 2025-09-25 10:40:21
Comments
Log in to comment.
No comments yet.