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
137 lines
6.6 KiB
Solidity
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);
|
|
}
|
|
}
|