// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /** * @title AereYieldFarm * @notice Stake LP tokens (e.g. AereSwap pair tokens) and earn rewards in native AERE. * @dev Standard MasterChef-style accounting (accRewardPerShare * 1e12). * Reward source: native AERE deposited via fundRewards() by treasury/governance. * Pools are added by the owner; the same staking token can have multiple pools * with different reward rates (e.g. boosted promotions). */ contract AereYieldFarm is Ownable, ReentrancyGuard { using SafeERC20 for IERC20; struct PoolInfo { IERC20 stakeToken; uint256 rewardPerSecond; // in wei AERE uint256 lastRewardTime; uint256 accRewardPerShare; // scaled 1e12 uint256 totalStaked; } struct UserInfo { uint256 amount; uint256 rewardDebt; uint256 pending; } PoolInfo[] public pools; mapping(uint256 => mapping(address => UserInfo)) public users; event PoolAdded(uint256 indexed pid, address stakeToken, uint256 rewardPerSecond); event PoolRateUpdated(uint256 indexed pid, uint256 rewardPerSecond); event Deposited(uint256 indexed pid, address indexed user, uint256 amount); event Withdrawn(uint256 indexed pid, address indexed user, uint256 amount); event Harvested(uint256 indexed pid, address indexed user, uint256 amount); event RewardsFunded(uint256 amount); receive() external payable { emit RewardsFunded(msg.value); } function poolCount() external view returns (uint256) { return pools.length; } function addPool(address stakeToken, uint256 rewardPerSecond) external onlyOwner returns (uint256 pid) { require(stakeToken != address(0), "token"); pools.push(PoolInfo({ stakeToken: IERC20(stakeToken), rewardPerSecond: rewardPerSecond, lastRewardTime: block.timestamp, accRewardPerShare: 0, totalStaked: 0 })); pid = pools.length - 1; emit PoolAdded(pid, stakeToken, rewardPerSecond); } function setRewardRate(uint256 pid, uint256 rewardPerSecond) external onlyOwner { _accrue(pid); pools[pid].rewardPerSecond = rewardPerSecond; emit PoolRateUpdated(pid, rewardPerSecond); } function fundRewards() external payable { emit RewardsFunded(msg.value); } function pendingReward(uint256 pid, address user) external view returns (uint256) { PoolInfo memory p = pools[pid]; UserInfo memory u = users[pid][user]; uint256 acc = p.accRewardPerShare; if (block.timestamp > p.lastRewardTime && p.totalStaked > 0) { uint256 reward = (block.timestamp - p.lastRewardTime) * p.rewardPerSecond; acc += (reward * 1e12) / p.totalStaked; } return u.pending + (u.amount * acc) / 1e12 - u.rewardDebt; } function deposit(uint256 pid, uint256 amount) external nonReentrant { _accrue(pid); PoolInfo storage p = pools[pid]; UserInfo storage u = users[pid][msg.sender]; if (u.amount > 0) { u.pending += (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt; } if (amount > 0) { p.stakeToken.safeTransferFrom(msg.sender, address(this), amount); u.amount += amount; p.totalStaked += amount; } u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12; emit Deposited(pid, msg.sender, amount); } function withdraw(uint256 pid, uint256 amount) external nonReentrant { _accrue(pid); PoolInfo storage p = pools[pid]; UserInfo storage u = users[pid][msg.sender]; require(u.amount >= amount, "balance"); u.pending += (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt; u.amount -= amount; p.totalStaked -= amount; u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12; if (amount > 0) p.stakeToken.safeTransfer(msg.sender, amount); emit Withdrawn(pid, msg.sender, amount); } function harvest(uint256 pid) external nonReentrant { _accrue(pid); PoolInfo storage p = pools[pid]; UserInfo storage u = users[pid][msg.sender]; uint256 reward = u.pending + (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt; u.pending = 0; u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12; require(reward > 0, "nothing"); require(address(this).balance >= reward, "underfunded"); (bool ok, ) = msg.sender.call{value: reward}(""); require(ok, "transfer"); emit Harvested(pid, msg.sender, reward); } function _accrue(uint256 pid) internal { PoolInfo storage p = pools[pid]; if (block.timestamp <= p.lastRewardTime) return; if (p.totalStaked == 0) { p.lastRewardTime = block.timestamp; return; } uint256 reward = (block.timestamp - p.lastRewardTime) * p.rewardPerSecond; p.accRewardPerShare += (reward * 1e12) / p.totalStaked; p.lastRewardTime = block.timestamp; } }