Goat27StakingUpgradeable 合约函数文档

本文档列出 src/Goat27/Goat27StakingUpgradeable.sol 中所有外部可调用函数的签名、用途、参数与返回值。
权限说明:OWNER 表示需要 OwnableUpgradeable.onlyOwner;ANYONE 表示任何地址可调用;REENTRANCY_SAFE 表示挂 nonReentrant 修饰符(由 OZ ReentrancyGuardTransient 提供,使用 transient storage,不占 storage slot)。

合约定位:UUPS 可升级的多层级质押挖矿合约;提供单币质押静态收益、推荐关系驱动的多层级业绩、加速奖励、直推奖励、销毁排行榜奖励。


1. 初始化(代理入口)

1.1 initialize

  • 签名:function initialize(address initialOwner, address stakingToken_, address usdtToken_) external initializer
  • 权限:ANYONE(仅可调用一次,后续调用 revert InvalidInitialization)
  • 用途:通过 ERC1967 代理完成合约初始化,一次调用即完成代币、预言机、销毁地址、奖池参数等全部初始化。
    • owner = initialOwner
    • stakingToken = stakingToken_(项目自有质押代币)
    • usdtToken = usdtToken_(USDT 稳定币,用于计价)
    • stakingUsdtPair = PancakeLibrary.pairFor(stakingToken_, usdtToken_)(自动由 CREATE2 算出 GOAT27/USDT 交易对地址)
    • burnAddress = 0x000000000000000000000000000000000000dEaD(硬编码,不可配置)
    • launchedAt = block.timestamp
    • lastLevelDistributionDay = _currentDay()
    • lastBurnRankDistributionDay = _currentDay()
    • minStakeValue = MIN_STAKE_VALUE (100 ether USDT)
    • maxStakeValue = MAX_STAKE_VALUE (11_000 ether USDT)
    • rewardInterval = 1 days
    • 默认等级门槛(USDT 计):[10_000, 50_000, 200_000, 500_000, 1_000_000]
    • 默认等级奖池分配比例(BPS):[300, 200, 200, 100, 100](合计 9%)
  • 参数:
    • initialOwner:合约所有者地址,必须非零(OZ Ownable 校验)。
    • stakingToken_:质押代币地址,必须非零(项目自有代币,部署前需确保在 Pancake V2 上已和 USDT 形成 Pair)。
    • usdtToken_:USDT 稳定币地址,必须非零(BSC 上一般用 0x55d398326f...)。
  • 返回值:无。
  • Pair 地址自动计算:Pair 地址由合约内部通过 PancakeLibrary.pairFor() 按 CREATE2 算出,无需部署者传入,也避免了人工算错或前后顺序不一致。底层使用 BSC 主网常量 PANCAKE_FACTORY=0xcA143Ce32...PANCAKE_INIT_CODE_HASH=0x00fb7f8...
  • 单跳预言:GOAT27 ↔ USDT 直接换算,价格精度与执行路径都更短。Pair 必须已经存在并有流动性(部署流程 Step 3 已保证)。
  • 销毁地址固定:burnAddress 一律为 0x...dEaD,不接受参数传入;若需自定义销毁地址,需通过 UUPS 升级新实现合约。
  • 错误:InvalidInitialization() 当已被初始化时抛出(OZ 自带)。
  • 事件:Configured(stakingToken, usdtToken, stakingUsdtPair, burnAddress)(沿用 Configured 事件名,便于链下索引)。
  • 注意:历史版本曾有独立的 configure(...) 函数允许 owner 二次配置并使用 WBNB 两跳预言,新版本已将其并入 initialize 并改为单跳 GOAT27/USDT Pair,该函数不再存在;若需修改代币/Pair/销毁地址,必须通过 UUPS 升级。

2. owner 配置接口

2.1 setStakeValueLimits

  • 签名:function setStakeValueLimits(uint256 minStakeValue_, uint256 maxStakeValue_) external
  • 权限:OWNER
  • 用途:调整单笔质押价值上下限(USDT 计)。存为 0 表示回退到常量默认值。
  • 参数:
    • minStakeValue_:新的最小质押价值,必须 > 0
    • maxStakeValue_:新的最大质押价值,必须 ≥ minStakeValue_
  • 返回值:无。
  • 错误:
    • MinZero()minStakeValue_ == 0 时抛出。
    • BadLimits()maxStakeValue_ < minStakeValue_ 时抛出。
  • 事件:StakeValueLimitsUpdated(minStakeValue, maxStakeValue)

