feat: trust gate hook — on-chain trust scoring for job lifecycle gating#9
feat: trust gate hook — on-chain trust scoring for job lifecycle gating#9rnwy wants to merge 9 commits intoerc-8183:mainfrom
Conversation
|
Yes, great work on #6! Complementary to it: different signal, different coverage, same hook interface.
Composable by design. A relying party can run both to check performance and trust independently. |
|
Fair correction on the sybil row — updated, thanks. The "safe to transact with" vs "worth transacting with" framing is exactly right. EvaluatorRegistry and dynamic fees are interesting additions to the hook pattern — value-based tiers especially. All four issuers verified in Douglas's reference verifier — good foundation to build on. |
|
Thanks for the PR! As we're building ERC-8183 as an open standard, I'd suggest keeping the trust oracle interface generic rather than locked to RNWY specifically. Could you extract an ITrustOracle interface that covers the core functions, then have RNWY implement that interface as your provider? This way other trust providers can plug in too, and yours becomes the reference implementation. Same idea as what we're discussing with PR #6 — both can implement the same interface with different data sources. Let me know if this works! |
…interface) feat: add TrustGateHook (provider-agnostic, imports IRNWYTrustOracle interface)
|
Thanks for the suggestion! We extracted an While working on this, we looked at the
These answer different questions at different levels of the stack: address reputation vs. agent identity trust. A shared interface would need to be so abstract it loses the type safety that makes each one useful. Our suggestion: both hooks accept an oracle address at construction and talk to their respective interfaces. The hooks are provider-agnostic; the oracle interfaces reflect the real differences in how trust data is keyed. A relying party can run both hooks to get address-level and agent-level signals independently. Happy to discuss if you see a different path — this is a great standard to get right. |
|
Hey @rnwy, @JhiNResH Trust gating is a useful direction, but I do not think we should end up with parallel trust-hook paths that stay separate forever. My preference would be to keep the trust direction neutral at the interface and hook level, remove as much provider-specific framing as possible, and coordinate with #9 so we end up with one canonical trust hook direction, or at least a clearly aligned split where providers implement the same neutral pattern. If the two of you think the right answer is wallet-risk trust hook plus agent-quality trust hook, that is fine, but I would like you to sort that boundary out explicitly together rather than both landing overlapping trust primitives with different framing. So I would not merge this as-is yet. Can we both work togeher to merge the 2 PRs? If you both think there are differences, lets chat! |
|
Thanks @psmiratisu: understood on not merging as-is. We agree the right outcome is a shared interface at the hook layer. The framing we're working toward: both hooks implement the same neutral pattern — We'll reach out to @JhiNResH directly to coordinate and come back with a concrete joint proposal within the next few days. |
|
@psmiratisu Have proposed a shared interface with @JhiNResH (isTrusted(address participant, uint8 threshold) returns (bool)). @JhiNResH when you have a cycle, happy to iterate on the signature or jump on a call in case our email went to spam. Goal is one canonical trust hook shape that both our oracles can implement. |
JhiNResH
left a comment
There was a problem hiding this comment.
Security Review — CHANGES REQUIRED
Reviewed against BaseERC8183Hook.sol (the canonical hook base in this repo). Two blocking issues found.
[CRITICAL] FUND_SEL selector is wrong — client trust check silently bypassed
// TrustGateHook.sol:41 — WRONG
bytes4 public constant FUND_SEL = bytes4(keccak256("fund(uint256,bytes)"));
// BaseERC8183Hook.sol — CORRECT
bytes4 private constant SEL_FUND = bytes4(keccak256("fund(uint256,uint256,bytes)"));AgenticCommerce.fund takes three parameters (uint256 jobId, uint256 amount, bytes optParams). The hook computes the hash of a two-parameter signature, so selector == FUND_SEL is always false. The beforeAction(fund) branch is dead code. Anyone can fund a job regardless of trust score.
The data encoding for fund is correct (abi.encode(caller, optParams) → abi.decode(data, (address, bytes))). Only the selector string needs fixing:
bytes4 public constant FUND_SEL = bytes4(keccak256("fund(uint256,uint256,bytes)"));[HIGH] No caller authentication on beforeAction / afterAction
BaseERC8183Hook implements an onlyERC8183(jobId) modifier that validates msg.sender against the ACP core address (or the job's registered hook). TrustGateHook skips this entirely — both hook entry points are open to any caller.
Consequences:
- Anyone can emit
TrustGatedandOutcomeRecordedwith arbitrary values, poisoning off-chain indexers - Anyone can trigger oracle reads against arbitrary wallet addresses
Fix: store erc8183Contract as immutable in constructor and add the caller check (same pattern as BaseERC8183Hook).
[MEDIUM] Admin mutations emit no events
setAgentId, setThreshold, and setOracle make no emit. Oracle swaps are invisible on-chain. setOracle also has no zero-address guard and no timelock — a compromised owner key can atomically replace the oracle with one that approves any address.
Minimum: add OracleUpdated, ThresholdUpdated, AgentIdSet events + require(oracle_ != address(0)).
[MEDIUM] agentId == 0 ambiguous sentinel
agentIds[unregisteredWallet] returns 0 by default. If RNWY assigns agentId 0 to any real agent, they are permanently locked out with a misleading NoAgentId error. Needs documentation or a separate existence flag.
[LOW] Other issues
- Constructor: no zero-address check on
oracle_parameter threshold = 0: silently disables the gate; no lower-bound validation- README: links to
RNWYTrustGateHook.solbut the file isTrustGateHook.sol - No tests: gate is untested for the fund path (which is also broken by issue #1)
Full report: available on request. Happy to discuss any of these — the oracle interface design and the submit gate are both solid; the CRITICAL and HIGH issues are mechanical fixes.
- Inherit BaseERC8183Hook for correct selector routing, data decoding, and onlyERC8183 caller authentication - Add registered mapping so agentId = 0 is a valid registered value - Add AgentIdSet, ThresholdUpdated, OracleUpdated admin events - Add zero-address guards on constructor and setOracle - Document wallet-risk vs. agent-quality trust boundary in header Addresses review feedback from @JhiNResH in erc-8183#32 and coordination request from @psmiratisu.
Covers four paths through beforeAction(fund): - high-trust client passes - low-trust client reverts with BelowThreshold - unregistered caller reverts with NoAgentId - unauthorized msg.sender reverts via BaseERC8183Hook.onlyERC8183 Includes MockRNWYTrustOracle for deterministic scoring in tests.
|
@psmiratisu, rebased per your direction. Canonical base adopted. Admin events added.
Fund-path tests added. Trust boundary documented. Header block on the contract sets out the wallet-risk vs. agent-quality split you raised as an acceptable outcome. This hook gates on agent-quality signals (registered agents, On the shared hook-level interface. I proposed Ready for your review. |
Summary
Adds
TrustGateHook— a provider-agnosticIACPHookimplementation that gates ERC-8183 job lifecycle transitions by on-chain trust score, reading from any oracle that implementsIRNWYTrustOracle.New Files
contracts/interfaces/IRNWYTrustOracle.solExtracted interface for agent-identity-based trust oracles. Lookup key is
(agentId, chainId, registry)— supports multi-chain, multi-registry agent identity resolution.Four functions:
getScore(),hasScore(),meetsThreshold(),agentCount().contracts/hooks/TrustGateHook.solIACPHook that reads from any
IRNWYTrustOracleimplementation to gate participants by trust score:beforeAction(fund)— checks client trust score, reverts if below thresholdbeforeAction(submit)— checks provider trust score, reverts if below thresholdafterAction(complete/reject)— emits outcome events (never reverts)The hook maps wallet addresses to agent IDs, then calls
meetsThreshold()on the oracle. The oracle does all scoring; the hook is a gate, not a judge.Reference Implementation
The RNWY Trust Oracle on Base mainnet implements
IRNWYTrustOraclewith 138,000+ agent scores covering ERC-8004, Olas, and Virtuals across 11 chains.0xD5fdccD492bB5568bC7aeB1f1E888e0BbA6276f4(source-verified via Sourcify)Security
Uses
abi.decodethroughout — no raw assembly, no delegatecall. Interface extraction is a structural refactor; zero logic changes from the original submission.