// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
interface IERC20 {
function transfer(
address recipient,
uint256 amount
) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
contract StakingContract is Ownable {
IERC20 public token;
struct Stake {
uint256 amount;
uint256 startTime;
uint256 duration;
uint256 rewardPercentage;
bool active;
uint256 claimedAmount;
}
uint256 public rewardPercentage30Days = 10;
uint256 public rewardPercentage60Days = 25;
uint256 public rewardPercentage90Days = 45;
uint public paneltyPercentage = 5;
mapping(address => Stake[]) public stakes;
event Staked(address indexed user, uint256 amount, uint256 duration);
event Withdrawn(address indexed user, uint256 amount);
event EmergencyWithdrawn(address indexed user, uint256 amount);
constructor(address _token) {
token = IERC20(_token);
}
function stake(uint256 _amount, uint256 _duration) external {
require(
_duration == 30 || _duration == 60 || _duration == 90,
"Invalid duration"
);
uint256 rewardPercentage = 0;
if (_duration == 30) {
rewardPercentage = rewardPercentage30Days;
} else if (_duration == 60) {
rewardPercentage = rewardPercentage60Days;
} else if (_duration == 90) {
rewardPercentage = rewardPercentage90Days;
}
require(
token.transferFrom(msg.sender, address(this), _amount),
"Transfer failed"
);
stakes[msg.sender].push(
Stake({
amount: _amount,
startTime: block.timestamp,
duration: _duration * 1 days,
rewardPercentage: rewardPercentage,
active: true,
claimedAmount: 0
})
);
emit Staked(msg.sender, _amount, _duration);
}
function calculateReward(
address _user,
uint256 _stakeIndex
) public view returns (uint256) {
Stake memory userStake = stakes[_user][_stakeIndex];
uint256 timeElapsed = block.timestamp - userStake.startTime;
uint256 duration = userStake.duration;
if (!userStake.active) return 0;
if (timeElapsed >= duration && userStake.active) {
return (userStake.amount * userStake.rewardPercentage) / 100;
} else {
return
(userStake.amount * userStake.rewardPercentage * timeElapsed) /
(100 * userStake.duration);
}
}
// Function for regular withdrawal (allowed after lock period)
function withdraw(uint256 _stakeIndex) external {
require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index");
Stake storage userStake = stakes[msg.sender][_stakeIndex];
uint256 reward = calculateReward(msg.sender, _stakeIndex);
uint256 timeElapsed = block.timestamp - userStake.startTime;
require(reward > 0, "No rewards to claim yet");
require(timeElapsed >= userStake.duration, "Lock period not ended");
uint256 withdrawAmount = userStake.amount + reward;
require(token.transfer(msg.sender, withdrawAmount), "Transfer failed");
userStake.active = false;
userStake.claimedAmount = reward;
emit Withdrawn(msg.sender, withdrawAmount);
}
// Function for emergency withdrawal (allowed before lock period ends)
function emergencyWithdraw(uint256 _stakeIndex) external {
require(_stakeIndex < stakes[msg.sender].length, "Invalid stake index");
Stake storage userStake = stakes[msg.sender][_stakeIndex];
uint256 reward = calculateReward(msg.sender, _stakeIndex);
uint256 timeElapsed = block.timestamp - userStake.startTime;
require(timeElapsed < userStake.duration, "Lock period ended");
uint256 penalty = ((userStake.amount + reward) * paneltyPercentage) /
100;
uint256 withdrawAmount = userStake.amount + reward - penalty;
require(token.transfer(msg.sender, withdrawAmount), "Transfer failed");
userStake.active = false;
userStake.claimedAmount = reward;
emit EmergencyWithdrawn(msg.sender, withdrawAmount);
}
function getStakesCount(address _user) external view returns (uint256) {
return stakes[_user].length;
}
function getStakeDetails(
address _user,
uint256 _index
)
external
view
returns (
uint256 amount,
uint256 startTime,
uint256 duration,
uint256 rewardPercentage,
bool active
)
{
Stake memory userStake = stakes[_user][_index];
return (
userStake.amount,
userStake.startTime,
userStake.duration,
userStake.rewardPercentage,
userStake.active
);
}
function ownerWithdrawTokens(uint256 _amount) external onlyOwner {
require(token.transfer(msg.sender, _amount), "Transfer failed");
}
function withdrawBep20Tokens(
address _token,
uint256 _amount
) external onlyOwner {
IERC20(_token).transfer(msg.sender, _amount);
}
// Function for the contract owner to update reward percentages
function updateRewardPercentages(
uint256 _rewardPercentage30Days,
uint256 _rewardPercentage60Days,
uint256 _rewardPercentage90Days
) external onlyOwner {
rewardPercentage30Days = _rewardPercentage30Days;
rewardPercentage60Days = _rewardPercentage60Days;
rewardPercentage90Days = _rewardPercentage90Days;
}
function changePaneltyAmount(uint _panelty) external onlyOwner {
paneltyPercentage = _panelty;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
{
"compilationTarget": {
"contracts/Lock.sol": "StakingContract"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdrawn","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":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"duration","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_stakeIndex","type":"uint256"}],"name":"calculateReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_panelty","type":"uint256"}],"name":"changePaneltyAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeIndex","type":"uint256"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getStakeDetails","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardPercentage","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getStakesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"ownerWithdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paneltyPercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rewardPercentage30Days","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPercentage60Days","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPercentage90Days","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakes","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardPercentage","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"claimedAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_rewardPercentage30Days","type":"uint256"},{"internalType":"uint256","name":"_rewardPercentage60Days","type":"uint256"},{"internalType":"uint256","name":"_rewardPercentage90Days","type":"uint256"}],"name":"updateRewardPercentages","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakeIndex","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawBep20Tokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]