Description:
Decentralized Finance (DeFi) protocol contract providing Staking functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
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 transferFrom(address sender, 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);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_owner = _msgSender();
emit OwnershipTransferred(address(0), _owner);
}
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 {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
}
/**
* @title Staking
* @notice Receives tokens from the Presale contract (onlyPresale) and keeps track of stakes & 300% APY rewards.
*
* Reward formula (linear accrual):
* reward = amount * APY_NUM * duration / (APY_DEN * YEAR)
* where APY_NUM = 300, APY_DEN = 100, YEAR = 365 days
*/
contract Staking is Ownable {
using SafeMath for uint256;
IERC20 public token;
address public presale; // only this address can call stakeFor
uint256 public constant APY_NUM = 300; // 300%
uint256 public constant APY_DEN = 100;
uint256 public constant YEAR = 365 days;
struct StakeInfo {
uint256 amount; // principal staked
uint256 rewards; // rewards accumulated up to lastUpdate
uint256 lastUpdated; // timestamp of last rewards update
}
mapping(address => StakeInfo) public stakes;
uint256 public totalStaked;
event StakedFor(address indexed user, uint256 amount);
event RewardsClaimed(address indexed user, uint256 reward);
event Unstaked(address indexed user, uint256 principal, uint256 reward);
event PresaleSet(address indexed presale);
event Funded(uint256 amount);
event EmergencyWithdrawal(address indexed to, uint256 amount);
modifier onlyPresale() {
require(msg.sender == presale, "Staking: caller is not presale");
_;
}
constructor(address _token) {
require(_token != address(0), "Invalid token");
token = IERC20(_token);
}
function setPresale(address _presale) external onlyOwner {
require(_presale != address(0), "Invalid presale");
presale = _presale;
emit PresaleSet(_presale);
}
/// @notice Called by presale AFTER presale has transferred tokens to this contract.
function stakeFor(address user, uint256 amount) external onlyPresale {
require(user != address(0), "Invalid user");
require(amount > 0, "Amount zero");
_updateRewards(user);
stakes[user].amount = stakes[user].amount.add(amount);
// lastUpdated already set in _updateRewards to block timestamp
totalStaked = totalStaked.add(amount);
emit StakedFor(user, amount);
}
/// @notice Claim only rewards (principal stays staked)
function claimRewards() external {
_updateRewards(msg.sender);
uint256 reward = stakes[msg.sender].rewards;
require(reward > 0, "No rewards");
stakes[msg.sender].rewards = 0;
require(token.transfer(msg.sender, reward), "Token transfer failed");
emit RewardsClaimed(msg.sender, reward);
}
/// @notice Unstake principal (optionally withdraw partial). Rewards are also paid out up to now.
function unstake(uint256 amount) external {
require(amount > 0, "Amount zero");
require(stakes[msg.sender].amount >= amount, "Insufficient staked");
_updateRewards(msg.sender);
// collect pending reward
uint256 reward = stakes[msg.sender].rewards;
stakes[msg.sender].rewards = 0;
// reduce principal
stakes[msg.sender].amount = stakes[msg.sender].amount.sub(amount);
totalStaked = totalStaked.sub(amount);
// transfer principal + reward
uint256 payout = amount.add(reward);
require(token.transfer(msg.sender, payout), "Token transfer failed");
emit Unstaked(msg.sender, amount, reward);
}
/// @notice View: compute pending rewards since last update (does not modify state)
function pendingRewards(address user) public view returns (uint256) {
StakeInfo memory s = stakes[user];
if (s.amount == 0) return s.rewards;
uint256 since = block.timestamp.sub(s.lastUpdated);
uint256 accrued = s.amount
.mul(APY_NUM)
.mul(since)
.div(APY_DEN)
.div(YEAR);
return s.rewards.add(accrued);
}
/// @dev update per-user rewards to now
function _updateRewards(address user) internal {
if (stakes[user].lastUpdated == 0) {
// first time being touched: set lastUpdated to now
stakes[user].lastUpdated = block.timestamp;
return;
}
uint256 since = block.timestamp.sub(stakes[user].lastUpdated);
if (since == 0) return;
if (stakes[user].amount > 0) {
uint256 accrued = stakes[user].amount
.mul(APY_NUM)
.mul(since)
.div(APY_DEN)
.div(YEAR);
stakes[user].rewards = stakes[user].rewards.add(accrued);
}
stakes[user].lastUpdated = block.timestamp;
}
/// @notice Owner can fund reward pool by transferring tokens to this contract and calling fund()
function fund(uint256 amount) external onlyOwner {
require(amount > 0, "Amount zero");
require(token.transferFrom(msg.sender, address(this), amount), "TransferFrom failed");
emit Funded(amount);
}
/// @notice Emergency: owner can withdraw any tokens (useful for admins)
function emergencyWithdraw(address to, uint256 amount) external onlyOwner {
require(to != address(0), "Zero address");
require(token.transfer(to, amount), "Transfer failed");
emit EmergencyWithdrawal(to, amount);
}
}
Submitted on: 2025-11-04 14:08:17
Comments
Log in to comment.
No comments yet.