mirror of
https://github.com/kucherenko/jscpd.git
synced 2026-09-19 08:11:03 +08:00
64e471e2b0
Adds 92 new tests (178 → 270 total) with zero source-file modifications: - packages/finder: 13 new test files covering all reporters, subscribers, validators, hooks, utils, options, cloneFound, getFilesToDetect, and InFilesDetector; shared buildClone() factory helper - apps/jscpd: 3 new test files (options-unit, print, store) plus extensions to formats, modes, and options integration tests - vitest.config.ts added to both packages to restrict coverage.include to src/** only, eliminating bin/ and prism.js noise - Fixes 3 tokenizer bugs (trailing space in format lookup, tokenization edge cases) uncovered by the new format fixture suite - Adds fixture files for 40+ language formats to support formats.test.ts
81 lines
2.9 KiB
Solidity
81 lines
2.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
|
|
/// @title SimpleStaking - Stake ERC20 tokens to earn rewards
|
|
contract SimpleStaking is Ownable, ReentrancyGuard {
|
|
IERC20 public immutable stakingToken;
|
|
IERC20 public immutable rewardToken;
|
|
|
|
uint256 public rewardRate; // tokens per second
|
|
uint256 public lastUpdateTime;
|
|
uint256 public rewardPerTokenStored;
|
|
|
|
mapping(address => uint256) public userRewardPerTokenPaid;
|
|
mapping(address => uint256) public rewards;
|
|
mapping(address => uint256) public stakedBalance;
|
|
|
|
uint256 public totalStaked;
|
|
|
|
event Staked(address indexed user, uint256 amount);
|
|
event Withdrawn(address indexed user, uint256 amount);
|
|
event RewardClaimed(address indexed user, uint256 reward);
|
|
|
|
constructor(address _stakingToken, address _rewardToken, uint256 _rewardRate) {
|
|
stakingToken = IERC20(_stakingToken);
|
|
rewardToken = IERC20(_rewardToken);
|
|
rewardRate = _rewardRate;
|
|
}
|
|
|
|
modifier updateReward(address account) {
|
|
rewardPerTokenStored = rewardPerToken();
|
|
lastUpdateTime = block.timestamp;
|
|
if (account != address(0)) {
|
|
rewards[account] = earned(account);
|
|
userRewardPerTokenPaid[account] = rewardPerTokenStored;
|
|
}
|
|
_;
|
|
}
|
|
|
|
function rewardPerToken() public view returns (uint256) {
|
|
if (totalStaked == 0) return rewardPerTokenStored;
|
|
return rewardPerTokenStored +
|
|
((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / totalStaked;
|
|
}
|
|
|
|
function earned(address account) public view returns (uint256) {
|
|
return (stakedBalance[account] *
|
|
(rewardPerToken() - userRewardPerTokenPaid[account])) /
|
|
1e18 + rewards[account];
|
|
}
|
|
|
|
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
|
|
require(amount > 0, "Cannot stake 0");
|
|
totalStaked += amount;
|
|
stakedBalance[msg.sender] += amount;
|
|
stakingToken.transferFrom(msg.sender, address(this), amount);
|
|
emit Staked(msg.sender, amount);
|
|
}
|
|
|
|
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
|
|
require(amount > 0, "Cannot withdraw 0");
|
|
require(stakedBalance[msg.sender] >= amount, "Insufficient balance");
|
|
totalStaked -= amount;
|
|
stakedBalance[msg.sender] -= amount;
|
|
stakingToken.transfer(msg.sender, amount);
|
|
emit Withdrawn(msg.sender, amount);
|
|
}
|
|
|
|
function claimReward() external nonReentrant updateReward(msg.sender) {
|
|
uint256 reward = rewards[msg.sender];
|
|
if (reward > 0) {
|
|
rewards[msg.sender] = 0;
|
|
rewardToken.transfer(msg.sender, reward);
|
|
emit RewardClaimed(msg.sender, reward);
|
|
}
|
|
}
|
|
}
|