Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/interfaces/IEIP7512.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
* @title IEIP7512
* @notice This interface is used for an EIP7512 implementation.
*/
interface IEIP7512 {
/// @notice Defines different types of signature standards.
enum SignatureType {
SECP256K1,
BLS,
ERC1271,
SECP256R1
}

/// @notice Represents the auditor.
/// @param name The name of the auditor.
/// @param uri The URI with additional information about the auditor.
/// @param authors List of authors responsible for the audit.
struct Auditor {
string name;
string uri;
string[] authors;
}

/// @notice Represents a summary of the audit.
/// @param auditor The auditor who performed the audit.
/// @param issuedAt The timestamp at which the audit was issued.
/// @param ercs List of ERC standards that were covered in the audit.
/// @param bytecodeHash Hash of the audited smart contract bytecode.
/// @param auditHash Hash of the audit document.
/// @param auditUri URI with additional information or the full audit report.
struct AuditSummary {
Auditor auditor;
uint256 issuedAt;
uint256[] ercs;
bytes32 bytecodeHash;
bytes32 auditHash;
string auditUri;
}

/// @notice Represents a cryptographic signature.
/// @param signatureType The type of the signature (e.g., SECP256K1, BLS, etc.).
/// @param data The actual signature data.
struct Signature {
SignatureType signatureType;
bytes data;
}

/// @notice Represents a signed audit summary.
/// @param summary The audit summary being signed.
/// @param signedAt Timestamp indicating when the audit summary was signed.
/// @param auditorSignature Signature of the auditor for authenticity.
struct SignedAuditSummary {
AuditSummary summary;
uint256 signedAt;
Signature auditorSignature;
}
}
92 changes: 92 additions & 0 deletions src/interfaces/IHookMetadata.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IEIP712_v4} from "./IEIP712_v4.sol";
import {IEIP7512} from "./IEIP7512.sol";

/**
* @title IHookMetadata
* @notice This interface is designed so that external indexing services can discover
* and display essential information about a Uniswap v4 hook. It extends the
* on-chain audit representation outlined in EIP‑7512, thereby allowing the hook
* to store and provide signed audit summaries for third-party verification.
*
* ----------------------------------------------------------------------------
* HOOK METADATA FLOW
*
* 1. Required Metadata
* - Every hook must implement: name(), repository(), logoURI(), websiteURI(),
* description(), version().
* - These fields are effectively immutable and indexed at deployment time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does "effectively" immutable mean? When can they be changed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We assume that after deploy they will not be changed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not actually changeable in the contracts though no? So I think you can just declare them to be immutable

Copy link

@smak0v smak0v Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@snreynolds Unfortunately, it is impossible to declare string variables as immutable in Solidity at this moment.

From the documentation:
Not all types for constants and immutables are implemented at this time. The only supported types are strings (only for constants) and value types.

However, there is one workaround we can implement instead: put all 6 variables in the HookMetadata storage, mark them as private (not immutable, as this is impossible) and allow them to be set up through the constructor only. No setters will be provided, only getters that will allow reading these state variables from outside or child contracts.

What do you think about this approach?

*
* 2. Audit Summaries
* - The function auditSummaries(auditId) returns a completed audit summary.
* - Audits can be appended over time, and each newly added audit summary must
* emit the AuditSummaryRegistered event for indexers.
*
* 3. EIP-712 Domain Information
* - The contract must provide an EIP-712 DOMAIN_SEPARATOR to allow auditors
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a standard format you'd want to enforce in the DOMAIN_SEPARATOR? Could give a recommendation for which fields should/must be specified. ie Im guessing the verifyingContract is the hook but maybe worth specifying?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* to produce valid signatures for the audit summaries.
*
* 4. Signature Types
* - Auditors may choose from multiple signature standards (SECP256K1, BLS, ERC1271, SECP256R1)
* - The chosen SignatureType is stored alongside the signature data.
*
* 5. Auditor Process
* - The auditor generates an EIP-712 signature over the audit summary, choosing
* the appropriate SignatureType.
* - The final signed audit summary is then delivered to the hook owner/deployer.
*
* 6. Developer / Deployer Process
* - Implement this interface in the hook contract, storing all required metadata.
* - Emit the AuditSummaryRegistered event whenever a new audit summary is added.
* - Treat core metadata (name, repository, etc.) as immutable after deployment,
* but you may append new audit summaries as needed.
*
* ----------------------------------------------------------------------------
*
* For more details, see:
* - EIP‑7512: https://eips.ethereum.org/EIPS/eip-7512
* - EIP‑712: https://eips.ethereum.org/EIPS/eip-712
*/
interface IHookMetadata is IEIP712_v4, IEIP7512 {
/// @notice An error emitted when a wrong audit ID is used.
error WrongAuditId();

/// @notice Emitted when a new audit summary is registered.
/// @dev This event must be emitted so that all indexing services can
/// index the newly added audit record.
/// @param auditId The identifier for the audit record.
/// @param auditHash The hash of the audit document.
/// @param auditUri The URI pointing to additional audit info or the full report.
event AuditSummaryRegistered(uint256 indexed auditId, bytes32 auditHash, string auditUri);

/// @notice Returns the name of the hook.
/// @return The hook's name as a string.
function name() external view returns (string memory);

/// @notice Returns the repository URI for the hook's source code.
/// @return The repository URI.
function repositoryURI() external view returns (string memory);

/// @notice Returns the URI for the hook's logo.
/// @return The logo URI.
function logoURI() external view returns (string memory);

/// @notice Returns the URI for the hook's website.
/// @return The website URI.
function websiteURI() external view returns (string memory);

/// @notice Returns a description of the hook.
/// @return The hook's description.
function description() external view returns (string memory);

/// @notice Returns the version of the hook.
/// @return The version identifier as bytes32.
function version() external view returns (bytes32);

/// @notice Returns the audit summary record for a given audit ID.
/// @param auditId The identifier used to look up a specific SignedAuditSummary.
/// @return summary A SignedAuditSummary struct containing the audit details and signature.
function auditSummaries(uint256 auditId) external view returns (SignedAuditSummary memory);
}
43 changes: 43 additions & 0 deletions src/utils/HookMetadata.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IHookMetadata} from "../interfaces/IHookMetadata.sol";