2.2 setLevelThresholds

  • 签名:function setLevelThresholds(uint256[5] calldata thresholds) external
  • 权限:OWNER
  • 用途:调整 5 个等级的业绩门槛(USDT 计)。会自动调用 _rebuildLevelCounts() 重建所有用户的等级人数统计,确保次日等级奖池瓜分时使用最新人数。
  • 参数:
    • thresholds:5 个新门槛,要求 thresholds[0] > 0 且严格单调递增。
  • 返回值:无。
  • 错误:
    • ThresholdZero()thresholds[0] == 0 时抛出。
    • BadThresholds() 当门槛不单调递增时抛出。
  • 事件:LevelThresholdsUpdated(thresholds)

2.3 setRewardInterval

  • 签名:function setRewardInterval(uint256 rewardInterval_) external
  • 权限:OWNER
  • 用途:调整静态收益领取间隔(秒)。默认 1 天。质押停止时间、奖池销毁时间、利率下降时间均为常量,owner 不可调整。
  • 参数:
    • rewardInterval_:新的间隔,必须 > 0
  • 返回值:无。
  • 错误:IntervalZero()rewardInterval_ == 0 时抛出。
  • 事件:RewardIntervalUpdated(rewardInterval)

2.4 resetActiveCycleRewardValue

  • 签名:function resetActiveCycleRewardValue(address user) external
  • 权限:OWNER
  • 用途:重置指定用户本周期的”已分配加速奖励价值”。用于处理误操作导致加速奖励分配异常的情况。仅在本周期刚开始时(未领取任何静态收益)允许。
  • 参数:
    • user:目标用户地址。
  • 返回值:无。
  • 错误:
    • NoActiveStake() 当用户无有效质押时抛出。
    • CycleStarted()startedAt != lastStaticClaimAtstartedAt == 0 时抛出。
    • CycleClaimed()claimedStaticDays != 0 时抛出。
  • 事件:ActiveCycleRewardValueReset(user)

2.5 rebuildLevelCounts

  • 签名:function rebuildLevelCounts() external
  • 权限:OWNER
  • 用途:重建所有用户的等级人数统计(在等级门槛变更后已自动调用一次,本接口提供手动入口,用于应对极端异常)。
  • 返回值:无。
  • 副作用:delete levelUserCounts → 遍历 users 重新统计各等级人数。

3. 用户操作接口

3.1 bindReferrer

  • 签名:function bindReferrer(address referrer) external
  • 权限:ANYONE
  • 用途:用户绑定推荐人(只能绑定一次)。必须在 stake() 前绑定,否则 5% 直推奖励会因无主而销毁。
  • 参数:
    • referrer:推荐人地址,不能为自己或零地址
  • 返回值:无。
  • 错误:
    • ReferrerExists() 当用户已绑定过推荐人时抛出。
    • ReferrerZero()referrer == 0 时抛出。
    • ReferrerSelf()referrer == msg.sender 时抛出。
  • 副作用:
    • 自动注册 msg.sender 到全局用户列表。
    • 自动注册 referrer(若未注册)。
    • 增加 accounts[referrer].directCount
    • 追加 msg.senderdirects[referrer]
  • 事件:ReferrerBound(user, referrer)

3.2 stake

  • 签名:function stake(uint256 amount) external
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:用户质押入口。流程:
    1. 检查合约已配置且未到质押停止时间(STAKING_STOP_TIMESTAMP)。
    2. 自动分配今日的等级奖池与销毁排行榜奖励(若跨日)。
    3. 处理上一周期过期。
    4. 通过 Uniswap V2 两跳预言把 amount 换算为 USDT 价值。
    5. 校验价值在 [effectiveMinStakeValue, effectiveMaxStakeValue] 内。
    6. 转账代币到本合约并按比例分配:
      • 85% 销毁(burnAmount)
      • 1% 进入销毁排行榜奖励池(rankAmount)
      • 5% 直推奖励(若推荐人存在;否则销毁)
      • 9% 进入 5 个等级奖池(按 levelDepositBps)
    7. 沿推荐链向上累加团队业绩(最多 200 代)。
  • 参数:
    • amount:质押的代币数量(18 位小数)。
  • 返回值:无。
  • 错误:
    • NotConfigured() stakingToken == 0 时抛出。
    • AmountZero() amount == 0 时抛出。
    • StakingStopped() 已到 STAKING_STOP_TIMESTAMP 时抛出。
    • ActiveStakeExists() 用户已有有效质押时抛出。
    • BelowMin() USDT 价值 < 最小质押价值时抛出。
    • AboveMax() USDT 价值 > 最大质押价值时抛出。
    • BadSplit() 安全校验失败(理论上不可达,防御性)。
    • NoLiquidity() 价格预言机交易对储备为 0 时抛出。
    • PairTokenMismatch() 价格预言机配置错误时抛出。
  • 事件:Staked(user, tokenAmount, valueInUsdt)

