Skip to content

Commit 3d3d283

Browse files
authored
feat: add ERC-8004 validation registry interface and storage (#4551)
### Description This PR introduces the ERC-8004 Validation Registry interface and supporting files for trustless agent verification. The validation registry enables agents to request verification of their work through various trust models including stake-secured inference re-execution, zkML verifiers, TEE oracles, or trusted judges. ### Changes - Added `IValidationRegistry.sol` interface that defines the core validation registry functionality including: - Events for validation requests and responses - Methods for requesting validation and submitting validation responses - Query functions for validation status and statistics - Created empty `ValidationRegistryFacet.sol` contract for implementation - Added `ValidationRegistryStorage.sol` with storage structures for: - Validation status records - Agent-to-request mappings - Validator-to-request mappings ### Checklist - [ ] Tests added where required - [ ] Documentation updated where applicable - [ ] Changes adhere to the repository's contribution guidelines
1 parent 8a53b69 commit 3d3d283

File tree

8 files changed

+2362
-0
lines changed

8 files changed

+2362
-0
lines changed

packages/contracts/scripts/deployments/diamonds/DeployAppRegistry.s.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {DeploySpaceFactory} from "../diamonds/DeploySpaceFactory.s.sol";
1919
import {DeploySimpleAppBeacon} from "../diamonds/DeploySimpleAppBeacon.s.sol";
2020
import {DeployIdentityRegistry} from "../facets/DeployIdentityRegistry.s.sol";
2121
import {DeployReputationRegistry} from "../facets/DeployReputationRegistry.s.sol";
22+
import {DeployValidationRegistry} from "../facets/DeployValidationRegistry.s.sol";
2223

2324
// contracts
2425
import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
@@ -110,6 +111,7 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
110111
facetHelper.add("AppFactoryFacet");
111112
facetHelper.add("IdentityRegistryFacet");
112113
facetHelper.add("ReputationRegistryFacet");
114+
facetHelper.add("ValidationRegistryFacet");
113115

114116
facetHelper.deployBatch(deployer);
115117

@@ -154,6 +156,13 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
154156
DeployReputationRegistry.makeInitData(FEEDBACK_SCHEMA, RESPONSE_SCHEMA)
155157
);
156158

159+
facet = facetHelper.getDeployedAddress("ValidationRegistryFacet");
160+
addFacet(
161+
makeCut(facet, FacetCutAction.Add, DeployValidationRegistry.selectors()),
162+
facet,
163+
DeployValidationRegistry.makeInitData()
164+
);
165+
157166
address multiInit = facetHelper.getDeployedAddress("MultiInit");
158167

159168
return
@@ -192,6 +201,9 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
192201
if (facetName.eq("IdentityRegistryFacet")) {
193202
addCut(makeCut(facet, FacetCutAction.Add, DeployIdentityRegistry.selectors()));
194203
}
204+
if (facetName.eq("ValidationRegistryFacet")) {
205+
addCut(makeCut(facet, FacetCutAction.Add, DeployValidationRegistry.selectors()));
206+
}
195207
}
196208