/**
* @title HookMetadata
* @notice An abstract implementation of the HookMetadata contract wich internaly implements registration of audits
* summaries and their retrivals using internal counting mechanism.
*/
abstract contract HookMetadata is IHookMetadata {
mapping(uint256 auditId => SignedAuditSummary signedAuditSummary) private signedAuditsSummaries;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to store on chain? If offchain indexing services are already indexing against the registration, whats the reason to also store on chain?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. The primary reason for storing the audit summary on-chain is to ensure immutability and verifiable proof that an audit actually occurred. By storing the audit data, including the auditor’s signature, directly on-chain, we guarantee that nobody can modify the audit content after submission.

While off-chain indexing services can indeed store a copy of the audit, they can't provide the same cryptographic guarantees of immutability. Additionally, by having an on-chain registry of trusted auditor addresses, anyone can verify directly on-chain whether a hook was audited by a trusted auditor or not.

It's important to note that builders aren't forced to submit audits on-chain—it's an optional but recommended approach if verifiable audit proof is desired directly within the onchain environment.

uint256 public auditsCount;

/// @notice Returns a summary about audit using signed audit ID.
/// @dev Throws an error in case of wrong audit ID.
/// @param auditId An ID of the audit to retrieve information about.
function auditSummaries(uint256 auditId) external view returns (SignedAuditSummary memory) {
if (auditId < auditsCount) return signedAuditsSummaries[auditId];

revert IHookMetadata.WrongAuditId();
}

/// @notice An internal method that registers a new signed audit summury and emits an event that may be useful for
/// external indexing services to discover and display essential information about a Uniswap V4 hook.
/// @dev This internal method should be called in the child hook contract whenever new audit summary is registered
/// (for example, in the constructor or in the custom owner/admin/DAO controlled method).
/// @param signedAuditSummary A new signed audit summury to register.
/// @return A new signed audit summary ID.
function _registerAuditSummary(SignedAuditSummary memory signedAuditSummary) internal returns (uint256) {
Comment on lines +24 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if I'm in love with the idea of adding an audit after audits, is there a reason why audit information cannot be provided at the time of deployment (via constructor)?

Copy link

@smak0v smak0v Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saucepoint There are a few reasons why we came out with this solution:

  1. a contract may be audited a few times and have a few audit results from different auditors.
  2. we need a bytecodeHash to store it in the contract with the audit results. To obtain a bytecode we need to deploy a contract.

uint256 _auditsCount = auditsCount;

signedAuditsSummaries[_auditsCount] = signedAuditSummary;

emit AuditSummaryRegistered(
_auditsCount, signedAuditSummary.summary.auditHash, signedAuditSummary.summary.auditUri
);

++auditsCount;

return _auditsCount;
}
}