Skip to content

Instantly share code, notes, and snippets.

@AndreiD
Created October 1, 2024 07:13
Show Gist options
  • Save AndreiD/71b25acde9cbd5379659b409599661e6 to your computer and use it in GitHub Desktop.
Save AndreiD/71b25acde9cbd5379659b409599661e6 to your computer and use it in GitHub Desktop.
erc20staking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/utils/Pausable.sol";
contract DogStaking is ReentrancyGuard, Ownable, Pausable {
IERC20 stakingToken;
struct stakeStruct {
uint256 amountStaked;
uint256 timeStaked;
}
uint256 public emissionRate = 1;
mapping(address => stakeStruct) public userStakeMap;
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event RewardClaimed(address indexed user, uint256 amount);
event RewardsAdded(uint256 amount);
event EmissionRateChanged(uint256 newRate);
constructor(address _stakingToken, uint256 _emissionRate, address _initialOwner) Ownable(_initialOwner) {
stakingToken = IERC20(_stakingToken);
emissionRate = _emissionRate;
}
function calculateRewards(address _staker) public view returns (uint256) {
uint256 contractBalance = stakingToken.balanceOf(address(this));
stakeStruct storage _stake = userStakeMap[_staker];
uint256 stakingDuration = block.timestamp - _stake.timeStaked;
return (_stake.amountStaked * stakingDuration * emissionRate * (contractBalance / 1e18)) / 1e18;
}
function stake(uint256 _amount) external nonReentrant whenNotPaused {
uint256 balance = stakingToken.balanceOf(msg.sender);
require(balance >= _amount, "not enough tokens to stake");
bool status = stakingToken.transferFrom(msg.sender, address(this), _amount);
require(status == true, "transfer failed");
stakeStruct storage _stake = userStakeMap[msg.sender];
_stake.amountStaked += _amount;
_stake.timeStaked = block.timestamp;
emit Staked(msg.sender, _amount);
}
function claim() external nonReentrant {
stakeStruct storage _stake = userStakeMap[msg.sender];
require(_stake.amountStaked > 0, "no stake found");
uint256 rewards = calculateRewards(msg.sender);
require(rewards > 0, "no rewards to claim");
_stake.timeStaked = block.timestamp;
stakingToken.transfer(msg.sender, rewards);
emit RewardClaimed(msg.sender, rewards);
}
function unstake() external nonReentrant {
stakeStruct storage _stake = userStakeMap[msg.sender];
require(_stake.amountStaked > 0, "no stake found");
bool status = stakingToken.transfer(msg.sender, _stake.amountStaked);
require(status == true, "Transfer failed");
uint256 rewards = calculateRewards(msg.sender);
if (rewards > 0) {
stakingToken.transfer(msg.sender, rewards);
}
_stake.amountStaked -= _stake.amountStaked;
_stake.timeStaked = block.timestamp;
emit Unstaked(msg.sender, _stake.amountStaked);
}
function addRewards(uint256 amount) external nonReentrant {
require(amount > 0, "Cannot add 0 rewards");
stakingToken.transferFrom(msg.sender, address(this), amount);
emit RewardsAdded(amount);
}
function setEmissionRate(uint256 _emissionRate) external onlyOwner {
emissionRate = _emissionRate;
emit EmissionRateChanged(_emissionRate);
}
function pause() external onlyOwner {
_pause();
}
// Function to unpause staking
function unpause() external onlyOwner {
_unpause();
}
function airdropRewards(
address[] calldata recipients,
uint256[] calldata amounts
) external onlyOwner {
require(recipients.length == amounts.length, "Mismatched array lengths");
for (uint256 i = 0; i < recipients.length; i++) {
(bool success, bytes memory data) = address(stakingToken).call(
abi.encodeWithSelector(IERC20.transfer.selector, recipients[i], amounts[i])
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "Token transfer failed");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment