aere-contracts/contracts/erc8004/AereReputationRegistry8004.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

201 lines
11 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "./IERC8004.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/// @dev The subset of the LIVE, deployed AereAIReputation (chain 2800, 0x781e…feBF) this adapter
/// uses. Reputation there is keyed by (operator address, bytes32 agentId): `attest` is gated
/// to a registered attestor set fixed at deploy, and `scoreOf` / `statsOf` are public reads.
interface IAereAIReputationLive {
function attest(address operator, bytes32 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceUri)
external;
function scoreOf(address operator, bytes32 agentId) external view returns (uint256);
function statsOf(address operator, bytes32 agentId)
external
view
returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed);
function isAttestor(address who) external view returns (bool);
}
/// @dev The one Identity-adapter read used to resolve an ERC-8004 agentId to its Aere operator
/// (the agent's controller), which is the operator address the live reputation is keyed by.
interface IAereIdentity8004 {
function isRegistered(uint256 agentId) external view returns (bool);
function getAgent(uint256 agentId)
external
view
returns (IERC8004Identity.AgentInfo memory);
}
/**
* @title AereReputationRegistry8004, an ERC-8004 Reputation adapter over the deployed AereAIReputation
*
* @notice Exposes the ERC-8004 Reputation surface (attest / read feedback) over Aere Network's
* ALREADY-DEPLOYED composable reputation primitive, AereAIReputation. It does NOT redeploy
* or modify the live reputation contract.
*
* READ (adapts the deployed contract directly). `getScore` and `getStats` map an ERC-8004
* agentId to the live reputation's (operator, agentId) key and read `scoreOf` / `statsOf`
* straight from the deployed AereAIReputation. This works against the currently-live
* instance as-is, with no change to it.
*
* WRITE (forwards feedback to the live contract). `giveFeedback` maps an ERC-8004 feedback
* signal to the live `attest(operator, agentId, delta, evidenceHash, uri)` and forwards it.
*
* @dev KEY MAPPING. The live reputation is keyed by (operator address, bytes32 agentId). This
* adapter derives:
* operator = the agent's current controller, resolved from the Identity adapter.
* agentKey = bytes32(agentId) (the ERC-8004 / DID agent id, left-padded).
* So an ERC-8004 read/write addresses exactly one live reputation slot, deterministically.
*
* @dev HONEST WRITE CONSTRAINT. AereAIReputation's attestor set is fixed at its deploy and has
* no add path. For `giveFeedback` to land in the live score, THIS adapter must be a
* registered attestor on the reputation instance it is constructed with. The currently-live
* AereAIReputation lists the Foundation attestor only (immutable), so pairing this adapter
* for WRITE-THROUGH is a Foundation governance decision, external to this contract and
* founder-gated. [VERIFY: on mainnet, register this adapter as an attestor before relying on
* giveFeedback, or have the Foundation attestor call the live attest directly.] The READ
* path needs no such authorization and works against the live instance today.
*
* @dev [VERIFY: confirm against the finalized ERC-8004 interface] ERC-8004 reputation feedback in
* later drafts carries a richer payload (score, tag, response URI). Here the feedback signal
* is normalized to the live model's delta in {-1, 0, +1} (+1 positive, -1 dispute, 0
* neutral) so no information is invented beyond what the deployed contract records.
*
* HONEST SCOPE. Application-layer reputation only. Does NOT change AERE consensus, holds no
* funds.
*
* @dev CALLER GATE (feedback authorization). Because every caller collapses into ONE on-chain
* attestor identity (this adapter) on the live AereAIReputation, an ungated write path would
* let ANY address inflate or tank any agent's reputation, defeating the live contract's
* curated-attestor model and its (attestor, evidenceHash) dedup. To mirror that curated model,
* `giveFeedback` is gated to an owner-managed allowlist of authorized feedback authors
* (`isAuthorizedFeedbackAuthor`). The owner curates WHO may submit feedback; it CANNOT set a
* reputation value directly, cannot bypass the {-1, 0, +1} delta clamp every author is held to,
* and holds no funds. This is the least-surprising design that closes the "anyone can write"
* hole while keeping the score honest and the read path fully permissionless.
*/
contract AereReputationRegistry8004 is IERC8004Reputation, Ownable {
/// @notice The LIVE, deployed AereAIReputation this adapter presents an ERC-8004 face over.
IAereAIReputationLive public immutable REPUTATION;
/// @notice The Aere ERC-8004 Identity adapter, used to resolve agentId -> operator (controller).
IAereIdentity8004 public immutable IDENTITY;
/// @notice Owner-curated set of addresses allowed to submit feedback through this adapter. Mirrors
/// the live AereAIReputation curated-attestor model at the adapter boundary: only an
/// authorized author may drive an agent's live reputation via `giveFeedback`.
mapping(address => bool) public isAuthorizedFeedbackAuthor;
error ZeroAddress();
error AgentNotRegistered(uint256 agentId);
error InvalidDelta(int8 delta);
error AdapterNotAttestor();
error NotAuthorizedFeedbackAuthor(address caller);
/// @notice Emitted when the owner adds or removes an authorized feedback author.
event FeedbackAuthorSet(address indexed author, bool authorized);
constructor(address reputation, address identity) {
if (reputation == address(0) || identity == address(0)) revert ZeroAddress();
REPUTATION = IAereAIReputationLive(reputation);
IDENTITY = IAereIdentity8004(identity);
}
// ==========================================================================
// Feedback-author administration
// ==========================================================================
/// @notice Authorize `author` to submit feedback through this adapter. Owner-only. This power is
/// scoped to feedback AUTHORIZATION only: it cannot set a reputation value, cannot exceed
/// the {-1, 0, +1} delta clamp, and moves no funds.
function addFeedbackAuthor(address author) external onlyOwner {
if (author == address(0)) revert ZeroAddress();
isAuthorizedFeedbackAuthor[author] = true;
emit FeedbackAuthorSet(author, true);
}
/// @notice Revoke `author`'s authorization to submit feedback through this adapter. Owner-only.
function removeFeedbackAuthor(address author) external onlyOwner {
isAuthorizedFeedbackAuthor[author] = false;
emit FeedbackAuthorSet(author, false);
}
// ==========================================================================
// Write
// ==========================================================================
/**
* @notice Record ERC-8004 feedback about an agent, forwarding it to the live AereAIReputation.
* The feedback signal is `delta` in {-1, 0, +1}. Resolves the live operator from the
* Identity adapter and calls the deployed `attest`. Reverts fail-closed if this adapter
* is not an authorized attestor on the live reputation (see contract NatSpec).
* @dev Gated to the owner-curated `isAuthorizedFeedbackAuthor` set: an unauthorized caller
* reverts NotAuthorizedFeedbackAuthor, so this adapter cannot act as an open reputation
* firehose once it is a live attestor. Every authorized author is still bound by the
* {-1, 0, +1} delta clamp, so authorization cannot forge an arbitrary score.
* @param agentId the ERC-8004 / DID agent id.
* @param delta +1 positive, -1 dispute, 0 neutral (audit-trail only).
* @param evidenceHash a 32-byte commitment to the off-chain evidence (non-zero on the live path).
* @param evidenceURI an optional public URI for the evidence.
*/
function giveFeedback(uint256 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceURI) external {
if (!isAuthorizedFeedbackAuthor[msg.sender]) revert NotAuthorizedFeedbackAuthor(msg.sender);
if (!IDENTITY.isRegistered(agentId)) revert AgentNotRegistered(agentId);
if (delta < -1 || delta > 1) revert InvalidDelta(delta);
// Surface the missing-authorization case with a clear adapter-level error rather than an
// opaque revert bubbling up from the live contract.
if (!REPUTATION.isAttestor(address(this))) revert AdapterNotAttestor();
address operator = _operatorOf(agentId);
REPUTATION.attest(operator, _agentKey(agentId), delta, evidenceHash, evidenceURI);
emit NewFeedback(agentId, msg.sender, delta, evidenceHash, evidenceURI);
}
// ==========================================================================
// Read
// ==========================================================================
/// @inheritdoc IERC8004Reputation
/// @notice The agent's live reputation score (0..10000), read straight from AereAIReputation.
function getScore(uint256 agentId) external view returns (uint256) {
return REPUTATION.scoreOf(_operatorOf(agentId), _agentKey(agentId));
}
/// @notice The raw live reputation counters for an agent (positive / dispute / lifetime slashed).
function getStats(uint256 agentId)
external
view
returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed)
{
return REPUTATION.statsOf(_operatorOf(agentId), _agentKey(agentId));
}
/// @notice True iff this adapter is authorized to write feedback into the live reputation.
function canWriteThrough() external view returns (bool) {
return REPUTATION.isAttestor(address(this));
}
/// @notice The (operator, agentKey) pair this adapter maps an ERC-8004 agentId to on the live contract.
function liveKeyOf(uint256 agentId) external view returns (address operator, bytes32 agentKey) {
return (_operatorOf(agentId), _agentKey(agentId));
}
// ==========================================================================
// Internal
// ==========================================================================
function _operatorOf(uint256 agentId) internal view returns (address) {
return IDENTITY.getAgent(agentId).agentAddress; // current controller
}
function _agentKey(uint256 agentId) internal pure returns (bytes32) {
return bytes32(agentId);
}
}