3.3 claimStatic

  • 签名:function claimStatic() external returns (uint256 rewardAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:领取用户的静态收益。会先触发今日的等级奖池与销毁排行榜奖励分配,再按”上次领取时间到当前时间”的天数发放代币,同时按 10% × 最多 5 代向上分配加速奖励。
  • 参数:无。
  • 返回值:rewardAmount 实际发放的代币数量。
  • 错误:
    • NoActiveStake() 无有效质押时抛出。
    • NothingToClaim() 可领取天数为 0 或奖励 USDT 价值为 0 时抛出。

3.4 claimLevelRewards

  • 签名:function claimLevelRewards() external returns (uint256 levelAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:领取用户的等级奖池奖励(pendingLevelReward)。
  • 参数:无。
  • 返回值:levelAmount 实际发放的代币数量,无奖励时返回 0。

3.5 claimAccelerationRewards

  • 签名:function claimAccelerationRewards() external returns (uint256 accelerationAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:领取用户的加速奖励(pendingAcceleration)。
  • 参数:无。
  • 返回值:accelerationAmount 实际发放的代币数量,无奖励时返回 0。

3.6 claimDirectReward

  • 签名:function claimDirectReward() external returns (uint256 directAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:领取用户的直推奖励(pendingDirectReward)。
  • 参数:无。
  • 返回值:directAmount 实际发放的代币数量,无奖励时返回 0。

3.7 claimBurnRankReward

  • 签名:function claimBurnRankReward() external returns (uint256 burnRankAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:领取用户的销毁排行榜奖励(pendingBurnRankReward)。
  • 参数:无。
  • 返回值:burnRankAmount 实际发放的代币数量,无奖励时返回 0。

3.8 claimAll

  • 签名:function claimAll() external returns (uint256 staticAmount, uint256 levelAmount, uint256 accelerationAmount, uint256 directAmount, uint256 burnRankAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:一键领取所有奖励(静态 + 等级 + 加速 + 直推 + 销毁排行榜)。同时作为 receive() 函数的回调入口。
  • 参数:无。
  • 返回值:5 个实际发放的代币数量;若所有都为空则 revert NothingToClaim()

3.9 claimRewards(旧接口)

  • 签名:function claimRewards() external returns (uint256 totalAmount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:旧版合并接口,仅包含加速 + 等级 + 直推奖励,不包含静态收益。新版本推荐用 claimAll()
  • 参数:无。
  • 返回值:totalAmount 实际发放的代币数量,无奖励时 revert NothingToClaim()

3.10 distributeLevelRewards

  • 签名:function distributeLevelRewards() external
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:手动触发今日的等级奖池分配。同时若销毁排行榜奖励跨日未分配,会一并触发。
  • 参数:无。
  • 返回值:无。
  • 错误:AlreadyDistributed() 今日已分配过时抛出。

3.11 distributeBurnRankRewards

  • 签名:function distributeBurnRankRewards() external
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:手动触发今日的销毁排行榜奖励分配。同时若等级奖池跨日未分配,会一并触发。
  • 参数:无。
  • 返回值:无。
  • 错误:AlreadyDistributed() 今日已分配过时抛出。

3.12 burnRemainingPool

  • 签名:function burnRemainingPool() external returns (uint256 amount)
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:一次性操作,将合约当前所有质押代币余额转入销毁地址(burnAddress = 0x...dEaD)。仅当达到销毁时间(POOL_BURN_TIMESTAMP = 2028-06-01 UTC)且尚未销毁过时允许
  • 参数:无。
  • 返回值:amount 实际销毁的代币数量。
  • 错误:
    • NotConfigured() stakingToken == 0 时抛出。
    • AlreadyBurned() 已销毁过时抛出。
    • TooEarly() 未到 POOL_BURN_TIMESTAMP 时抛出。
  • 副作用:remainingPoolBurned = true 防止重复销毁。
  • 事件:RemainingPoolBurned(amount)

3.13 receive

  • 签名:receive() external payable
  • 权限:ANYONE(REENTRANCY_SAFE)
  • 用途:作为”一键领取所有奖励”的链上触发入口。要求 msg.value == 0,自动调用 _claimAll(msg.sender)
  • 错误:
    • BnbNotAccepted()msg.value != 0 时抛出。
    • NothingToClaim() 当用户没有任何奖励可领取时抛出。

4. 视图函数

4.1 静态收益查询

4.1.1 claimableStaticDays

  • 签名:function claimableStaticDays(address user) public view returns (uint256)
  • 用途:查询用户当前可领取的静态收益天数(向下取整)。仅在用户处于有效质押状态且尚未达到退出倍数时返回非零值。
  • 参数:
    • user:目标用户地址。
  • 返回值:uint256 可领取天数。

4.1.2 staticRewardValue

  • 签名:function staticRewardValue(address user) public view returns (uint256)
  • 用途:查询用户当前可领取的静态收益 USDT 价值。结果受退出倍数限制。
  • 参数:
    • user:目标用户地址。
  • 返回值:uint256 USDT 计价的奖励价值(USDT 自身精度)。

4.2 退出倍数与加速奖励

4.2.1 exitCapValue

  • 签名:function exitCapValue(address user) external view returns (uint256)
  • 用途:查询用户本周期的退出倍数价值上限,即 stakeValue * 200%
  • 参数:
    • user:目标用户地址。
  • 返回值:uint256 USDT 计价的退出倍数上限。

4.2.2 remainingStaticAccelerationValue

  • 签名:function remainingStaticAccelerationValue(address user) external view returns (uint256)
  • 用途:查询用户本周期剩余可分配的加速奖励价值(USDT 计),即”退出倍数上限 - 已分配”。
  • 参数:
    • user:目标用户地址。
  • 返回值:uint256 USDT 计价的剩余可分配加速奖励;返回 0 表示本周期已达退出倍数。

4.3 等级

4.3.1 currentLevel

  • 签名:function currentLevel(address user) public view returns (uint256 level)
  • 用途:实时计算用户当前的等级(根据团队业绩即时计算,可能尚未同步到 accounts[user].level)。

4.3.2 storedLevel

  • 签名:function storedLevel(address user) external view returns (uint256)
  • 用途:查询用户已存储的等级(上次同步时的结果)。

4.3.3 exactLevelCount

  • 签名:function exactLevelCount(uint256 level) external view returns (uint256)
  • 用途:查询某等级(L1~L5)的用户人数(仅统计该等级)。
  • 错误:BadLevel()level < 1 || level > 5 时抛出。

4.3.4 qualifiedLevelCount

  • 签名:function qualifiedLevelCount(uint256 level) external view returns (uint256 count)
  • 用途:查询某等级(L1~L5)及以上的总达标人数。例如 qualifiedLevelCount(2) 返回 L2+L3+L4+L5 的人数之和。
  • 错误:BadLevel()level < 1 || level > 5 时抛出。

4.3.5 currentDay

  • 签名:function currentDay() external view returns (uint256)

  • 用途:查询当前的”北京日”序号(实现为 (block.timestamp + 8 hours) / 1 days)。链下脚本可与 lastLevelDistributionDay / lastBurnRankDistributionDay 比较,判断是否需要触发每日分配。

  • 权限:无访问限制,纯查询。

  • 典型用法:

    1
    cast call $STAKING_PROXY "currentDay()(uint256)" --rpc-url bsc

4.3.6 needsDistribution

  • 签名:function needsDistribution() external view returns (bool needsLevelRewards, bool needsBurnRankRewards)

  • 用途:一次性返回”今日是否还需要触发等级奖池分配”以及”今日是否还需要触发销毁排行榜奖励分配”,即 _currentDay() > lastXxxDistributionDay 的两个布尔值。便于链下脚本(包括 cron / 前端)在发送交易前判断是否值得发送,避免无谓的 gas 浪费。

  • 权限:无访问限制,纯查询。

  • 典型用法:

    1
    cast call $STAKING_PROXY "needsDistribution()(bool,bool)" --rpc-url bsc

注:lastLevelDistributionDay / lastBurnRankDistributionDay 是 state 上声明的 public uint256,Solidity 自动生成同名 external view getter,可直接 cast call $STAKING_PROXY "lastLevelDistributionDay()(uint256)" / "lastBurnRankDistributionDay()(uint256)",无需额外添加 view 函数。

4.4 团队业绩

4.4.1 effectiveTeamValue

  • 签名:function effectiveTeamValue(address user) public view returns (uint256)
  • 用途:计算用户的有效团队业绩。规则:总业绩 - 最大单线业绩,防止通过单个大户下线达到等级。

4.4.2 effectiveDirectCount

  • 签名:function effectiveDirectCount(address user) public view returns (uint256 count)
  • 用途:计算用户的有效直推人数(只统计当前存在有效质押的下线)。用于加速奖励分配时校验是否满足”必须达到该层级的直推人数”条件。

4.5 用户列表

4.5.1 userCount

  • 签名:function userCount() external view returns (uint256)
  • 用途:查询系统注册过的用户总数。

4.5.2 getDirects

  • 签名:function getDirects(address user) external view returns (address[] memory)
  • 用途:查询用户的直接下级地址列表。

4.6 一键查询奖励

4.6.1 pendingRewards

  • 签名:function pendingRewards(address user) external view returns (uint256 staticTokenAmount, uint256 accelerationAmount, uint256 levelAmount, uint256 directAmount, uint256 burnRankAmount)
  • 用途:一键查询用户待领取的所有奖励。
  • 参数:
    • user:目标用户地址。
  • 返回值:5 个代币数量,分别对应静态收益(代币)、加速奖励、等级奖池、直推奖励、销毁排行榜奖励。

4.7 每日统计

4.7.1 todayBurned

  • 签名:function todayBurned() external view returns (uint256)
  • 用途:查询今天(北京时间)已销毁的代币总量。

4.7.2 todayLevelPoolTotal

  • 签名:function todayLevelPoolTotal() external view returns (uint256 total)
  • 用途:查询今天(北京时间)累积进入 5 个等级奖池的代币总量。

4.7.3 todayBurnRankPool

  • 签名:function todayBurnRankPool() external view returns (uint256)
  • 用途:查询今天(北京时间)累积进入销毁排行榜奖励池的代币总量。

4.8 销毁排行榜查询

4.8.1 dailyBurnOf

  • 签名:function dailyBurnOf(address user, uint256 day) external view returns (uint256)
  • 用途:查询指定用户在指定北京日的销毁量(用于排行榜判定)。
  • 参数:
    • user:目标用户地址。
    • day:北京日序号((block.timestamp + 8h) / 1 days)。

4.8.2 dailyTopRankCount

  • 签名:function dailyTopRankCount(uint256 day) external view returns (uint256)
  • 用途:查询指定北京日的销毁排行榜参与者数量(≤ BURN_RANK_TOP_N = 20)。

4.8.3 dailyTopRank

  • 签名:function dailyTopRank(uint256 day, uint256 index) external view returns (address user, uint256 amount)
  • 用途:查询指定北京日销毁排行榜第 index 名的用户与销毁量(index 从 0 开始)。
  • 错误:BadRankIndex()index >= BURN_RANK_TOP_N 时抛出。

4.8.4 getDailyTopRanks

  • 签名:function getDailyTopRanks(uint256 day) external view returns (address[20] memory users_, uint256[20] memory amounts_, uint256 count)
  • 用途:查询指定北京日销毁排行榜前 N 名的批量信息,用于链下前端一次性展示完整榜单。

4.9 等级奖池查询

4.9.1 currentLevelPoolTotal

  • 签名:function currentLevelPoolTotal() external view returns (uint256 total)
  • 用途:查询当前所有等级奖池中尚未分配的代币总量。

4.10 等级奖池与等级门槛(public 状态变量)

名称 类型 说明
levelThresholds uint256[5] public 5 个等级业绩门槛(USDT 计)
levelDepositBps uint256[5] public 5 个等级分配比例(BPS,合计 9%)
levelPools uint256[5] public 5 个等级待分配奖池余额
levelUserCounts uint256[6] public index 0 不用,index 1~5 记录各等级人数

4.11 价格预言

4.11.1 tokenToUsdt

  • 签名:function tokenToUsdt(uint256 tokenAmount) public view returns (uint256)
  • 用途:将质押代币数量换算为 USDT 价值。通过 PancakeSwap V2 单跳预言 stakingToken ↔ USDT,使用恒定乘积公式(忽略 0.3% 手续费)。
  • 依赖:stakingUsdtPair(GOAT27/USDT Pair)由 initialize 内部 PancakeLibrary.pairFor 自动算出。

4.11.2 usdtToToken

  • 签名:function usdtToToken(uint256 usdtAmount) public view returns (uint256)
  • 用途:反向换算:USDT → stakingToken(单跳)。

4.12 当前生效参数

名称 签名 说明
effectiveMinStakeValue function effectiveMinStakeValue() public view returns (uint256) 当前生效的最小质押价值,为 0 时回退到 MIN_STAKE_VALUE (100 ether USDT)
effectiveMaxStakeValue function effectiveMaxStakeValue() public view returns (uint256) 当前生效的最大质押价值,为 0 时回退到 MAX_STAKE_VALUE (11_000 ether USDT)
effectiveRewardInterval function effectiveRewardInterval() public view returns (uint256) 当前生效的静态收益领取间隔,为 0 时回退到 1 days

4.13 不可变量与公开状态

名称 类型 说明
stakingToken IERC20 public 质押代币(由 initialize 设置)
usdtToken IERC20 public USDT 计价(由 initialize 设置)
wbnbToken IERC20 public 已删除:新版改用 GOAT27/USDT 单跳预言,不再需要 WBNB
pricePair IPancakePair public 已删除
wbnbUsdtPair IPancakePair public 已删除
stakingUsdtPair IPancakePair public GOAT27/USDT Pancake V2 Pair(PancakeLibrary.pairFor 自动算出)
burnAddress address public 销毁地址(硬编码为 0x000000000000000000000000000000000000dEaD,不可配置)
burnRankPool uint256 public 销毁排行榜奖励池累积余额
lastLevelDistributionDay uint256 public 上次分配等级奖池的北京日
lastBurnRankDistributionDay uint256 public 上次分配销毁排行榜奖励的北京日
minStakeValue uint256 public 当前生效最小质押价值(USDT 计)
maxStakeValue uint256 public 当前生效最大质押价值(USDT 计)
rewardInterval uint256 public 当前生效静态收益领取间隔
launchedAt uint256 public 合约启动时间戳
remainingPoolBurned bool public 是否已执行过剩余池子销毁
burnedByDay(day) mapping(uint256 => uint256) public 按北京日索引的每日销毁量
dailyBurns(day, user) mapping(uint256 => mapping(address => uint256)) public 按北京日索引的每用户销毁量
topRankUsersByDay(day) mapping(uint256 => address[20]) public 按北京日索引的排行榜前 N 名地址
topRankAmountsByDay(day) mapping(uint256 => uint256[20]) public 按北京日索引的排行榜前 N 名销毁量
topRankCountByDay(day) mapping(uint256 => uint256) public 按北京日索引的排行榜实际参与人数
burnRankPoolByDay(day) mapping(uint256 => uint256) public 按北京日索引的排行榜奖励池累积数量
levelPoolsByDay(day) mapping(uint256 => uint256[5]) public 按北京日索引的等级奖池累积数量
accounts(user) mapping(address => Account) public 用户账户信息(详见 Account 结构体)
directs(user) mapping(address => address[]) public 直接下级地址列表
branchValue(user, child) mapping(address => mapping(address => uint256)) public 单线下线贡献的业绩
users(uint256) address[] public 全局用户列表

5. Account 结构体

字段 类型 说明
referrer address 推荐人地址
directCount uint256 直接下级数量
stakeAmount uint256 当前周期质押代币数量
stakeValue uint256 当前周期质押价值(USDT 计)
claimedStaticDays uint256 已领取静态收益的天数
startedAt uint256 当前周期开始时间戳
lastStaticClaimAt uint256 上次领取静态收益的时间戳
totalStakedAmount uint256 历史累计质押总量
totalClaimedAmount uint256 历史累计领取总量
pendingAcceleration uint256 待领取加速奖励
pendingLevelReward uint256 待领取等级奖池奖励
pendingDirectReward uint256 待领取直推奖励
pendingBurnRankReward uint256 待领取销毁排行榜奖励
totalTeamValue uint256 当前周期累计团队业绩(USDT 计)
staticAccelerationRewardValue uint256 当前周期已分配加速奖励价值(USDT 计)
level uint256 当前等级(0 表示未达 L1)
exists bool 账户是否存在
active bool 当前是否存在有效质押
teamActive bool 当前是否还计入团队业绩

6. UUPS 升级标准接口

合约使用 OpenZeppelin UUPSUpgradeable,提供以下标准接口:

函数 签名 说明
upgradeToAndCall function upgradeToAndCall(address newImplementation, bytes data) external payable 由 owner 调用以升级到新实现合约(升级授权见 _authorizeUpgrade,仅 owner 可触发)
proxiableUUID function proxiableUUID() external view returns (bytes32) 返回实现合约 UUID,ERC1967 标准

推荐升级流程(完整说明见 docs/deploy/部署流程.md#步骤-53–升级已有代理):

1
2
3
4
# 1. 部署新实现合约(留空 NEW_IMPL 时由脚本现场部署)
# 2. 由 proxy owner 执行 UUPS 升级并自动验证
PROXY=$STAKING_PROXY forge script script/Goat27/UpgradeStaking.s.sol \
--rpc-url bsc --broadcast

脚本会自动做的事:

  • 通过 ERC1967 槽位(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)读取并打印旧实现地址
  • 部署新实现(或读取 env 传入的)
  • 调用 upgradeToAndCall(newImpl, "") — 空 data 表示本次不执行额外初始化(适合纯增量升级,例如本次新增 currentDay() / needsDistribution() view)
  • 校验 ERC1967 槽位已切到新地址
  • 校验 storage 保持不变:owner / stakingToken / stakingUsdtPair 都非零且与升级前一致
  • 验证新 view 函数可调用
  • 把升级记录写到 ./staking-upgrade.json

⚠️ 升级时严格遵守 storage layout:只能在已有 slot 后追加新状态变量,不能修改现有变量的顺序或类型,不能缩小 __gap 槽位(当前为 50)。已有初始化的字段不能再赋默认值,必须通过 reinitializer(N) 新建初始化函数。

⚠️ 代币 / Pair / 销毁地址不可再修改:stakingToken / usdtToken / stakingUsdtPair / burnAddress 都已固化到 initialize 内部,owner 无法在升级后调整。如需更换,只能部署全新的代理。

💡 本次升级属于纯增量:新增 currentDay() / needsDistribution() 只把 _currentDay 从 internal 暴露成 external、加一个 helper,不动 storage、不需要 reinitializer。


7. Ownable 标准函数

合约继承 OpenZeppelin OwnableUpgradeable,提供以下标准接口(节选):

函数 签名 说明
owner function owner() external view returns (address) 当前合约所有者
transferOwnership function transferOwnership(address newOwner) external 发起所有权转移(仅 owner)
renounceOwnership function renounceOwnership() external 放弃所有权(仅 owner)
acceptOwnership function acceptOwnership() external 待定 owner 调用以接受所有权

8. 事件索引

事件 签名 触发时机
Initialized event Initialized(uint64 version) 初始化完成(OZ 自带)
Configured event Configured(address stakingToken, address usdtToken, address stakingUsdtPair, address burnAddress) initialize 完成时同时发出(沿用 Configured 事件名,便于链下索引;Pair 由 PancakeLibrary.pairFor 自动算出,burnAddress 固定为 0x...dEaD)
ReferrerBound event ReferrerBound(address indexed user, address indexed referrer) 用户绑定推荐人
Staked event Staked(address indexed user, uint256 tokenAmount, uint256 valueInUsdt) 用户完成质押
StaticClaimed event StaticClaimed(address indexed user, uint256 daysClaimed, uint256 rewardAmount) 用户领取静态收益
AccelerationQueued event AccelerationQueued(address indexed from, address indexed to, uint256 rewardAmount) 加速奖励被加入上级待领取余额
RewardsClaimed event RewardsClaimed(address indexed user, uint256 accelerationAmount, uint256 levelAmount, uint256 directAmount, uint256 burnRankAmount) 用户领取奖励(任意类型)
BurnRankRewardsDistributed event BurnRankRewardsDistributed(uint256 indexed dayIndex, uint256 poolAmount, uint256 participantCount) 销毁排行榜每日奖励分配完成
StakeExpired event StakeExpired(address indexed user, uint256 valueInUsdt) 用户本周期达到退出倍数
LevelRewardsDistributed event LevelRewardsDistributed(uint256 indexed dayIndex) 等级奖池每日分配完成
StakeValueLimitsUpdated event StakeValueLimitsUpdated(uint256 minStakeValue, uint256 maxStakeValue) 质押价值上下限更新
LevelThresholdsUpdated event LevelThresholdsUpdated(uint256[5] levelThresholds) 等级门槛更新
RewardIntervalUpdated event RewardIntervalUpdated(uint256 rewardInterval) 静态收益领取间隔更新
RemainingPoolBurned event RemainingPoolBurned(uint256 amount) 剩余奖池销毁
LevelUpdated event LevelUpdated(address indexed user, uint256 oldLevel, uint256 newLevel) 用户等级变更
ActiveCycleRewardValueReset event ActiveCycleRewardValueReset(address indexed user) owner 重置用户本周期加速奖励价值

9. 错误索引

错误 触发条件
ZeroAddress() 关键参数为零地址(initialize 时 owner / stakingToken / usdtToken 任一为 0)
NotConfigured() stakingToken == 0,合约未初始化或 initialize 失败
AmountZero() 质押数量为 0
StakingStopped() 已到 STAKING_STOP_TIMESTAMP
ActiveStakeExists() 用户已有有效质押
BelowMin() USDT 价值 < 最小质押价值
AboveMax() USDT 价值 > 最大质押价值
BadSplit() 分配比例求和超过总额(防御性)
NoActiveStake() 无有效质押
CycleStarted() 周期已开始
CycleClaimed() 本周期已领取
MinZero() setStakeValueLimitsmin == 0
BadLimits() setStakeValueLimitsmax < min
ThresholdZero() setLevelThresholdsthresholds[0] == 0
BadThresholds() setLevelThresholds 时门槛不单调递增
IntervalZero() setRewardIntervalrewardInterval == 0
NothingToClaim() 无任何奖励可领取
AlreadyBurned() 已销毁过剩余池子
TooEarly() 未到 POOL_BURN_TIMESTAMP
NoLiquidity() 价格预言机交易对储备为 0
PairTokenMismatch() 价格预言机配置错误
ReferrerExists() 已绑定过推荐人
ReferrerZero() 推荐人为零地址
ReferrerSelf() 推荐人为自己
BadLevel() `level < 1
BnbNotAccepted() receive()msg.value != 0
BadRankIndex() 销毁排行榜 index >= BURN_RANK_TOP_N
AlreadyDistributed() 今日已分配过该奖励
ReentrancyGuardReentrantCall() 重入调用(OZ 自带)
InvalidInitialization() 重复初始化(OZ 自带)

⚠️ PairMismatch() 已在旧版本移除(用于 configure() 校验交易对 token,现由 PancakeLibrary.pairFor 自动算 Pair,无需人工校验)。


10. 关键时间常量

常量 UTC 时刻 说明
STATIC_RATE_DROP_TIMESTAMP 1801872000 2027-02-06 00:00:00 静态利率从 1%/天降为 0.5%/天
STAKING_STOP_TIMESTAMP 1830297600 2028-01-01 00:00:00 停止接受新质押
POOL_BURN_TIMESTAMP 1843430400 2028-06-01 00:00:00 允许销毁剩余奖池

北京日计算:(block.timestamp + 8 hours) / 1 days,即北京时间 00:00 切换日。


11. 质押分配流程示例

假设用户 Alice 质押 1000 GOAT27(USDT 价值 = 1000 USDT,推荐人 = Bob):

分配项 比例 数量 接收方
销毁 85% 850 GOAT27 burnAddress (0x…dEaD,硬编码)
销毁排行榜 1% 10 GOAT27 burnRankPool(明日分配)
直推奖励 5% 50 GOAT27 accounts[Bob].pendingDirectReward
L1 奖池 3% 30 GOAT27 levelPools[0]
L2 奖池 2% 20 GOAT27 levelPools[1]
L3 奖池 2% 20 GOAT27 levelPools[2]
L4 奖池 1% 10 GOAT27 levelPools[3]
L5 奖池 1% 10 GOAT27 levelPools[4]
合计 100% 1000 GOAT27

副作用:

  • accounts[Alice].stakeValue = 1000e18 USDT
  • accounts[Bob].totalTeamValue += 1000e18(沿链继续向上传播)
  • accounts[Bob].branchValue[Alice] += 1000e18
  • _syncLevel(Bob) 检查是否晋升

12. 销毁排行榜奖励流程示例

假设某日有 3 名用户销毁:Alice 500、Bob 300、Carol 200(总计 1000),且销毁排行榜池累积 100 GOAT27:

用户 当日销毁量 占比 奖励(100 × stake / 1000)
Alice 500 50% 50 GOAT27
Bob 300 30% 30 GOAT27
Carol 200 20% 20 GOAT27
合计 1000 100% 100 GOAT27

排名按每日累计销毁量降序排列,仅取前 20 名参与分配;若不足 20 名则按实际数量分配。


13. 静态收益与退出倍数示例

假设 Alice 在 2026-06-01 质押 1000 USDT 等值的 GOAT27,在 2026-09-01(90 天后)领取:

  • 90 天均在利率下降前(2027-02-06 前),每日利率 = 1%
  • 累计静态收益 USDT 价值 = 1000 × 0.01 × 90 = 900 USDT(已接近退出倍数 200% = 2000 USDT)
  • 退出倍数价值上限 = 1000 × 200% = 2000 USDT,剩余可分配加速奖励 = 2000 - 900 = 1100 USDT

加速奖励沿链向上 5 代分配:每代 10%,因此理论可向上分配 900 × 50% = 450 USDT 给 5 个上级。


14. 部署后必做

  1. 通过 ERC1967 代理调用 initialize(initialOwner, stakingToken, usdtToken)(由部署脚本 DeployStakingProxy.s.sol 完成):
    • stakingToken / usdtToken 必须已经部署,且质押代币已在 Pancake V2 上和 USDT 形成 Pair(参见部署流程 Step 3)。
    • Pair 地址由合约内部 PancakeLibrary.pairFor(stakingToken, usdtToken) 自动计算,部署者无需传入
    • burnAddress 硬编码为 0x000000000000000000000000000000000000dEaD,部署者无法自定义
    • 部署脚本示例:
      1
      2
      3
      4
      forge script script/Goat27/DeployStakingProxy.s.sol \
      --rpc-url <network> \
      --broadcast \
      --sig "run()" \
      必填环境变量:PRIVATE_KEYSTAKING_TOKENUSDT_TOKEN;可选:INITIAL_OWNERIMPLEMENTATION
  2. owner 调用 configure(...) 配置预言机(已废弃,合并入 initialize,无需二次配置)。
  3. 用户先调用 bindReferrer(referrer) 绑定推荐人(若不绑定,5% 直推奖励会销毁)。
  4. 用户再调用 stake(amount) 完成质押。
  5. owner 推荐把代理地址加入 Goat27 的双白名单(addToWhitelist),避免被 5% 税污染。