Goat27LPStaking 合约函数文档
Goat27LPStaking 合约函数文档
本文档列出 src/Goat27/Goat27LPStaking.sol 中所有外部可调用函数的签名、用途、参数与返回值。
权限说明:ANYONE 表示任何地址可调用;GOAT27 表示只能是 goat27 不可变量指定的合约地址;OWNER 表示需要 Ownable.onlyOwner;REENTRANCY_SAFE 表示挂 nonReentrant 修饰符(可重入调用会 revert OZ ReentrancyGuardReentrantCall())。
合约定位:Goat27-USDT LP 代币的无锁仓质押池,Goat27 合约在每次税费分配时把 USDT 转入本合约并调用 triggerDistribution 按 LP 占比分配 USDT 奖励,同时按 10× 同步累积 Goat27Points 奖励。
1. 公共函数:用户操作
1.1 stake
- 签名:
function stake(uint256 amount) external - 权限:ANYONE(
REENTRANCY_SAFE) - 用途:用户质押 GOAT27-USDT LP 代币,获取按 LP 占比瓜分 USDT 奖励的资格。调用前需先自行
approve本合约对 LP 代币的额度。首次质押的地址会写入stakers列表,后续奖励分配会遍历此列表。 - 参数:
amount:本次质押的 LP 数量(LP 自身精度,通常 18 位)。
- 返回值:无。
- 错误:
ZeroAmount()当amount == 0时抛出。string("ERC20: transfer amount exceeds balance")等标准 ERC20 错误:用户未授权或 LP 余额不足(底层safeTransferFrom抛出)。
- 副作用:
- 首次质押的用户:
isStaker[msg.sender] = true并追加到stakers列表。 stakedLp[msg.sender] += amount、totalLpStaked += amount。
- 首次质押的用户:
- 事件:
Staked(msg.sender, amount)。
1.2 redeem
- 签名:
function redeem(uint256 amount) external - 权限:ANYONE(
REENTRANCY_SAFE) - 用途:无条件赎回已质押的 LP 代币,无任何惩罚/费用/锁仓期。即使
pendingUsdt / pendingPoints > 0也可以随时赎回,奖励不会被扣减。 - 参数:
amount:本次赎回的 LP 数量,必须 ≤ 当前质押数量。
- 返回值:无。
- 错误:
ZeroAmount()当amount == 0时抛出。InsufficientStake()当amount > stakedLp[msg.sender]时抛出。
- 副作用:
stakedLp[msg.sender] -= amount、totalLpStaked -= amount。- 若
stakedLp减到 0,不会自动清理stakers列表与isStaker标记(用户仍然出现在getStakerCount()中,分配时通过stakedLp == 0跳过)。
- 事件:
Redeemed(msg.sender, amount)。
1.3 claimRewards
- 签名:
function claimRewards() external - 权限:ANYONE(
REENTRANCY_SAFE) - 用途:一次性提取自己的全部
pendingUsdt与pendingPoints奖励。两个 token 独立判断余额,任一不足都整体回退。 - 参数:无。
- 返回值:无。
- 错误:
ZeroAmount()当pendingUsdt == 0 && pendingPoints == 0时抛出。InsufficientPoolBalance()当pendingUsdt > 0但合约 USDT 余额不足,或pendingPoints > 0但合约 Points 余额不足时抛出。
- 执行逻辑:
1
2
3
4
5
6若 pendingUsdt > 0:
USDT 余额不足 → revert InsufficientPoolBalance
否则 pendingUsdt 清零,USDT 转给 msg.sender
若 pendingPoints > 0:
Points 余额不足 → revert InsufficientPoolBalance
否则 pendingPoints 清零,Points 转给 msg.sender即使 USDT 余额充足但 Points 不足,USDT 也不会发放(整体回退,防止零碎发放)。
- 事件:
RewardsClaimed(msg.sender, usdtReward, pointsReward)。
2. 受限函数:触发分配
2.1 triggerDistribution
- 签名:
function triggerDistribution(uint256 usdtAmount) external - 权限:
GOAT27(REENTRANCY_SAFE) - 用途:由 Goat27 主合约在每次税费分配时调用,按 LP 占比把
usdtAmountUSDT 瓜分给所有 staker,同时按POINTS_MULTIPLIER同步累积 Goat27Points 奖励。调用前 Goat27 必须先把对应数量的 USDT 转入本合约;若转账与triggerDistribution不在同一交易原子完成,USDT 余额会不足,claimRewards时会 revert。 - 参数:
usdtAmount:本次分配的 USDT 数量(USDT 自身精度,通常 18 位;BSC 主网 USDT 也是 18 位)。
- 返回值:无。
- 错误:
OnlyGoat27()当msg.sender != goat27时抛出。
- 边缘情况:
usdtAmount == 0:直接返回,不发事件、不改 storage。totalLpStaked == 0:发DistributionTriggered(usdtAmount, 0),USDT 留在合约余额中等待未来分配;不会清零 staker 状态。- 某个 staker 的
stakedLp == 0(全额赎回过):跳过,不计入stakerCount。 - 某个 staker 的本次份额
(usdtAmount * userStake) / total计算结果为 0(份额太小导致向下取整):跳过,避免分配尘埃。
- 公式:
1
2usdtReward_i = usdtAmount * stakedLp[i] / totalLpStaked
pointsReward_i = usdtReward_i * POINTS_MULTIPLIER (10) - 事件:
DistributionTriggered(usdtAmount, stakerCount)(stakerCount是本次实际获得奖励的用户数)。
3. 管理员函数:资产救援
3.1 rescueToken
- 签名:
function rescueToken(address token, uint256 amount, address recipient) external - 权限:OWNER
- 用途:owner 紧急提取合约中误转入或暂时滞留的任意 BEP20 代币。可用于救援 USDT、Goat27Points、LP 代币,也可救援项目方误转入的任意其他代币。
- 参数:
token:被救援的代币地址,不能为零地址。amount:救援数量,不会做余额校验,若超过余额则safeTransfer会 revert。recipient:接收地址,不能为零地址。
- 返回值:无。
- 错误:
ZeroAddress()当token == 0或recipient == 0时抛出。- 标准 ERC20 错误:余额不足、未授权等(底层
safeTransfer抛出)。
- 事件:
TokenRescued(token, recipient, amount)。
4. 视图函数
4.1 goat27
- 签名:
function goat27() external view returns (address) - 权限:ANYONE
- 用途:返回允许调用
triggerDistribution的 Goat27 主合约地址(不可变量)。
4.2 points
- 签名:
function points() external view returns (address) - 权限:ANYONE
- 用途:返回用于同步发放积分奖励的 Goat27Points 合约地址(不可变量)。
4.3 usdt
- 签名:
function usdt() external view returns (address) - 权限:ANYONE
- 用途:返回用于发放 USDT 奖励的 USDT 合约地址(不可变量)。
4.4 lpToken
- 签名:
function lpToken() external view returns (address) - 权限:ANYONE
- 用途:返回通过
PancakeLibrary.pairFor(goat27, usdt)CREATE2 算法预先算出的 LP 代币地址(不可变量)。注意:此处只算出地址,并不要求 LP 已部署。
4.5 POINTS_MULTIPLIER
- 签名:
function POINTS_MULTIPLIER() external view returns (uint256) - 权限:ANYONE
- 用途:返回常量
10,即每分到 1 USDT 同步累积 10 枚 Goat27Points。
4.6 stakedLp
- 签名:
function stakedLp(address) external view returns (uint256) - 权限:ANYONE
- 用途:查询指定用户当前质押的 LP 数量。
4.7 pendingUsdt
- 签名:
function pendingUsdt(address) external view returns (uint256) - 权限:ANYONE
- 用途:查询指定用户待领取的 USDT 奖励余额。
4.8 pendingPoints
- 签名:
function pendingPoints(address) external view returns (uint256) - 权限:ANYONE
- 用途:查询指定用户待领取的 Goat27Points 奖励余额。
4.9 isStaker
- 签名:
function isStaker(address) external view returns (bool) - 权限:ANYONE
- 用途:查询指定地址是否在
stakers列表中(首次质押后置为 true,永不清除)。
4.10 stakers
- 签名:
function stakers(uint256) external view returns (address) - 权限:ANYONE
- 用途:按索引返回
stakers列表中的地址。该列表用于分配奖励时的遍历。
4.11 totalLpStaked
- 签名:
function totalLpStaked() external view returns (uint256) - 权限:ANYONE
- 用途:返回当前所有用户质押的 LP 总数,
triggerDistribution计算占比时分母。
4.12 getStakerCount
- 签名:
function getStakerCount() external view returns (uint256) - 权限:ANYONE
- 用途:返回
stakers数组的长度。注意:可能多于当前实际有质押的用户数,因为已全额赎回的用户不会被清理。
5. Ownable 标准函数
合约继承 OpenZeppelin Ownable(initialOwner_),提供以下标准接口(节选):
| 函数 | 签名 | 说明 |
|---|---|---|
owner |
function owner() external view returns (address) |
当前合约所有者 |
transferOwnership |
function transferOwnership(address newOwner) external |
发起所有权转移(仅 owner) |
renounceOwnership |
function renounceOwnership() external |
放弃所有权(仅 owner) |
OwnershipTransferred |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) |
所有权转移事件(来自 OZ) |
6. 事件索引
| 事件 | 签名 | 触发时机 |
|---|---|---|
Staked |
event Staked(address indexed user, uint256 amount) |
用户质押 LP |
Redeemed |
event Redeemed(address indexed user, uint256 amount) |
用户赎回 LP |
DistributionTriggered |
event DistributionTriggered(uint256 indexed usdtAmount, uint256 stakerCount) |
Goat27 调用 triggerDistribution 完成分配,stakerCount 为本次实际获得奖励的用户数 |
RewardsClaimed |
event RewardsClaimed(address indexed user, uint256 usdtAmount, uint256 pointsAmount) |
用户通过 claimRewards 提取奖励 |
TokenRescued |
event TokenRescued(address indexed token, address indexed recipient, uint256 amount) |
owner 救援代币 |
OwnershipTransferred |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) |
所有权转移(来自 OZ) |
7. 错误索引
| 错误 | 触发条件 |
|---|---|
ZeroAddress() |
关键参数为零地址(适用于 rescueToken 等) |
ZeroAmount() |
质押/赎回/领取奖励时数量为 0 |
InsufficientStake() |
赎回数量超过当前质押数量 |
NoBnb() |
直接向合约发送 BNB |
OnlyGoat27() |
非 goat27 不可变量地址调用了 triggerDistribution |
InsufficientPoolBalance() |
claimRewards 时 USDT 或 Points 余额不足 |
ReentrancyGuardReentrantCall() |
重入调用(OZ 自带) |
8. 接收 BNB
合约不接受 BNB:任何 msg.value > 0 的直接转账都会 revert NoBnb()。
⚠️ 此项设计意味着合约不会出现误转入 BNB 滞留的情况;若希望接收 BNB,只能通过 wrap → WBNB → 救援的方式。
9. 分配示例
假设 3 个用户 A/B/C 分别质押 100/300/600 LP(总 1000 LP),Goat27 调用 triggerDistribution(100 * 1e18) 分配 100 USDT:
| 用户 | LP 质押量 | USDT 奖励(100 * stake / 1000) | Points 奖励(USDT × 10) |
|---|---|---|---|
| A | 100 | 10 USDT | 100 Points |
| B | 300 | 30 USDT | 300 Points |
| C | 600 | 60 USDT | 600 Points |
| 总计 | 1000 | 100 USDT | 1000 Points |
注意:此处忽略向下取整损失;若有零头,实际总量可能略小于 100 USDT(归合约余额,可通过
rescueToken回收或等待下一次分配触发再次分配)。
10. 部署前/后清单
部署前必读
- Goat27、Goat27Points、USDT、初始 owner 四个地址必须正确;三者若为零地址则
constructorrevertZeroAddress()。 - LP 代币地址由
PancakeLibrary.pairFor(goat27, usdt)计算;只要 Goat27 + USDT 正确,LP 地址自动正确,无需查 PancakeSwap Factory。
部署后必做
- 手动向本合约转入 Goat27Points(例如管理员调
transfer转入若干积分);否则用户首次claimRewards会因 Points 余额不足 revert。 - 不需要手动转入 USDT;每次 Goat27 自动分配时会同步转入。
- 推荐把 LP 质押池加入 Goat27 的双白名单(
addToWhitelist调用),避免 LP 池自身持币时被 5% 税污染(尽管本合约不主动接收 Goat27)。
救援场景
- 若合约 USDT 或 Points 余额不足且长期未补充,
claimRewards会持续 revertInsufficientPoolBalance;通过transfer向合约补充对应 token 后即可正常领取。 - 若误转入其他代币,owner 可通过
rescueToken提回;但若误转入 LP 代币,注意救援会降低合约总 LP 持有量,从而让剩余 staker 占比变大,不影响已记录但未发放的奖励额度。