fix(p0-12b): per-xPNTs community-controlled facilitator whitelist#110
Conversation
代码审查警告 — 三层防御架构方向正确,但存在 CRITICAL 运营风险和 HIGH 代码问题 CRITICAL(运营)— PR 描述提到"community multisig only",但合约层面没有验证
HIGH — require(facilitator != communityOwner, "communityOwner cannot be facilitator");HIGH — MEDIUM — 缺少 |
|
已在 核心警告: |
HIGH 修复:阻止 communityOwner 自我添加为 facilitator问题(PR 审核意见): 修复内容(commit
测试结果:10/10 PASS(含新增用例)。
|
Option A 实现:addApprovedFacilitator 自动同步 autoApprovedSpenders根据架构决策,已实现 auto-sync 方案(commit 最新推送): 合约变更
测试变更
测试结果:12/12 PASS |
撤销 auto-sync,恢复两个 mapping 独立设计架构纠正: 内部的 调用方是 SuperPaymaster 合约本身(msg.sender = SP),而非 facilitator。工厂部署时已执行 ,因此 facilitator 完全不需要进入 。两个 mapping 的职责是正交的:
合约变更: / 移除 auto-sync 逻辑,只写 ,NatSpec 更新说明正交关系。 测试变更:移除 auto-sync 断言测试,新增 测试结果:11/11 PASS |
fanhousanbu
left a comment
There was a problem hiding this comment.
MEDIUM(CEI 模式): settleX402PaymentDirect() 里,_validateX402AndComputeFee() 内部先写 nonce(x402SettlementNonces[key] = true),之后才做 isXPNTs 和 approvedFacilitators 两个检查。行为上安全(revert 会回滚 nonce 写入),但违反 CEI——未来维护者若在两个检查之间插入 external call 会引入 re-entrancy 风险。请在该段加注释说明 nonce 写入依赖 revert atomicity 而非代码顺序。不阻断。
✅ Approved
210a7f6 to
0bee645
Compare
c16cddb to
b02e1b3
Compare
0bee645 to
e1afdbc
Compare
b02e1b3 to
25c8aef
Compare
|
All contributors have signed the CLA ✍️ ✅ |
25c8aef to
57c01cf
Compare
|
@fanhousanbu 再次 rebase(#109 P0-12a 已合并到 main,跳过该 commit)。代码内容不变,请 approve。 |
fanhousanbu
left a comment
There was a problem hiding this comment.
✅ Approved(force-push re-review)
P0-12b,commit authored dates 与历次 review 一致,纯 rebase,无新逻辑。
…dComputeFee Nonce is intentionally consumed before the asset whitelist check so that replay is prevented regardless of the failure reason. Document the trade-off: a caller with a non-xPNTs asset loses the nonce and must retry with a fresh one; well-formed SDK clients always validate assets first.
The comment claimed a caller loses their nonce if they supply a wrong asset. In reality EVM revert semantics roll back the storage write, so the nonce is NOT consumed on failure. The correct behavior is already documented in the settleX402PaymentDirect NatSpec; the contradicting inline comment is simply deleted.
P0-12b (D4): even after P0-12a restricts `settleX402PaymentDirect` to
xPNTs assets, a single global facilitator (e.g. AAStar's default) is
still implicitly trusted by every community's xPNTs. A compromise of
that one key would blast across all communities at once. Per D4, each
xPNTs token now carries its own community-controlled
`approvedFacilitators` whitelist.
Defense:
- xPNTsToken.sol:
- new `mapping(address => bool) public approvedFacilitators` storage
slot. Distinct from `autoApprovedSpenders`: the latter is the
ERC20 transferFrom firewall ("no approve needed"), the former
gates `settleX402PaymentDirect` invocation. The two are
intentionally orthogonal — both must hold for a Direct settle to
succeed.
- `addApprovedFacilitator(address)` / `removeApprovedFacilitator
(address)` — `onlyCommunityOwner` (matches the existing
`removeAutoApprovedSpender` access pattern; community owner is
the policy authority).
- new events `FacilitatorApproved` / `FacilitatorRemoved`.
- per D4, factory does NOT auto-add AAStar's facilitator at
deploy. Communities must explicitly add facilitators after
deployment (runbook concern, not contract default). Existing
`deployxPNTsToken` ABI is unchanged — no breaking SDK impact.
- IxPNTsToken adds `approvedFacilitators(address) returns (bool)`.
- SuperPaymaster.settleX402PaymentDirect: after the P0-12a asset
gate passes, also enforce
`IxPNTsToken(asset).approvedFacilitators(msg.sender)`. Reverts
`Unauthorized` (reuses existing error). Without this, a compromised
facilitator that still holds ROLE_PAYMASTER_SUPER could touch
every community's xPNTs even after that community has tried to
blacklist them upstream.
Trust-model summary (per threat-model.md §3.1): facilitator is
BOUNDED TRUST at the SP layer; this whitelist binds the bound to
the per-community policy surface so a facilitator compromise is
contained to communities that have explicitly opted in.
Storage layout: xPNTsToken adds one mapping (clones share storage
layout; `approvedFacilitators` appends after `autoApprovedSpenders`,
which is safe for clones since storage is fresh at clone time).
SuperPaymaster has no new storage. UUPS upgrade safe.
Tests:
- contracts/test/v3/X402Direct_FacilitatorWhitelist.t.sol (new, 9
tests):
- test_SettleDirect_RevertsForUnapprovedFacilitator
- test_SettleDirect_AllowsApprovedFacilitator
- test_SettleDirect_PerCommunityIsolation (A's approval doesn't
leak to B's xPNTs — the core blast-radius property)
- test_AddApprovedFacilitator_OnlyCommunity (SP owner cannot, only
xPNTs communityOwner can)
- test_AddApprovedFacilitator_RevertsZeroAddress
- test_AddApprovedFacilitator_EmitsEvent
- test_RemoveApprovedFacilitator_RevokesAccess (instant revoke;
next settle reverts even with same allowance state)
- test_RemoveApprovedFacilitator_OnlyCommunity
- test_DefaultEmptyApprovedFacilitators (no auto-add at deploy)
- contracts/test/v3/X402Direct_AssetWhitelist.t.sol: helper updated
to also `addApprovedFacilitator(operator)` so its happy path still
exercises Direct end-to-end. All 6 P0-12a tests still green.
- contracts/test/v3/SuperPaymasterV5Features.t.sol: MockDirectToken
now exposes `approvedFacilitators` + `setApprovedFacilitator`;
setUp wires it for operator1. All 36 V5 features tests still green.
Refs: P0-12b in docs/security/2026-04-26-p0-prelaunch.md, D4 in
docs/security/2026-04-26-decision-records.md
…st functions Document that communityOwner MUST be a multisig wallet. A compromised single-EOA owner can add arbitrary facilitators enabling unauthorized token extraction. Add the same warning to removeApprovedFacilitator and a brief note to transferCommunityOwnership referencing the security rationale.
communityOwner calling addApprovedFacilitator(communityOwner) would let them act as both administrator and settlement facilitator — a conflict of interest that bypasses separation-of-duties. Add a check that reverts with Unauthorized(facilitator) when facilitator == communityOwner, and cover it with test_AddApprovedFacilitator_RevertsIfCommunityOwner.
…Spenders are orthogonal
…ion) X402Direct_AssetWhitelist tests had operator double as both communityOwner and facilitator. After 1f838a2 (block self-add), that pattern fails. Split: community deploys + owns; operator is the registered facilitator approved by community.
57c01cf to
26a6d8f
Compare
|
@fanhousanbu 最后一次 rebase(#112 P0-1 BLS rewrite 已合并到 main,一个 NatDoc 注释冲突已解决)。代码逻辑不变。请 approve。 |
Waiting on PR #118 for CI to passCI failing with EIP-170 size overflow in PR #118 fixes the root cause. Once it merges to main, please rebase this branch and CI should go green. @fanhousanbu re-review needed after rebase — the previous approval was dismissed by force-push. |
…d with SDK migration guide Remove from on-chain ABI to gain ~230B headroom for P0 PRs #110/#113/#114: - isChainlinkStale() → SDK reads cachedPrice().updatedAt + priceStalenessThreshold - getAvailableCredit() → SDK computes off-chain from pendingDebts + registry.getCreditLimit - getSlashHistory() → use getSlashCount + getSlashRecord(operator, index) loop - getLatestSlash() → use getSlashRecord(operator, count - 1) Add getSlashRecord(address,uint256) returns (SlashRecord) to work around Solidity mapping-to-struct-array tuple limitation. Net cost: ~30B. Net savings: ~200B. Update test files to use new API: - EmergencyBreakGlass.t.sol: replace isChainlinkStale() with local _chainlinkStale() - SuperPaymasterV3Query.t.sol: full rewrite using getSlashCount + getSlashRecord - SuperPaymasterV3_Admin.t.sol: use getSlashRecord() instead of removed functions Docs: - API_SUPERPAYMASTER.md: mark removed functions with migration paths; update context encoding (6→5 field), add emergencySetPrice/cancel/execute, add updatePriceDVT chainlinkRecovered param; add⚠️ SDK MAINTAINER banner at top - eip170-impact-analysis: append Section 7 with SDK migration code samples, off-chain indexing guidance, and ABI update instructions All 617 tests pass.
Rebase artifact: identical mapping(address => bool) approvedFacilitators appeared at lines 76 and 96. Compiler error 2333 (Identifier already declared). Remove the duplicate block (lines 86-96) introduced during rebase.
26a6d8f to
f906725
Compare
…l 607 tests pass Size reductions in SuperPaymaster.sol: - Remove dead `useRealtime` branch from `_calculateAPNTsAmount` (cache-only path) - Remove `oracleDecimals` storage var (Chainlink always returns 8, hardcode it) - Remove unused `bytes memory` param from internal `_slash()` - Drop try/catch in `configureOperator` factory check (direct call, removes FactoryVerificationFailed error) - Remove `xPNTsAmount` from validatePaymasterUserOp context encoding (unused in postOp) - Make DRYRUN_* constants internal (no public getter bytecode) Test fixes: - DryRunValidation.t.sol: replace `paymaster.DRYRUN_*()` calls with hardcoded bytes32 values - xPNTs_SpenderRateLimit.t.sol: correct expected default cap (50_000 ether, not 15_000 ether) - SuperPaymasterHardenVerification.t.sol, SuperPaymasterV3.t.sol, SuperPaymaster_BurnRestore.t.sol: update context encode/decode to 5-field format (token, user, initialAPNTs, userOpHash, operator)
…atSpec Follow-up to EIP-170 fix: add syncStakeFromStaking/getEffectiveStake stubs to MockRegistryDR, MockRegistryReconstruct, MockRegistryDVT, MockRegistryToggleableBLS, MockRegistryUnit, MockRegistryToggleable, and MockSPRegistry — these were missing after P0-2 extended IRegistry, causing "should be marked abstract" compile errors. Also update PriceCache_FutureTimestamp.t.sol to use the 4-arg updatePriceDVT() signature (chainlinkRecovered param added in P0-10), and fix NatSpec "@$0.02/xPNTs" dollar-sign tag in xPNTs_SpenderRateLimit.t.sol. 605/605 tests pass, SuperPaymaster = 24,537B (39B under EIP-170).
f906725 to
40c145d
Compare
fanhousanbu
left a comment
There was a problem hiding this comment.
✅ Approved
P0-12b 核心逻辑不变。新增内容:
- NatSpec 补全 multisig 安全说明(addApprovedFacilitator / removeApprovedFacilitator / transferCommunityOwnership)
- duplicate mapping 声明清除
- EIP-170 rebase(同 #118 已 approve 内容)
- 测试补全 facilitator/communityOwner 角色分离
fanhousanbu
left a comment
There was a problem hiding this comment.
✅ Approved — P0-12b 逻辑不变,新增 multisig NatSpec 安全说明,duplicate mapping 清除,EIP-170 rebase 内容同 #118。
Base 是 `fix/p0-12a-x402-asset-whitelist`(PR #N+5)。完整 stack: main → P0-13 → P0-12a → P0-12b。GitHub 只展示这条 commit 的 delta。
P0-12b (D4 新设计)
D4 决策:每个 xPNTs 由社区 multisig 指定 trusted facilitators。当前 `autoApprovedSpenders` 是单一全局映射 —— 跨社区 blast radius 太大。社区 A 不应被强迫 trust 社区 B 的 facilitator。
Defense
`autoApprovedSpenders` 与 `approvedFacilitators` 是不同概念,两者并存 —— 前者是 ERC20 transfer firewall,后者是 settle 调用权限白名单。
D4 用户决策:社区部署默认不自动 add AAStar facilitator —— community 自己显式 add。AAStar 提供一个可选默认。
Tests
新测试(spender 不在白名单 revert / 在白名单接受 / add/remove only community / settle 强制 `isXPNTs && approvedFacilitator`)+ 现有不破坏
Spec
`docs/security/2026-04-26-p0-prelaunch.md` §3 P0-12b
`docs/security/2026-04-26-decision-records.md` D4
`docs/deployment/facilitator-deployment.md`(PR #98)—— 部署后社区 add facilitator 的 runbook