197209
return baseFacets();
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
// interfaces
5+
import {IDiamond} from "@towns-protocol/diamond/src/IDiamond.sol";
6+
7+
// libraries
8+
import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
9+
10+
// contracts
11+
import {ValidationRegistryFacet} from "src/apps/facets/validation/ValidationRegistryFacet.sol";
12+
import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
13+
14+
library DeployValidationRegistry {
15+
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
16+
17+
function selectors() internal pure returns (bytes4[] memory res) {
18+
uint256 selectorsCount = 6;
19+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(selectorsCount);
20+
21+
arr.p(ValidationRegistryFacet.validationRequest.selector);
22+
arr.p(ValidationRegistryFacet.validationResponse.selector);
23+
arr.p(ValidationRegistryFacet.getValidationStatus.selector);
24+
arr.p(ValidationRegistryFacet.getSummary.selector);
25+
arr.p(ValidationRegistryFacet.getAgentValidations.selector);
26+
arr.p(ValidationRegistryFacet.getValidatorRequests.selector);
27+
28+
bytes32[] memory selectors_ = arr.asBytes32Array();
29+
30+
assembly ("memory-safe") {
31+
res := selectors_
32+
}
33+
}
34+
35+
function makeCut(
36+
address facetAddress,
37+
IDiamond.FacetCutAction action
38+
) internal pure returns (IDiamond.FacetCut memory) {
39+
return
40+
IDiamond.FacetCut({
41+
action: action,
42+
facetAddress: facetAddress,
43+
functionSelectors: selectors()
44+
});
45+
}
46+
47+
function makeInitData() internal pure returns (bytes memory) {
48+
return abi.encodeCall(ValidationRegistryFacet.__ValidationRegistryFacet_init, ());
49+
}
50+
51+
function deploy() internal returns (address) {
52+
return LibDeploy.deployCode("ValidationRegistryFacet.sol", "");
53+
}
54+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.29;
3+
4+
// interfaces
5+
6+
// libraries
7+
8+
// contracts
9+
10+
/// @title IValidationRegistryBase
11+
/// @notice Base interface for validation registry events
12+
/// @dev Implements ERC-8004 validation event standards for trustless agent verification
13+
interface IValidationRegistryBase {
14+
/// @notice Emitted when an agent requests validation from a validator
15+
/// @dev This event is emitted by validationRequest() and enables tracking of all validation requests
16+
/// @param validatorAddress The address of the validator smart contract that will perform the validation
17+
/// @param agentId The unique identifier of the agent requesting validation (ERC-721 tokenId from Identity Registry)
18+
/// @param requestUri The URI pointing to off-chain data containing inputs, outputs, and all information needed for validation
19+
/// @param requestHash The KECCAK-256 commitment hash of the request data (optional if requestUri is IPFS)
20+
event ValidationRequest(
21+
address indexed validatorAddress,
22+
uint256 indexed agentId,
23+
string requestUri,
24+
bytes32 indexed requestHash
25+
);
26+
27+
/// @notice Emitted when a validator responds to a validation request
28+
/// @dev This event can be emitted multiple times for the same requestHash to support progressive validation states
29+
/// @param validatorAddress The address of the validator providing the response
30+
/// @param agentId The unique identifier of the agent being validated
31+
/// @param requestHash The hash of the original validation request being responded to
32+
/// @param response The validation result (0-100 scale: 0=failed, 100=passed, or intermediate values for spectrum outcomes)
33+
/// @param responseUri The URI pointing to off-chain evidence or audit trail of the validation (optional)
34+
/// @param responseHash The KECCAK-256 hash of the response data (optional if responseUri is IPFS, use bytes32(0) if not provided)
35+
/// @param tag Custom categorization or additional metadata for the validation (optional, e.g., "soft-finality", "hard-finality")
36+
event ValidationResponse(
37+
address indexed validatorAddress,
38+
uint256 indexed agentId,
39+
bytes32 indexed requestHash,
40+
uint8 response,
41+
string responseUri,
42+
bytes32 responseHash,
43+
bytes32 tag
44+
);
45+
46+
error ValidationRegistry__InvalidValidator();
47+
error ValidationRegistry__InvalidAgent();
48+
error ValidationRegistry__RequestAlreadyExists();
49+
error ValidationRegistry__AgentNotExists();
50+
error ValidationRegistry__NotAuthorized();
51+
error ValidationRegistry__RequestNotFound();
52+
error ValidationRegistry__InvalidResponseScore();
53+
error ValidationRegistry__ZeroResponseRequiresMetadata();
54+
}
55+
56+
/// @title IValidationRegistry
57+
/// @notice Interface for the ERC-8004 Validation Registry enabling trustless agent verification
58+
/// @dev This registry enables agents to request verification of their work through various trust models:
59+
/// stake-secured inference re-execution, zkML verifiers, TEE oracles, or trusted judges.
60+
/// The identityRegistry address is set at deployment and accessible via getIdentityRegistry().
61+
/// All validation data is stored on-chain for composability and transparency.
62+
interface IValidationRegistry is IValidationRegistryBase {
63+
/// @notice Request validation of agent work from a validator smart contract
64+
/// @dev MUST be called by the owner or operator of the agentId.
65+
/// The requestUri should contain all information needed for validation including inputs and outputs.
66+
/// The requestHash is a KECCAK-256 commitment to the request data (optional if requestUri is IPFS).
67+
/// Emits a ValidationRequest event upon success.
68+
/// @param validatorAddress The address of the validator smart contract that will perform the validation (mandatory)
69+
/// @param agentId The unique identifier of the agent requesting validation (mandatory, must be validly registered)
70+
/// @param requestUri The URI pointing to off-chain validation data (mandatory)
71+
/// @param requestHash The KECCAK-256 hash of the request data (mandatory unless requestUri is content-addressable like IPFS)
72+
function validationRequest(
73+
address validatorAddress,
74+
uint256 agentId,
75+
string calldata requestUri,
76+
bytes32 requestHash
77+
) external;
78+
79+
/// @notice Submit a validation response for a previously requested validation
80+
/// @dev MUST be called by the validatorAddress specified in the original ValidationRequest.
81+
/// Can be called multiple times for the same requestHash to support use cases like
82+
/// progressive validation states or updates to validation status.
83+
/// Stores requestHash, validatorAddress, agentId, response, lastUpdate, and tag on-chain.
84+
/// Emits a ValidationResponse event upon success.
85+
/// @param requestHash The hash of the validation request being responded to (mandatory)
86+
/// @param response The validation result on a 0-100 scale where 0=failed, 100=passed, or intermediate values (mandatory)
87+
/// @param responseUri The URI pointing to off-chain evidence or audit trail (optional, use empty string if not provided)
88+
/// @param responseHash The KECCAK-256 hash of the response data (optional if responseUri is IPFS, use bytes32(0) if not provided)
89+
/// @param tag Custom categorization or metadata for this validation response (optional, use bytes32(0) if not provided)
90+
function validationResponse(
91+
bytes32 requestHash,
92+
uint8 response,
93+
string calldata responseUri,
94+
bytes32 responseHash,
95+
bytes32 tag
96+
) external;
97+
98+
/// @notice Retrieve the current validation status for a specific request
99+
/// @dev Returns the stored validation data for the given requestHash.
100+
/// If no validation exists, returns zero values.
101+
/// @param requestHash The hash of the validation request to query
102+
/// @return validatorAddress The address of the validator that provided the response
103+
/// @return agentId The unique identifier of the agent that was validated
104+
/// @return response The validation result (0-100 scale)
105+
/// @return tag The custom tag associated with this validation response
106+
/// @return lastUpdate The timestamp of the last validation response update
107+
function getValidationStatus(
108+
bytes32 requestHash
109+
)
110+
external
111+
view
112+
returns (
113+
address validatorAddress,
114+
uint256 agentId,
115+
uint8 response,
116+
bytes32 tag,
117+
uint256 lastUpdate
118+
);
119+
120+
/// @notice Returns aggregated validation statistics for an agent
121+
/// @dev Provides on-chain composable reputation metrics. The agentId parameter is mandatory;
122+
/// validatorAddresses and tag are optional filters to narrow results.
123+
/// Use empty array for validatorAddresses and bytes32(0) for tag to include all validations.
124+
/// @param agentId The agent ID to get statistics for (mandatory)
125+
/// @param validatorAddresses Array of validator addresses to filter by (optional, use empty array for no filter)
126+
/// @param tag The tag to filter by (optional, use bytes32(0) for no filter)
127+
/// @return count The total number of validations matching the filters
128+
/// @return avgResponse The average validation response across all matching validations (0-100 scale)
129+
function getSummary(
130+
uint256 agentId,
131+
address[] calldata validatorAddresses,
132+
bytes32 tag
133+
) external view returns (uint64 count, uint8 avgResponse);
134+
135+
/// @notice Returns all validation request hashes for a specific agent
136+
/// @dev Useful for discovering the complete validation history of an agent.
137+
/// The returned hashes can be used with getValidationStatus() to retrieve full details.
138+
/// @param agentId The agent ID to query validation requests for
139+
/// @return requestHashes Array of all validation request hashes associated with this agent
140+
function getAgentValidations(
141+
uint256 agentId
142+
) external view returns (bytes32[] memory requestHashes);
143+
144+
/// @notice Returns all validation request hashes handled by a specific validator
145+
/// @dev Useful for discovering all validations performed by a validator contract.
146+
/// The returned hashes can be used with getValidationStatus() to retrieve full details.
147+
/// @param validatorAddress The validator address to query validation requests for
148+
/// @return requestHashes Array of all validation request hashes handled by this validator
149+
function getValidatorRequests(
150+
address validatorAddress
151+
) external view returns (bytes32[] memory requestHashes);
152+
}

0 commit comments

Comments
 (0)