编译器
0.8.20+commit.a1b79de6
文件 1 的 2:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function burn(uint256 value) external;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 2 的 2:TokenStaking.sol
pragma solidity ^0.8.20;
import {IERC20} from "./utils/IERC20.sol";
contract TokenStaking {
IERC20 public taxFarmingToken;
uint public totalStaked;
uint public totalRewards;
uint private accumulatedRewardsPerToken = 1;
uint private constant DECIMALS = 1e12;
struct Stake {
uint tokensStaked;
uint accumulatedRewardsPerToken;
}
mapping (address => Stake) public stakersInfos;
mapping (address => uint) public lastTx;
event TokenStaked(address indexed user, uint amount, uint accumulatedRewardsPerToken);
event TokenUnstaked(address indexed user, uint amount, uint accumulatedRewardsPerToken);
constructor(address _taxFarmingToken) {
taxFarmingToken = IERC20(_taxFarmingToken);
}
modifier blockUser(address user) {
require(lastTx[user] != block.number, "User blocked");
lastTx[user] = block.number;
_;
}
function stake(uint amount) external blockUser(msg.sender) {
if (amount == 0) return;
address user = msg.sender;
if (stakersInfos[user].tokensStaked == 0) stakersInfos[user].accumulatedRewardsPerToken = accumulatedRewardsPerToken;
else _claim(user);
taxFarmingToken.transferFrom(user, address(this), amount);
totalStaked += amount;
stakersInfos[user].tokensStaked += amount;
emit TokenStaked(user, amount, accumulatedRewardsPerToken);
}
function unstake(uint amount) external blockUser(msg.sender) {
address user = msg.sender;
if (amount == 0 || stakersInfos[user].tokensStaked == 0) return;
_claim(user);
require(stakersInfos[user].tokensStaked >= amount, "Not enough staked tokens");
stakersInfos[user].tokensStaked -= amount;
totalStaked -= amount;
taxFarmingToken.transfer(user, amount);
emit TokenUnstaked(user, amount, accumulatedRewardsPerToken);
}
function claim() external blockUser(msg.sender) {
_claim(msg.sender);
}
function _claim(address user) private {
uint rewards = getUserRewards(user);
stakersInfos[user].accumulatedRewardsPerToken = accumulatedRewardsPerToken;
if (rewards == 0) return;
(bool success, ) = payable(user).call{value: rewards}("");
require(success, "Unable to claim rewards");
}
function getUserRewards(address user) public view returns (uint) {
uint rewardsPerToken = accumulatedRewardsPerToken - stakersInfos[user].accumulatedRewardsPerToken;
uint rewards = (rewardsPerToken * stakersInfos[user].tokensStaked) / DECIMALS;
return rewards;
}
receive() payable external {
require(totalStaked != 0, "No stakers");
uint rewardsPerToken = (msg.value * DECIMALS) / totalStaked;
accumulatedRewardsPerToken += rewardsPerToken;
totalRewards += msg.value;
}
}
{
"compilationTarget": {
"contracts/TokenStaking.sol": "TokenStaking"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_taxFarmingToken","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"},{"indexed":false,"internalType":"uint256","name":"accumulatedRewardsPerToken","type":"uint256"}],"name":"TokenStaked","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":"accumulatedRewardsPerToken","type":"uint256"}],"name":"TokenUnstaked","type":"event"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastTx","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakersInfos","outputs":[{"internalType":"uint256","name":"tokensStaked","type":"uint256"},{"internalType":"uint256","name":"accumulatedRewardsPerToken","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"taxFarmingToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]