Contract Name:
StakingWithOptionalReferral
Contract Source Code:
File 1 of 1 : StakingWithOptionalReferral
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Address library from OpenZeppelin
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: weiValue}(data);
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SafeERC20 from OpenZeppelin
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// Context from OpenZeppelin
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// ReentrancyGuard from OpenZeppelin
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;
}
}
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 allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, 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);
}
/// @title StakingWithOptionalReferral - A staking contract for SANIC tokens with optional referrals and robust security.
contract StakingWithOptionalReferral is ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public sanicToken; // Token to be staked
address public feeRecipient; // Address to receive fees
address public owner; // Contract owner
uint256 public dailyRewardRate = 8; // 8% daily rewards
uint256 public depositFee = 5; // 5% deposit fee
uint256 public referralFee = 12; // 12% referral bonus
uint256 public claimCooldown = 1 minutes; // Cooldown period for claiming rewards
uint256 public penaltyCooldown = 24 hours; // Time window for penalty enforcement
uint256 public penaltyRate = 50; // 50% penalty on rewards for early claims
struct User {
uint256 depositedAmount;
uint256 lastClaimTime;
uint256 lastWithdrawalTime;
}
mapping(address => User) public users;
mapping(address => address) public referrers;
mapping(address => bool) public hasBeenReferred;
uint256 public totalDeposits;
event Deposit(address indexed user, uint256 amount, uint256 netAmount, address indexed referrer);
event ClaimRewards(address indexed user, uint256 reward, uint256 penalty);
event Compound(address indexed user, uint256 compoundedAmount);
event ReferralBonusPaid(address indexed referrer, uint256 bonus);
event TokenUpdated(address indexed oldToken, address indexed newToken);
event FeeRecipientUpdated(address indexed oldRecipient, address indexed newRecipient);
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
constructor(address _sanicToken, address _feeRecipient) {
require(_sanicToken != address(0), "Invalid token address");
require(_feeRecipient != address(0), "Invalid fee recipient address");
sanicToken = IERC20(_sanicToken);
feeRecipient = _feeRecipient;
owner = msg.sender;
}
function deposit(uint256 _amount, address _referrer) external nonReentrant {
require(_amount > 0, "Deposit amount must be greater than zero");
uint256 allowance = sanicToken.allowance(msg.sender, address(this));
require(allowance >= _amount, "Token allowance too low");
sanicToken.safeTransferFrom(msg.sender, address(this), _amount);
uint256 fee = (_amount * depositFee) / 100;
uint256 netAmount = _amount - fee;
sanicToken.safeTransfer(feeRecipient, fee);
if (_referrer != address(0) && _referrer != msg.sender) {
require(!hasBeenReferred[msg.sender], "User has already been referred");
uint256 referralBonus = (_amount * referralFee) / 100;
sanicToken.safeTransfer(_referrer, referralBonus);
referrers[msg.sender] = _referrer;
hasBeenReferred[msg.sender] = true;
emit ReferralBonusPaid(_referrer, referralBonus);
}
User storage user = users[msg.sender];
user.depositedAmount += netAmount;
user.lastClaimTime = block.timestamp;
user.lastWithdrawalTime = 0; // Reset withdrawal time on deposit
totalDeposits += netAmount;
emit Deposit(msg.sender, _amount, netAmount, _referrer);
}
function claimRewards() external nonReentrant {
User storage user = users[msg.sender];
require(user.depositedAmount > 0, "No deposits found");
require(block.timestamp >= user.lastClaimTime + claimCooldown, "Claim cooldown active");
uint256 rewards = calculateRewards(msg.sender);
require(rewards > 0, "No rewards available");
uint256 penalty = 0;
if (block.timestamp < user.lastWithdrawalTime + penaltyCooldown) {
penalty = (rewards * penaltyRate) / 100;
}
uint256 netRewards = rewards - penalty;
user.lastClaimTime = block.timestamp;
user.lastWithdrawalTime = block.timestamp;
user.depositedAmount -= netRewards; // Deduct claimed rewards from deposit
sanicToken.safeTransfer(msg.sender, netRewards);
if (penalty > 0) {
sanicToken.safeTransfer(feeRecipient, penalty);
}
emit ClaimRewards(msg.sender, netRewards, penalty);
}
function calculateRewards(address _user) public view returns (uint256) {
User memory user = users[_user];
uint256 elapsedTime = block.timestamp - user.lastClaimTime;
uint256 dailyRewards = (user.depositedAmount * dailyRewardRate) / 100;
return (dailyRewards * elapsedTime) / 1 days;
}
function compound() external nonReentrant {
User storage user = users[msg.sender];
require(user.depositedAmount > 0, "No deposits found");
require(block.timestamp >= user.lastClaimTime + claimCooldown, "Claim cooldown active");
uint256 rewards = calculateRewards(msg.sender);
require(rewards > 0, "No rewards available");
user.depositedAmount += rewards; // Add rewards to deposit
user.lastClaimTime = block.timestamp;
emit Compound(msg.sender, rewards);
}
function getTotalDeposited() external view returns (uint256) {
return totalDeposits;
}
function updateToken(address _newToken) external onlyOwner {
require(_newToken != address(0), "Invalid token address");
address oldToken = address(sanicToken);
sanicToken = IERC20(_newToken);
emit TokenUpdated(oldToken, _newToken);
}
function updateFeeRecipient(address _newRecipient) external onlyOwner {
require(_newRecipient != address(0), "Invalid fee recipient address");
address oldRecipient = feeRecipient;
feeRecipient = _newRecipient;
emit FeeRecipientUpdated(oldRecipient, _newRecipient);
}
}