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.
132 lines
5.2 KiB
Solidity
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;
|
|
}
|
|
}
|