SimpleStaking
Single-asset staking. Users deposit an ERC-20 token and earn a second token as rewards per block.
SimpleStaking.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.20; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool);} contract SimpleStaking { IERC20 public immutable stakingToken; IERC20 public immutable rewardToken; uint256 public immutable rewardRatePerBlock; address public owner; uint256 public totalStaked; uint256 public rewardPerTokenStored; uint256 public lastUpdateBlock; mapping(address => uint256) public staked; mapping(address => uint256) public userRewardPerTokenPaid; mapping(address => uint256) public rewards; event Staked(address indexed user, uint256 amount); event Withdrawn(address indexed user, uint256 amount); event RewardPaid(address indexed user, uint256 reward); constructor(address stakingToken_, address rewardToken_, uint256 rewardRatePerBlock_) { stakingToken = IERC20(stakingToken_); rewardToken = IERC20(rewardToken_); rewardRatePerBlock = rewardRatePerBlock_; owner = msg.sender; lastUpdateBlock = block.number; } function rewardPerToken() public view returns (uint256) { if (totalStaked == 0) return rewardPerTokenStored; return rewardPerTokenStored + ((block.number - lastUpdateBlock) * rewardRatePerBlock * 1e18) / totalStaked; } function earned(address account) public view returns (uint256) { return (staked[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 + rewards[account]; } modifier updateReward(address account) { rewardPerTokenStored = rewardPerToken(); lastUpdateBlock = block.number; rewards[account] = earned(account); userRewardPerTokenPaid[account] = rewardPerTokenStored; _; } function stake(uint256 amount) external updateReward(msg.sender) { require(amount > 0, "ZERO"); totalStaked += amount; staked[msg.sender] += amount; require(stakingToken.transferFrom(msg.sender, address(this), amount), "TRANSFER_FAILED"); emit Staked(msg.sender, amount); } function withdraw(uint256 amount) external updateReward(msg.sender) { require(staked[msg.sender] >= amount, "BALANCE"); totalStaked -= amount; staked[msg.sender] -= amount; require(stakingToken.transfer(msg.sender, amount), "TRANSFER_FAILED"); emit Withdrawn(msg.sender, amount); } function claimReward() external updateReward(msg.sender) { uint256 reward = rewards[msg.sender]; require(reward > 0, "NO_REWARD"); rewards[msg.sender] = 0; require(rewardToken.transfer(msg.sender, reward), "TRANSFER_FAILED"); emit RewardPaid(msg.sender, reward); }}