aere-contracts/contracts/AereYieldFarm.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

132 lines
5.2 KiB
Solidity

// 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;
}
}