SPCXStakingRewards.sol· solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5import "@openzeppelin/contracts/access/Ownable.sol";
6
7contract SPCXStakingRewards is Ownable {
8 IERC20 public immutable stakingToken;
9 IERC20 public immutable rewardToken;
10
11 uint256 public totalStaked;
12 uint256 public accRewardPerShare;
13
14 mapping(address => uint256) public staked;
15 mapping(address => uint256) public rewardDebt;
16
17 address public keeper;
18
19 event Staked(address indexed user, uint256 amount);
20 event Unstaked(address indexed user, uint256 amount);
21 event Claimed(address indexed user, uint256 amount);
22 event ProfitDistributed(uint256 amount);
23 event KeeperUpdated(address keeper);
24
25 modifier onlyKeeper() {
26 require(msg.sender == keeper, "Not keeper");
27 _;
28 }
29
30 constructor(
31 address _stakingToken,
32 address _rewardToken,
33 address _keeper
34 ) Ownable(msg.sender) {
35 stakingToken = IERC20(_stakingToken);
36 rewardToken = IERC20(_rewardToken);
37 keeper = _keeper;
38 }
39
40 function setKeeper(address _keeper) external onlyOwner {
41 keeper = _keeper;
42 emit KeeperUpdated(_keeper);
43 }
44
45 function stake(uint256 amount) external {
46 require(amount > 0, "Amount zero");
47
48 _claim(msg.sender);
49
50 stakingToken.transferFrom(msg.sender, address(this), amount);
51
52 staked[msg.sender] += amount;
53 totalStaked += amount;
54
55 rewardDebt[msg.sender] =
56 (staked[msg.sender] * accRewardPerShare) / 1e18;
57
58 emit Staked(msg.sender, amount);
59 }
60
61 function unstake(uint256 amount) external {
62 require(amount > 0, "Amount zero");
63 require(staked[msg.sender] >= amount, "Too much");
64
65 _claim(msg.sender);
66
67 staked[msg.sender] -= amount;
68 totalStaked -= amount;
69
70 rewardDebt[msg.sender] =
71 (staked[msg.sender] * accRewardPerShare) / 1e18;
72
73 stakingToken.transfer(msg.sender, amount);
74
75 emit Unstaked(msg.sender, amount);
76 }
77
78 function claim() external {
79 _claim(msg.sender);
80
81 rewardDebt[msg.sender] =
82 (staked[msg.sender] * accRewardPerShare) / 1e18;
83 }
84
85 function distributeProfit(uint256 amount) external onlyKeeper {
86 require(amount > 0, "Amount zero");
87 require(totalStaked > 0, "No stakers");
88
89 rewardToken.transferFrom(msg.sender, address(this), amount);
90
91 accRewardPerShare += (amount * 1e18) / totalStaked;
92
93 emit ProfitDistributed(amount);
94 }
95
96 function pendingRewards(address user) public view returns (uint256) {
97 uint256 accumulated =
98 (staked[user] * accRewardPerShare) / 1e18;
99
100 if (accumulated < rewardDebt[user]) {
101 return 0;
102 }
103
104 return accumulated - rewardDebt[user];
105 }
106
107 function _claim(address user) internal {
108 uint256 reward = pendingRewards(user);
109
110 if (reward > 0) {
111 rewardToken.transfer(user, reward);
112 emit Claimed(user, reward);
113 }
114 }
115}