aere-contracts/contracts/erc8004/v2/AereValidationRegistry8004V2.sol
Aere Network 1b50878368
Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Failing after 16m0s
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Failing after 27s
contracts-ci / PQC known-answer tests (NIST vectors) (push) Failing after 16m53s
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
ERC-8004 V2 registries transcribed from the current draft (identity ERC-721+metadata+agentWallet, reputation, validation), 17 unit tests, local conformance deployment script; fixtures provenance; the MPC verifier moves to contracts/mpc (the publish gate refuses any 'confidential' path by structure)
2026-09-02 17:52:51 +03:00

137 lines
6.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/**
* @title AereValidationRegistry8004V2 — ERC-8004 Validation Registry, to the letter of the draft.
*
* @notice Transcribed from the "Validation Registry" section of the ERC-8004 draft (read
* 2026-09-02): validationRequest (owner or operator of the agent), validationResponse (only
* the validator named in the request; response 0..100; may be called repeatedly, e.g. soft
* then hard finality via `tag`), and the four read functions, with the exact argument
* orders and event indexing. Stores requestHash, validatorAddress, agentId, response,
* responseHash, lastUpdate and tag, as the draft lists.
*
* @dev `initialize(address)` follows the draft's wording and can run once; a constructor
* argument is offered for non-proxy deployments. `requestCount` is kept for the operational
* check POP-1 of the conformance suite. No owner, no admin, no upgrade.
*/
contract AereValidationRegistry8004V2 {
struct Request {
address validatorAddress;
uint256 agentId;
uint8 response;
bytes32 responseHash;
string tag;
uint256 lastUpdate;
bool exists;
bool responded;
}
event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId, string requestURI, bytes32 indexed requestHash);
event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag);
error AlreadyInitialized();
error NotInitialized();
error ZeroAddress();
error NotAgentOwnerOrOperator(uint256 agentId, address caller);
error AgentNotRegistered(uint256 agentId);
error RequestExists(bytes32 requestHash);
error NoSuchRequest(bytes32 requestHash);
error NotTheValidator(bytes32 requestHash, address caller);
error BadResponse(uint8 response);
address private _identityRegistry;
uint256 public requestCount;
mapping(bytes32 => Request) private _requests;
mapping(uint256 => bytes32[]) private _byAgent;
mapping(address => bytes32[]) private _byValidator;
constructor(address identityRegistry_) {
if (identityRegistry_ != address(0)) _identityRegistry = identityRegistry_;
}
function initialize(address identityRegistry_) external {
if (_identityRegistry != address(0)) revert AlreadyInitialized();
if (identityRegistry_ == address(0)) revert ZeroAddress();
_identityRegistry = identityRegistry_;
}
function getIdentityRegistry() external view returns (address identityRegistry) {
return _identityRegistry;
}
// ------------------------------------------------------------------ writes
function validationRequest(address validatorAddress, uint256 agentId, string calldata requestURI, bytes32 requestHash) external {
if (validatorAddress == address(0)) revert ZeroAddress();
_requireOwnerOrOperator(agentId);
if (_requests[requestHash].exists) revert RequestExists(requestHash);
_requests[requestHash] = Request({ validatorAddress: validatorAddress, agentId: agentId, response: 0, responseHash: bytes32(0), tag: "", lastUpdate: block.timestamp, exists: true, responded: false });
_byAgent[agentId].push(requestHash);
_byValidator[validatorAddress].push(requestHash);
requestCount += 1;
emit ValidationRequest(validatorAddress, agentId, requestURI, requestHash);
}
function validationResponse(bytes32 requestHash, uint8 response, string calldata responseURI, bytes32 responseHash, string calldata tag) external {
Request storage r = _requests[requestHash];
if (!r.exists) revert NoSuchRequest(requestHash);
if (msg.sender != r.validatorAddress) revert NotTheValidator(requestHash, msg.sender);
if (response > 100) revert BadResponse(response);
r.response = response;
r.responseHash = responseHash;
r.tag = tag;
r.lastUpdate = block.timestamp;
r.responded = true;
emit ValidationResponse(r.validatorAddress, r.agentId, requestHash, response, responseURI, responseHash, tag);
}
// ------------------------------------------------------------------ reads
function getValidationStatus(bytes32 requestHash) external view returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 responseHash, string memory tag, uint256 lastUpdate) {
Request storage r = _requests[requestHash];
if (!r.exists) revert NoSuchRequest(requestHash);
return (r.validatorAddress, r.agentId, r.response, r.responseHash, r.tag, r.lastUpdate);
}
function getSummary(uint256 agentId, address[] calldata validatorAddresses, string calldata tag) external view returns (uint64 count, uint8 averageResponse) {
bytes32[] storage hs = _byAgent[agentId];
bytes32 ht = keccak256(bytes(tag)); bool ft = bytes(tag).length != 0;
uint256 sum;
for (uint256 i = 0; i < hs.length; i++) {
Request storage r = _requests[hs[i]];
if (!r.responded) continue;
if (validatorAddresses.length != 0 && !_contains(validatorAddresses, r.validatorAddress)) continue;
if (ft && keccak256(bytes(r.tag)) != ht) continue;
sum += r.response; count += 1;
}
if (count != 0) averageResponse = uint8(sum / count);
}
function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory requestHashes) {
return _byAgent[agentId];
}
function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory requestHashes) {
return _byValidator[validatorAddress];
}
// ------------------------------------------------------------------ internals
function _contains(address[] calldata xs, address x) internal pure returns (bool) {
for (uint256 i = 0; i < xs.length; i++) if (xs[i] == x) return true;
return false;
}
function _requireOwnerOrOperator(uint256 agentId) internal view {
if (_identityRegistry == address(0)) revert NotInitialized();
IERC721 idr = IERC721(_identityRegistry);
address owner;
try idr.ownerOf(agentId) returns (address o) { owner = o; } catch { revert AgentNotRegistered(agentId); }
if (msg.sender == owner || idr.isApprovedForAll(owner, msg.sender) || idr.getApproved(agentId) == msg.sender) return;
revert NotAgentOwnerOrOperator(agentId, msg.sender);
}
}