From 210a86170ecfa67ad093c57ec7ffa072db446ba5 Mon Sep 17 00:00:00 2001 From: Melvillian Date: Tue, 19 Aug 2025 12:42:43 -0400 Subject: [PATCH] N-02 update missing docstrings, and add interfaces This commit does 2 things: 1. It addresses N-02 from the Q3 2025 OZ audit 2. It creates full IFlashtestation and IBlockBuilderPolicy interfaces to ease the integration with third parties initial migration to IBlockBuilderPolicy add IBlockBuilderPolicy and add missing docstrings for all events + errors in IBlockBuilderPolicy add missing docstring for IFlashtestationRegistry functions update IFlashtestation interface with public and external functions --- script/Interactions.s.sol | 3 +- src/BlockBuilderPolicy.sol | 195 +++++--------------- src/FlashtestationRegistry.sol | 149 ++++----------- src/interfaces/IBlockBuilderPolicy.sol | 203 +++++++++++++++++++++ src/interfaces/IFlashtestationRegistry.sol | 186 ++++++++++++++++++- test/BlockBuilderPolicy.t.sol | 66 ++++--- 6 files changed, 515 insertions(+), 287 deletions(-) create mode 100644 src/interfaces/IBlockBuilderPolicy.sol diff --git a/script/Interactions.s.sol b/script/Interactions.s.sol index cd2bd1d..56682e6 100644 --- a/script/Interactions.s.sol +++ b/script/Interactions.s.sol @@ -2,7 +2,8 @@ pragma solidity 0.8.28; import {Script, console} from "forge-std/Script.sol"; -import {BlockBuilderPolicy, WorkloadId} from "../src/BlockBuilderPolicy.sol"; +import {BlockBuilderPolicy} from "../src/BlockBuilderPolicy.sol"; +import {WorkloadId} from "../src/interfaces/IBlockBuilderPolicy.sol"; import {FlashtestationRegistry} from "../src/FlashtestationRegistry.sol"; import {IFlashtestationRegistry} from "../src/interfaces/IFlashtestationRegistry.sol"; import {DeploymentUtils} from "./utils/DeploymentUtils.sol"; diff --git a/src/BlockBuilderPolicy.sol b/src/BlockBuilderPolicy.sol index 4889f21..927b687 100644 --- a/src/BlockBuilderPolicy.sol +++ b/src/BlockBuilderPolicy.sol @@ -7,25 +7,8 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {FlashtestationRegistry} from "./FlashtestationRegistry.sol"; - -/// @notice WorkloadID uniquely identifies a TEE workload. A workload is roughly equivalent to a version of an application's -/// code, can be reproduced from source code, and is derived from a combination of the TEE's measurement registers. -/// The TDX platform provides several registers that capture cryptographic hashes of code, data, and configuration -/// loaded into the TEE's environment. This means that whenever a TEE device changes anything about its compute stack -/// (e.g. user code, firmware, OS, etc), the workloadID will change. -/// See the [Flashtestation's specification](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#workload-identity-derivation) for more details -type WorkloadId is bytes32; - -/** - * @notice Metadata associated with a workload - * @dev Used to track the source code used to build the TEE image identified by the workloadId - */ -struct WorkloadMetadata { - /// @notice The Git commit hash of the source code repository - string commitHash; - /// @notice An array of URLs pointing to the source code repository - string[] sourceLocators; -} +import {IFlashtestationRegistry} from "./interfaces/IFlashtestationRegistry.sol"; +import {IBlockBuilderPolicy, WorkloadId} from "./interfaces/IBlockBuilderPolicy.sol"; /** * @notice Cached workload information for gas optimization @@ -49,12 +32,18 @@ struct CachedWorkload { * changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address * is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest */ -contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeable, EIP712Upgradeable { +contract BlockBuilderPolicy is + Initializable, + UUPSUpgradeable, + OwnableUpgradeable, + EIP712Upgradeable, + IBlockBuilderPolicy +{ using ECDSA for bytes32; // ============ EIP-712 Constants ============ - /// @notice EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function + /// @inheritdoc IBlockBuilderPolicy bytes32 public constant VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH = keccak256("VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)"); @@ -79,14 +68,14 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl /// @notice Mapping from workloadId to its metadata (commit hash and source locators) /// @dev This is only updateable by governance (i.e. the owner) of the Policy contract /// Adding and removing a workload is O(1). - /// This means the critical `isAllowedPolicy` function is O(1) since we can directly check if a workloadId exists + /// This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists /// in the mapping - mapping(bytes32 => WorkloadMetadata) public approvedWorkloads; + mapping(bytes32 => WorkloadMetadata) private approvedWorkloads; - /// @notice Address of the FlashtestationRegistry contract that verifies TEE quotes + /// @inheritdoc IBlockBuilderPolicy address public registry; - /// @notice Tracks nonces for EIP-712 signatures to prevent replay attacks + /// @inheritdoc IBlockBuilderPolicy mapping(address => uint256) public nonces; /// @notice Cache of computed workloadIds to avoid expensive recomputation @@ -97,43 +86,8 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl /// @dev This reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads) uint256[46] __gap; - // ============ Errors ============ - - error InvalidRegistry(); - error WorkloadAlreadyInPolicy(); - error WorkloadNotInPolicy(); - error UnauthorizedBlockBuilder(address caller); // the teeAddress is not associated with a valid TEE workload - error InvalidNonce(uint256 expected, uint256 provided); - error EmptyCommitHash(); - error EmptySourceLocators(); - - // ============ Events ============ - - event WorkloadAddedToPolicy(WorkloadId workloadId); - event WorkloadRemovedFromPolicy(WorkloadId workloadId); - event RegistrySet(address registry); - /// @notice Emitted when a block builder proof is successfully verified - /// @param caller The address that called the verification function (TEE address) - /// @param workloadId The workload identifier of the TEE - /// @param blockNumber The block number when the verification occurred - /// @param version The flashtestation protocol version used - /// @param blockContentHash The hash of the block content - /// @param commitHash The git commit hash associated with the workload - event BlockBuilderProofVerified( - address caller, - WorkloadId workloadId, - uint256 blockNumber, - uint8 version, - bytes32 blockContentHash, - string commitHash - ); - - /** - * @notice Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract - * @param _initialOwner The address of the initial owner of the contract - * @param _registry The address of the registry contract - */ - function initialize(address _initialOwner, address _registry) external initializer { + /// @inheritdoc IBlockBuilderPolicy + function initialize(address _initialOwner, address _registry) external override initializer { __Ownable_init(_initialOwner); __EIP712_init("BlockBuilderPolicy", "1"); require(_registry != address(0), InvalidRegistry()); @@ -146,37 +100,18 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl /// @param newImplementation The address of the new implementation contract function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - /// @notice Verify a block builder proof with a Flashtestation Transaction - /// @param version The version of the flashtestation's protocol used to generate the block builder proof - /// @param blockContentHash The hash of the block content - /// @notice This function will only succeed if the caller is a registered TEE-controlled address from an attested TEE - /// and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`) - /// @notice The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. - /// See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/77fc19f785eeeb9b4eb5fb08463bc556dec2c837/specs/flashtestations.md) for more details - /// @dev If you do not want to deal with the operational difficulties of keeping your TEE-controlled - /// addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs - /// more gas, but allows any EOA to submit a block builder proof on behalf of a TEE - function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external { + /// @inheritdoc IBlockBuilderPolicy + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external override { _verifyBlockBuilderProof(msg.sender, version, blockContentHash); } - /// @notice Verify a block builder proof with a Flashtestation Transaction using EIP-712 signatures - /// @notice This function allows any EOA to submit a block builder proof on behalf of a TEE - /// @notice The TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address - /// whose associated workload is approved under this policy - /// @dev This function is useful if you do not want to deal with the operational difficulties of keeping your - /// TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost - /// more gas than the non-EIP-712 verifyBlockBuilderProof function - /// @param version The version of the flashtestation's protocol used to generate the block builder proof - /// @param blockContentHash The hash of the block content - /// @param nonce The nonce to use for the EIP-712 signature - /// @param eip712Sig The EIP-712 signature of the verification message + /// @inheritdoc IBlockBuilderPolicy function permitVerifyBlockBuilderProof( uint8 version, bytes32 blockContentHash, uint256 nonce, bytes calldata eip712Sig - ) external { + ) external override { // Get the TEE address from the signature bytes32 digest = getHashedTypeDataV4(computeStructHash(version, blockContentHash, nonce)); address teeAddress = digest.recover(eip712Sig); @@ -211,19 +146,15 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl // onchain. We rely on the TEE workload to correctly compute this hash according to the // specified version of the calculation method. - string memory commitHash = approvedWorkloads[WorkloadId.unwrap(workloadId)].commitHash; - emit BlockBuilderProofVerified(teeAddress, workloadId, block.number, version, blockContentHash, commitHash); + bytes32 workloadKey = WorkloadId.unwrap(workloadId); + string memory commitHash = approvedWorkloads[workloadKey].commitHash; + emit BlockBuilderProofVerified(teeAddress, workloadKey, block.number, version, blockContentHash, commitHash); } - /// @notice Check if this TEE-controlled address has registered a valid TEE workload with the registry, and - /// if the workload is approved under this policy - /// @param teeAddress The TEE-controlled address - /// @return allowed True if the TEE is using an approved workload in the policy - /// @return workloadId The workloadId of the TEE that is using an approved workload in the policy, or 0 if - /// the TEE is not using an approved workload in the policy - function isAllowedPolicy(address teeAddress) public view returns (bool allowed, WorkloadId) { + /// @inheritdoc IBlockBuilderPolicy + function isAllowedPolicy(address teeAddress) public view override returns (bool allowed, WorkloadId) { // Get full registration data and compute workload ID - (, FlashtestationRegistry.RegisteredTEE memory registration) = + (, IFlashtestationRegistry.RegisteredTEE memory registration) = FlashtestationRegistry(registry).getRegistration(teeAddress); // Invalid Registrations means the attestation used to register the TEE is no longer valid @@ -288,14 +219,11 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl } } - /// @notice Application specific mapping of registration data to a workload identifier - /// @dev Think of the workload identifier as the version of the application for governance. - /// The workloadId verifiably maps to a version of source code that builds the TEE VM image - /// @param registration The registration data from a TEE device - /// @return The computed workload identifier - function workloadIdForTDRegistration(FlashtestationRegistry.RegisteredTEE memory registration) + /// @inheritdoc IBlockBuilderPolicy + function workloadIdForTDRegistration(IFlashtestationRegistry.RegisteredTEE memory registration) public pure + override returns (WorkloadId) { // We expect FPU and SSE xfam bits to be set, and anything else should be handled by explicitly allowing the workloadid @@ -321,29 +249,10 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl ); } - /// @notice Add a workload to a policy (governance only) - /// @notice Only the owner of this contract can add workloads to the policy - /// and it is the responsibility of the owner to ensure that the workload is valid - /// otherwise the address associated with this workload has full power to do anything - /// who's authorization is based on this policy - /// @dev The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy - /// to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, - /// op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead - /// we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever - /// it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so - /// we can also assume the owner will not add a commit hash that is not associated with the workloadId. If - /// the owner did act maliciously, this can easily be determined offchain by an honest actor building the - /// TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the - /// workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, - /// this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a - /// source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image - /// identified by the workloadId. - /// @param workloadId The workload identifier - /// @param commitHash The 40-character hexadecimal commit hash of the git repository - /// whose source code is used to build the TEE image identified by the workloadId - /// @param sourceLocators An array of URIs pointing to the source code + /// @inheritdoc IBlockBuilderPolicy function addWorkloadToPolicy(WorkloadId workloadId, string calldata commitHash, string[] calldata sourceLocators) external + override onlyOwner { require(bytes(commitHash).length > 0, EmptyCommitHash()); @@ -357,12 +266,11 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl // Store the workload metadata approvedWorkloads[workloadKey] = WorkloadMetadata({commitHash: commitHash, sourceLocators: sourceLocators}); - emit WorkloadAddedToPolicy(workloadId); + emit WorkloadAddedToPolicy(workloadKey); } - /// @notice Remove a workload from a policy (governance only) - /// @param workloadId The workload identifier - function removeWorkloadFromPolicy(WorkloadId workloadId) external onlyOwner { + /// @inheritdoc IBlockBuilderPolicy + function removeWorkloadFromPolicy(WorkloadId workloadId) external override onlyOwner { bytes32 workloadKey = WorkloadId.unwrap(workloadId); // Check if workload exists @@ -371,39 +279,36 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl // Remove the workload metadata delete approvedWorkloads[workloadKey]; - emit WorkloadRemovedFromPolicy(workloadId); + emit WorkloadRemovedFromPolicy(workloadKey); } - /// @notice Get the metadata for a workload - /// @param workloadId The workload identifier to query - /// @return The metadata associated with the workload - function getWorkloadMetadata(WorkloadId workloadId) external view returns (WorkloadMetadata memory) { + /// @inheritdoc IBlockBuilderPolicy + function getWorkloadMetadata(WorkloadId workloadId) external view override returns (WorkloadMetadata memory) { return approvedWorkloads[WorkloadId.unwrap(workloadId)]; } - /// @notice Computes the digest for the EIP-712 signature - /// @param structHash The struct hash for the EIP-712 signature - /// @return The digest for the EIP-712 signature + /// @inheritdoc IBlockBuilderPolicy function getHashedTypeDataV4(bytes32 structHash) public view returns (bytes32) { return _hashTypedDataV4(structHash); } - /// @notice Computes the struct hash for the EIP-712 signature - /// @param version The version of the flashtestation's protocol - /// @param blockContentHash The hash of the block content - /// @param nonce The nonce to use for the EIP-712 signature - /// @return The struct hash for the EIP-712 signature + /// @inheritdoc IBlockBuilderPolicy function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce) public pure returns (bytes32) { return keccak256(abi.encode(VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH, version, blockContentHash, nonce)); } - /** - * @notice Returns the domain separator for the EIP-712 signature - * @dev This is useful for when both onchain and offchain users want to compute the domain separator - * for the EIP-712 signature, and then use it to verify the signature - * @return The domain separator for the EIP-712 signature - */ + /// @inheritdoc IBlockBuilderPolicy function domainSeparator() external view returns (bytes32) { return _domainSeparatorV4(); } + + /// @inheritdoc IBlockBuilderPolicy + function getApprovedWorkloads(bytes32 workloadId) + external + view + override + returns (string memory commitHash, string[] memory sourceLocators) + { + return (approvedWorkloads[workloadId].commitHash, approvedWorkloads[workloadId].sourceLocators); + } } diff --git a/src/FlashtestationRegistry.sol b/src/FlashtestationRegistry.sol index b348d40..26653ed 100644 --- a/src/FlashtestationRegistry.sol +++ b/src/FlashtestationRegistry.sol @@ -39,33 +39,29 @@ contract FlashtestationRegistry is uint256 public constant MAX_BYTES_SIZE = 20 * 1024; /// @notice EIP-712 Typehash, used in the permitRegisterTEEService function - bytes32 public constant REGISTER_TYPEHASH = + bytes32 public constant override REGISTER_TYPEHASH = keccak256("RegisterTEEService(bytes rawQuote,bytes extendedRegistrationData,uint256 nonce,uint256 deadline)"); // ============ Storage Variables ============ - /// @notice The address of the Automata DCAP Attestation contract, which verifies TEE quotes - /// @dev This is deployed by Automata and set during initialization - IAttestation public attestationContract; + /// @inheritdoc IFlashtestationRegistry + IAttestation public override attestationContract; - /// @notice Tracks the TEE-controlled address that registered a particular attestation quote and app data. - /// @dev This enables efficient O(1) lookup in `getRegistration`, so that apps can quickly verify the - /// output of a TEE workload + /** + * @notice Returns the registered TEE for a given address + * @dev This is used to get the registered TEE for a given address + */ mapping(address => RegisteredTEE) public registeredTEEs; - /// @notice Tracks nonces for EIP-712 signatures to prevent replay attacks - mapping(address => uint256) public nonces; + /// @inheritdoc IFlashtestationRegistry + mapping(address => uint256) public override nonces; /// @dev Storage gap to allow for future storage variable additions in upgrades /// @dev This reserves 47 storage slots (out of 50 total - 3 used for attestationContract, registeredTEEs and nonces) uint256[47] __gap; - /** - * Initializer to set the Automata DCAP Attestation contract, which verifies TEE quotes - * @param owner The address of the initial owner of the contract, who is able to upgrade the contract - * @param _attestationContract The address of the Automata DCAP attestation contract, used to verify TEE quotes - */ - function initialize(address owner, address _attestationContract) external initializer { + /// @inheritdoc IFlashtestationRegistry + function initialize(address owner, address _attestationContract) external override initializer { __Ownable_init(owner); __EIP712_init("FlashtestationRegistry", "1"); require(_attestationContract != address(0), InvalidAttestationContract()); @@ -92,45 +88,24 @@ contract FlashtestationRegistry is _; } - /** - * @notice Registers a TEE workload with a specific TEE-controlled address in the FlashtestationRegistry - * @notice The TEE must be registered with a quote whose validity is verified by the attestationContract - * @dev In order to mitigate DoS attacks, the quote must be less than 20KB - * @dev This is a costly operation (5 million gas) and should be used sparingly. - * @param rawQuote The raw quote from the TEE device. Must be a V4 TDX quote - * @param extendedRegistrationData Abi-encoded application specific attested data, reserved for future upgrades - */ + /// @inheritdoc IFlashtestationRegistry function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external payable + override nonReentrant { doRegister(msg.sender, rawQuote, extendedRegistrationData); } - /** - * @notice Registers a TEE workload with a specific TEE-controlled address using EIP-712 signatures - * @notice The TEE must be registered with a quote whose validity is verified by the attestationContract - * @dev In order to mitigate DoS attacks, the quote must be less than 20KB - * @dev This function exists so that the TEE does not need to be funded with gas for transaction fees, and - * instead can rely on any EOA to execute the transaction, but still only allow quotes from attested TEEs - * @dev Replay is implicitly shielded against replay attacks through the transaction's nonce (TEE must sign the new nonce) - * @param rawQuote The raw quote from the TEE device. Must be a V4 TDX quote - * @param extendedRegistrationData Abi-encoded application specific attested data, this is arbitrary app-related - * data that the app wants to associate with the TEE-controlled address. Even though it's passed in as a parameter, - * we can trust that it comes from the TEE because we verify that the hash derived from all of the variables in - * extendedRegistrationData matches the hash in the TDX report data. - * @param nonce The nonce to use for the EIP-712 signature (to prevent replay attacks) - * @param deadline The blocktime after which this signature is no longer valid - * @param signature The EIP-712 signature of the registration message - */ + /// @inheritdoc IFlashtestationRegistry function permitRegisterTEEService( bytes calldata rawQuote, bytes calldata extendedRegistrationData, uint256 nonce, uint256 deadline, bytes calldata signature - ) external payable nonReentrant { + ) external payable override nonReentrant { // Create the digest using EIP712Upgradeable's _hashTypedDataV4 bytes32 digest = hashTypedDataV4(computeStructHash(rawQuote, extendedRegistrationData, nonce, deadline)); @@ -235,51 +210,24 @@ contract FlashtestationRegistry is return existingQuoteHash != 0; } - /** - * @notice Fetches TEE registration for a given address - * @dev getRegistration will only return true if a valid TEE quote containing - * teeAddress in its reportData field was previously registered with the FlashtestationRegistry - * using the registerTEEService function. - * @param teeAddress The TEE-controlled address to check - * @return isValid Whether the TEE quote, td attributes, or xfam have not been invalidated - * @return registeredTEE The RegisteredTEE struct containing raw quote, parsed report body, and extended data - */ - function getRegistration(address teeAddress) public view returns (bool, RegisteredTEE memory) { + /// @inheritdoc IFlashtestationRegistry + function getRegistration(address teeAddress) public view override returns (bool, RegisteredTEE memory) { return (registeredTEEs[teeAddress].isValid, registeredTEEs[teeAddress]); } - /** - * @notice Fetches only the validity status and quote hash for a given TEE address - * @dev This is a gas-optimized version of getRegistration that only returns the minimal data - * needed for caching optimizations in policy contracts - * @param teeAddress The TEE-controlled address to check - * @return isValid Whether the TEE quote, td attributes, or xfam have not been invalidated - * @return quoteHash The keccak256 hash of the raw quote - */ - function getRegistrationStatus(address teeAddress) external view returns (bool isValid, bytes32 quoteHash) { + /// @inheritdoc IFlashtestationRegistry + function getRegistrationStatus(address teeAddress) + external + view + override + returns (bool isValid, bytes32 quoteHash) + { RegisteredTEE storage tee = registeredTEEs[teeAddress]; return (tee.isValid, tee.quoteHash); } - /** - * @notice Invalidates the attestation of a TEE - * @dev This is a costly operation (5 million gas) and should be used sparingly. - * @dev Will always revert except if the attestation is valid and the attestation re-verification - * fails. This is to prevent a user needlessly calling this function and for a no-op to occur - * @dev This function exists to handle an important security requirement: occasionally Intel - * will release a new set of DCAP Endorsements for a particular TEE setup (for instance if a - * TDX vulnerability was discovered), which invalidates all prior quotes generated by that TEE. - * By invalidates we mean that the outputs generated by the TEE-controlled address associated - * with these invalid quotes are no longer secure and cannot be relied upon. This fact needs to be - * reflected onchain, so that any upstream contracts that try to call `getRegistration` will - * correctly return `false` for the TEE-controlled addresses associated with these invalid quotes. - * This is a security requirement to ensure that no downstream contracts can be exploited by - * a malicious TEE that has been compromised - * @dev Note: this function is callable by anyone, so that offchain monitoring services can - * quickly mark TEEs as invalid - * @param teeAddress The TEE-controlled address to invalidate - */ - function invalidateAttestation(address teeAddress) external payable nonReentrant { + /// @inheritdoc IFlashtestationRegistry + function invalidateAttestation(address teeAddress) external payable override nonReentrant { // check to make sure it even makes sense to invalidate the TEE-controlled address // if the TEE-controlled address is not registered with the FlashtestationRegistry, // it doesn't make sense to invalidate the attestation @@ -308,61 +256,32 @@ contract FlashtestationRegistry is } } - /** - * @notice Allows a user to increment their EIP-712 signature nonce, invalidating any previously signed but unexecuted permit signatures. - * @dev This function provides a way for users to proactively invalidate old signatures by incrementing their nonce, - * without needing to execute a valid permit. - * This is particularly useful if a user suspects a signature may have been compromised or simply wants to ensure - * that any outstanding, unused signatures with the current nonce can no longer be executed. - * @dev The function requires the provided nonce to match the user's current nonce, as a defense against the caller - * mistakenly invalidating a nonce that they did not intend to invalidate - * @param _nonce The expected current nonce for the caller; must match the stored nonce - */ - function invalidatePreviousSignature(uint256 _nonce) external { + /// @inheritdoc IFlashtestationRegistry + function invalidatePreviousSignature(uint256 _nonce) external override { require(_nonce == nonces[msg.sender], InvalidNonce(nonces[msg.sender], _nonce)); nonces[msg.sender]++; emit PreviousSignatureInvalidated(msg.sender, _nonce); } - /** - * @notice Computes the digest for the EIP-712 signature - * @dev This is useful for when both onchain and offchain users want to compute the digest - * for the EIP-712 signature, and then use it to verify the signature - * @param structHash The struct hash for the EIP-712 signature - * @return The digest for the EIP-712 signature - */ - function hashTypedDataV4(bytes32 structHash) public view returns (bytes32) { + /// @inheritdoc IFlashtestationRegistry + function hashTypedDataV4(bytes32 structHash) public view override returns (bytes32) { return _hashTypedDataV4(structHash); } - /** - * @notice Computes the struct hash for the EIP-712 signature - * @dev This is useful for when both onchain and offchain users want to compute the struct hash - * for the EIP-712 signature, and then use it to verify the signature - * @param rawQuote The raw quote from the TEE device - * @param extendedRegistrationData Abi-encoded attested data, application specific - * @param nonce The nonce to use for the EIP-712 signature - * @param deadline The blocktime after which this signature is no longer valid - * @return The struct hash for the EIP-712 signature - */ + /// @inheritdoc IFlashtestationRegistry function computeStructHash( bytes calldata rawQuote, bytes calldata extendedRegistrationData, uint256 nonce, uint256 deadline - ) public pure returns (bytes32) { + ) public pure override returns (bytes32) { return keccak256( abi.encode(REGISTER_TYPEHASH, keccak256(rawQuote), keccak256(extendedRegistrationData), nonce, deadline) ); } - /** - * @notice Returns the domain separator for the EIP-712 signature - * @dev This is useful for when both onchain and offchain users want to compute the domain separator - * for the EIP-712 signature, and then use it to verify the signature - * @return The domain separator for the EIP-712 signature - */ - function domainSeparator() external view returns (bytes32) { + /// @inheritdoc IFlashtestationRegistry + function domainSeparator() external view override returns (bytes32) { return _domainSeparatorV4(); } } diff --git a/src/interfaces/IBlockBuilderPolicy.sol b/src/interfaces/IBlockBuilderPolicy.sol new file mode 100644 index 0000000..dea02c0 --- /dev/null +++ b/src/interfaces/IBlockBuilderPolicy.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IFlashtestationRegistry} from "./IFlashtestationRegistry.sol"; + +/// @notice WorkloadID uniquely identifies a TEE workload. A workload is roughly equivalent to a version of an application's +/// code, can be reproduced from source code, and is derived from a combination of the TEE's measurement registers. +/// The TDX platform provides several registers that capture cryptographic hashes of code, data, and configuration +/// loaded into the TEE's environment. This means that whenever a TEE device changes anything about its compute stack +/// (e.g. user code, firmware, OS, etc), the workloadID will change. +/// See the [Flashtestation's specification](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#workload-identity-derivation) for more details +type WorkloadId is bytes32; + +/** + * @title IBlockBuilderPolicy + * @dev Interface exposing errors, events, and external/public functions of BlockBuilderPolicy + */ +interface IBlockBuilderPolicy { + // ============ Types ============ + + /** + * @notice Metadata associated with a workload + * @dev Used to track the source code used to build the TEE image identified by the workloadId + */ + struct WorkloadMetadata { + string commitHash; + string[] sourceLocators; + } + + // ============ Events ============ + + /// @notice Emitted when a workload is added to the policy + /// @param workloadId The workload identifier + event WorkloadAddedToPolicy(bytes32 workloadId); + /// @notice Emitted when a workload is removed from the policy + /// @param workloadId The workload identifier + event WorkloadRemovedFromPolicy(bytes32 workloadId); + /// @notice Emitted when the registry is set in the initializer + /// @param registry The address of the registry + event RegistrySet(address registry); + /// @notice Emitted when a block builder proof is successfully verified + /// @param caller The address that called the verification function (TEE address) + /// @param workloadId The workload identifier of the TEE + /// @param blockNumber The block number when the verification occurred + /// @param version The flashtestation protocol version used + /// @param blockContentHash The hash of the block content + /// @param commitHash The git commit hash associated with the workload + event BlockBuilderProofVerified( + address caller, + bytes32 workloadId, + uint256 blockNumber, + uint8 version, + bytes32 blockContentHash, + string commitHash + ); + + // ============ Errors ============ + + /// @notice Emitted when the registry is the 0x0 address + error InvalidRegistry(); + /// @notice Emitted when a workload to be added is already in the policy + error WorkloadAlreadyInPolicy(); + /// @notice Emitted when a workload to be removed is not in the policy + error WorkloadNotInPolicy(); + /// @notice Emitted when the address is not in the approvedWorkloads mapping + error UnauthorizedBlockBuilder(address caller); + /// @notice Emitted when the nonce is invalid + error InvalidNonce(uint256 expected, uint256 provided); + /// @notice Emitted when the commit hash is empty + error EmptyCommitHash(); + /// @notice Emitted when the source locators array is empty + error EmptySourceLocators(); + + // ============ Functions ============ + + /// @notice Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract + /// @param _initialOwner The address of the initial owner of the contract + /// @param _registry The address of the registry contract + function initialize(address _initialOwner, address _registry) external; + + /// @notice Verify a block builder proof with a Flashtestation Transaction + /// @param version The version of the flashtestation's protocol used to generate the block builder proof + /// @param blockContentHash The hash of the block content + /// @notice This function will only succeed if the caller is a registered TEE-controlled address from an attested TEE + /// and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`) + /// @notice The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. + /// See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/77fc19f785eeeb9b4eb5fb08463bc556dec2c837/specs/flashtestations.md) for more details + /// @dev If you do not want to deal with the operational difficulties of keeping your TEE-controlled + /// addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs + /// more gas, but allows any EOA to submit a block builder proof on behalf of a TEE + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + + /// @notice Verify a block builder proof with a Flashtestation Transaction using EIP-712 signatures + /// @notice This function allows any EOA to submit a block builder proof on behalf of a TEE + /// @notice The TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address + /// whose associated workload is approved under this policy + /// @dev This function is useful if you do not want to deal with the operational difficulties of keeping your + /// TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost + /// more gas than the non-EIP-712 verifyBlockBuilderProof function + /// @param version The version of the flashtestation's protocol used to generate the block builder proof + /// @param blockContentHash The hash of the block content + /// @param nonce The nonce to use for the EIP-712 signature + /// @param eip712Sig The EIP-712 signature of the verification message + function permitVerifyBlockBuilderProof( + uint8 version, + bytes32 blockContentHash, + uint256 nonce, + bytes calldata eip712Sig + ) external; + + /// @notice Check if this TEE-controlled address has registered a valid TEE workload with the registry, and + /// if the workload is approved under this policy + /// @param teeAddress The TEE-controlled address + /// @return allowed True if the TEE is using an approved workload in the policy + /// @return workloadId The workloadId of the TEE that is using an approved workload in the policy, or 0 if + /// the TEE is not using an approved workload in the policy + function isAllowedPolicy(address teeAddress) external view returns (bool, WorkloadId workloadId); + + /// @notice Application specific mapping of registration data to a workload identifier + /// @dev Think of the workload identifier as the version of the application for governance. + /// The workloadId verifiably maps to a version of source code that builds the TEE VM image + /// @param registration The registration data from a TEE device + /// @return workloadId The computed workload identifier + function workloadIdForTDRegistration(IFlashtestationRegistry.RegisteredTEE memory registration) + external + pure + returns (WorkloadId); + + /// @notice Add a workload to a policy (governance only) + /// @notice Only the owner of this contract can add workloads to the policy + /// and it is the responsibility of the owner to ensure that the workload is valid + /// otherwise the address associated with this workload has full power to do anything + /// who's authorization is based on this policy + /// @dev The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy + /// to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, + /// op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead + /// we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever + /// it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so + /// we can also assume the owner will not add a commit hash that is not associated with the workloadId. If + /// the owner did act maliciously, this can easily be determined offchain by an honest actor building the + /// TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the + /// workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, + /// this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a + /// source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image + /// identified by the workloadId. + /// @param workloadId The workload identifier + /// @param commitHash The 40-character hexadecimal commit hash of the git repository + /// whose source code is used to build the TEE image identified by the workloadId + /// @param sourceLocators An array of URIs pointing to the source code + function addWorkloadToPolicy(WorkloadId workloadId, string calldata commitHash, string[] calldata sourceLocators) + external; + + /// @notice Remove a workload from a policy (governance only) + /// @param workloadId The workload identifier + function removeWorkloadFromPolicy(WorkloadId workloadId) external; + + /// @notice Get the metadata for a workload + /// @param workloadId The workload identifier to query + /// @return The metadata associated with the workload + function getWorkloadMetadata(WorkloadId workloadId) external view returns (WorkloadMetadata memory); + + /// @notice Computes the digest for the EIP-712 signature + /// @param structHash The struct hash for the EIP-712 signature + /// @return The digest for the EIP-712 signature + function getHashedTypeDataV4(bytes32 structHash) external view returns (bytes32); + + /// @notice Computes the struct hash for the EIP-712 signature + /// @param version The version of the flashtestation's protocol + /// @param blockContentHash The hash of the block content + /// @param nonce The nonce to use for the EIP-712 signature + /// @return The struct hash for the EIP-712 signature + function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce) + external + pure + returns (bytes32); + + /// @notice Returns the domain separator for the EIP-712 signature + /// @dev This is useful for when both onchain and offchain users want to compute the domain separator + /// for the EIP-712 signature, and then use it to verify the signature + /// @return The domain separator for the EIP-712 signature + function domainSeparator() external view returns (bytes32); + + // ============ Auto-generated getters for public state ============ + + /// @notice Mapping from workloadId to its metadata (commit hash and source locators) + /// @dev This is only updateable by governance (i.e. the owner) of the Policy contract + /// Adding and removing a workload is O(1). + /// This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists + /// in the mapping + function getApprovedWorkloads(bytes32 workloadId) + external + view + returns (string memory commitHash, string[] memory sourceLocators); + + /// @notice Address of the FlashtestationRegistry contract that verifies TEE quotes + function registry() external view returns (address); + + /// @notice Tracks nonces for EIP-712 signatures to prevent replay attacks + function nonces(address teeAddress) external view returns (uint256); + + /// @notice EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function + function VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH() external view returns (bytes32); +} diff --git a/src/interfaces/IFlashtestationRegistry.sol b/src/interfaces/IFlashtestationRegistry.sol index c2f0ecf..3d1ccaf 100644 --- a/src/interfaces/IFlashtestationRegistry.sol +++ b/src/interfaces/IFlashtestationRegistry.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.28; +pragma solidity ^0.8.20; +import {IAttestation} from "./IAttestation.sol"; import {TD10ReportBody} from "automata-dcap-attestation/contracts/types/V4Structs.sol"; /** @@ -9,7 +10,7 @@ import {TD10ReportBody} from "automata-dcap-attestation/contracts/types/V4Struct * identities and configurations using Automata's Intel DCAP attestation */ interface IFlashtestationRegistry { - // TEE identity and status tracking + /// @notice TEE identity and status tracking struct RegisteredTEE { bool isValid; // true upon first registration, and false after a quote invalidation bytes rawQuote; // The raw quote from the TEE device, which is stored to allow for future quote quote invalidation @@ -18,22 +19,199 @@ interface IFlashtestationRegistry { bytes32 quoteHash; // keccak256 hash of rawQuote for caching purposes } - // Events + // ============ Events ============ + + /// @notice Emitted when a TEE service is registered + /// @param teeAddress The address of the TEE service + /// @param rawQuote The raw quote from the TEE device + /// @param alreadyExists Whether the TEE service is already registered event TEEServiceRegistered(address teeAddress, bytes rawQuote, bool alreadyExists); + /// @notice Emitted when a TEE service is invalidated + /// @param teeAddress The address of the TEE service event TEEServiceInvalidated(address teeAddress); + /// @notice Emitted when a previous signature is invalidated + /// @param teeAddress The address of the TEE service + /// @param invalidatedNonce The nonce of the invalidated signature event PreviousSignatureInvalidated(address teeAddress, uint256 invalidatedNonce); - // Errors + // ============ Errors ============ + + /// @notice Emitted when the attestation contract is the 0x0 address error InvalidAttestationContract(); + /// @notice Emitted when the signature is expired because the deadline has passed error ExpiredSignature(uint256 deadline); + /// @notice Emitted when the quote is invalid according to the Automata DCAP Attestation contract error InvalidQuote(bytes output); + /// @notice Emitted when the report data length is too short error InvalidReportDataLength(uint256 length); + /// @notice Emitted when the registration data hash does not match the expected hash error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); + /// @notice Emitted when the byte size is exceeded error ByteSizeExceeded(uint256 size); + /// @notice Emitted when the TEE service is already registered when registering error TEEServiceAlreadyRegistered(address teeAddress); + /// @notice Emitted when the sender must match the TEE address error SenderMustMatchTEEAddress(address sender, address teeAddress); + /// @notice Emitted when the TEE service is not registered error TEEServiceNotRegistered(address teeAddress); + /// @notice Emitted when the TEE service is already invalid when trying to invalidate a TEE registration error TEEServiceAlreadyInvalid(address teeAddress); + /// @notice Emitted when the TEE service is still valid when trying to invalidate a TEE registration error TEEIsStillValid(address teeAddress); + /// @notice Emitted when the nonce is invalid when verifying a signature error InvalidNonce(uint256 expected, uint256 provided); + + // ============ Functions ============ + + /** + * Initializer to set the Automata DCAP Attestation contract, which verifies TEE quotes + * @param owner The address of the initial owner of the contract, who is able to upgrade the contract + * @param _attestationContract The address of the Automata DCAP attestation contract, used to verify TEE quotes + */ + function initialize(address owner, address _attestationContract) external; + + /** + * @notice Registers a TEE workload with a specific TEE-controlled address in the FlashtestationRegistry + * @notice The TEE must be registered with a quote whose validity is verified by the attestationContract + * @dev In order to mitigate DoS attacks, the quote must be less than 20KB + * @dev This is a costly operation (5 million gas) and should be used sparingly. + * @param rawQuote The raw quote from the TEE device. Must be a V4 TDX quote + * @param extendedRegistrationData Abi-encoded application specific attested data, reserved for future upgrades + */ + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external payable; + + /** + * @notice Registers a TEE workload with a specific TEE-controlled address using EIP-712 signatures + * @notice The TEE must be registered with a quote whose validity is verified by the attestationContract + * @dev In order to mitigate DoS attacks, the quote must be less than 20KB + * @dev This function exists so that the TEE does not need to be funded with gas for transaction fees, and + * instead can rely on any EOA to execute the transaction, but still only allow quotes from attested TEEs + * @dev Replay is implicitly shielded against replay attacks through the transaction's nonce (TEE must sign the new nonce) + * @param rawQuote The raw quote from the TEE device. Must be a V4 TDX quote + * @param extendedRegistrationData Abi-encoded application specific attested data, this is arbitrary app-related + * data that the app wants to associate with the TEE-controlled address. Even though it's passed in as a parameter, + * we can trust that it comes from the TEE because we verify that the hash derived from all of the variables in + * extendedRegistrationData matches the hash in the TDX report data. + * @param nonce The nonce to use for the EIP-712 signature (to prevent replay attacks) + * @param deadline The blocktime after which this signature is no longer valid + * @param signature The EIP-712 signature of the registration message + */ + function permitRegisterTEEService( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline, + bytes calldata signature + ) external payable; + + /** + * @notice Fetches only the validity status and quote hash for a given TEE address + * @dev This is a gas-optimized version of getRegistration that only returns the minimal data + * needed for caching optimizations in policy contracts + * @param teeAddress The TEE-controlled address to check + * @return isValid True if the TEE is registered and the attestation is valid + * @return registeredTEE The registered TEE + */ + function getRegistration(address teeAddress) + external + view + returns (bool isValid, RegisteredTEE memory registeredTEE); + + /** + * @notice Fetches only the validity status and quote hash for a given TEE address + * @dev This is a gas-optimized version of getRegistration that only returns the minimal data + * needed for caching optimizations in policy contracts + * @param teeAddress The TEE-controlled address to check + * @return isValid True if the TEE is registered and the attestation is valid + * @return quoteHash The keccak256 hash of the raw quote + */ + function getRegistrationStatus(address teeAddress) external view returns (bool isValid, bytes32 quoteHash); + + /** + * @notice Invalidates the attestation of a TEE + * @dev This is a costly operation (5 million gas) and should be used sparingly. + * @dev Will always revert except if the attestation is valid and the attestation re-verification + * fails. This is to prevent a user needlessly calling this function and for a no-op to occur + * @dev This function exists to handle an important security requirement: occasionally Intel + * will release a new set of DCAP Endorsements for a particular TEE setup (for instance if a + * TDX vulnerability was discovered), which invalidates all prior quotes generated by that TEE. + * By invalidates we mean that the outputs generated by the TEE-controlled address associated + * with these invalid quotes are no longer secure and cannot be relied upon. This fact needs to be + * reflected onchain, so that any upstream contracts that try to call `getRegistration` will + * correctly return `false` for the TEE-controlled addresses associated with these invalid quotes. + * This is a security requirement to ensure that no downstream contracts can be exploited by + * a malicious TEE that has been compromised + * @dev Note: this function is callable by anyone, so that offchain monitoring services can + * quickly mark TEEs as invalid + * @param teeAddress The TEE-controlled address to invalidate + */ + function invalidateAttestation(address teeAddress) external payable; + + /** + * @notice Allows a user to increment their EIP-712 signature nonce, invalidating any previously signed but unexecuted permit signatures. + * @dev This function provides a way for users to proactively invalidate old signatures by incrementing their nonce, + * without needing to execute a valid permit. + * This is particularly useful if a user suspects a signature may have been compromised or simply wants to ensure + * that any outstanding, unused signatures with the current nonce can no longer be executed. + * @dev The function requires the provided nonce to match the user's current nonce, as a defense against the caller + * mistakenly invalidating a nonce that they did not intend to invalidate + * @param _nonce The expected current nonce for the caller; must match the stored nonce + */ + function invalidatePreviousSignature(uint256 _nonce) external; + + /** + * @notice Computes the digest for the EIP-712 signature + * @dev This is useful for when both onchain and offchain users want to compute the digest + * for the EIP-712 signature, and then use it to verify the signature + * @param structHash The struct hash for the EIP-712 signature + * @return The digest for the EIP-712 signature + */ + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + /** + * @notice Computes the struct hash for the EIP-712 signature + * @dev This is useful for when both onchain and offchain users want to compute the struct hash + * for the EIP-712 signature, and then use it to verify the signature + * @param rawQuote The raw quote from the TEE device + * @param extendedRegistrationData Abi-encoded attested data, application specific + * @param nonce The nonce to use for the EIP-712 signature + * @param deadline The blocktime after which this signature is no longer valid + * @return The struct hash for the EIP-712 signature + */ + function computeStructHash( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline + ) external pure returns (bytes32); + + /** + * @notice Returns the domain separator for the EIP-712 signature + * @dev This is useful for when both onchain and offchain users want to compute the domain separator + * for the EIP-712 signature, and then use it to verify the signature + * @return The domain separator for the EIP-712 signature + */ + function domainSeparator() external view returns (bytes32); + + /** + * @notice Returns the current nonce for a given TEE-controlled address + * @dev This is used in the permitRegisterTEEService function to prevent replay attacks + * @param teeAddress The TEE-controlled address + * @return The current nonce + */ + function nonces(address teeAddress) external view returns (uint256); + + /** + * @notice EIP-712 Typehash, used in the permitRegisterTEEService function + * @dev This is used in the permitRegisterTEEService function to prevent replay attacks + * @return The EIP-712 Typehash + */ + function REGISTER_TYPEHASH() external view returns (bytes32); + + /** + * @notice Returns the address of the Automata DCAP Attestation contract + * @dev This is used to verify TEE quotes + * @return The address of the Automata DCAP Attestation contract + */ + function attestationContract() external view returns (IAttestation); } diff --git a/test/BlockBuilderPolicy.t.sol b/test/BlockBuilderPolicy.t.sol index 8321220..46c9b9b 100644 --- a/test/BlockBuilderPolicy.t.sol +++ b/test/BlockBuilderPolicy.t.sol @@ -5,7 +5,8 @@ import {Test, console} from "forge-std/Test.sol"; import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {BlockBuilderPolicy, WorkloadId, WorkloadMetadata} from "../src/BlockBuilderPolicy.sol"; +import {BlockBuilderPolicy} from "../src/BlockBuilderPolicy.sol"; +import {IBlockBuilderPolicy, WorkloadId} from "../src/interfaces/IBlockBuilderPolicy.sol"; import {FlashtestationRegistry} from "../src/FlashtestationRegistry.sol"; import {IFlashtestationRegistry} from "../src/interfaces/IFlashtestationRegistry.sol"; import {MockQuote} from "../test/FlashtestationRegistry.t.sol"; @@ -144,7 +145,7 @@ contract BlockBuilderPolicyTest is Test { registry = FlashtestationRegistry(registryProxy); address policyImplementation = address(new BlockBuilderPolicy()); - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.InvalidRegistry.selector)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.InvalidRegistry.selector)); UnsafeUpgrades.deployUUPSProxy( policyImplementation, abi.encodeCall(BlockBuilderPolicy.initialize, (owner, address(0))) ); @@ -154,7 +155,7 @@ contract BlockBuilderPolicyTest is Test { // Add a workload policy.addWorkloadToPolicy(mockf200.workloadId, mockf200.commitHash, mockf200.sourceLocators); - WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); + IBlockBuilderPolicy.WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); assertEq(workload.commitHash, mockf200.commitHash); assertEq(workload.sourceLocators.length, 2); assertEq(workload.sourceLocators[0], mockf200.sourceLocators[0]); @@ -169,7 +170,7 @@ contract BlockBuilderPolicyTest is Test { mockf200WithDifferentWorkloadId.sourceLocators ); - WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); + IBlockBuilderPolicy.WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); assertEq(workload.commitHash, mockf200.commitHash); assertEq(workload.sourceLocators.length, 2); assertEq(workload.sourceLocators[0], mockf200.sourceLocators[0]); @@ -184,17 +185,17 @@ contract BlockBuilderPolicyTest is Test { function test_addWorkloadToPolicy_reverts_if_duplicate() public { policy.addWorkloadToPolicy(mockf200.workloadId, mockf200.commitHash, mockf200.sourceLocators); - vm.expectRevert(BlockBuilderPolicy.WorkloadAlreadyInPolicy.selector); + vm.expectRevert(IBlockBuilderPolicy.WorkloadAlreadyInPolicy.selector); policy.addWorkloadToPolicy(mockf200.workloadId, mockf200.commitHash, mockf200.sourceLocators); } function test_addWorkloadToPolicy_reverts_if_empty_commit_hash() public { - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.EmptyCommitHash.selector, 0)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.EmptyCommitHash.selector, 0)); policy.addWorkloadToPolicy(mockf200.workloadId, "", mockf200.sourceLocators); } function test_addWorkloadToPolicy_reverts_if_empty_source_locators() public { - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.EmptySourceLocators.selector, 0)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.EmptySourceLocators.selector, 0)); policy.addWorkloadToPolicy(mockf200.workloadId, mockf200.commitHash, new string[](0)); } @@ -207,7 +208,7 @@ contract BlockBuilderPolicyTest is Test { function test_removeWorkloadFromPolicy() public { policy.addWorkloadToPolicy(mockf200.workloadId, mockf200.commitHash, mockf200.sourceLocators); policy.removeWorkloadFromPolicy(mockf200.workloadId); - WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); + IBlockBuilderPolicy.WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200.workloadId); assertEq(workload.commitHash, ""); assertEq(workload.sourceLocators.length, 0); } @@ -220,7 +221,8 @@ contract BlockBuilderPolicyTest is Test { mockf200WithDifferentWorkloadId.sourceLocators ); policy.removeWorkloadFromPolicy(mockf200.workloadId); - WorkloadMetadata memory workload = policy.getWorkloadMetadata(mockf200WithDifferentWorkloadId.workloadId); + IBlockBuilderPolicy.WorkloadMetadata memory workload = + policy.getWorkloadMetadata(mockf200WithDifferentWorkloadId.workloadId); assertEq(workload.commitHash, mockf200WithDifferentWorkloadId.commitHash); assertEq(workload.sourceLocators.length, 2); assertEq(workload.sourceLocators[0], mockf200WithDifferentWorkloadId.sourceLocators[0]); @@ -256,7 +258,7 @@ contract BlockBuilderPolicyTest is Test { } function test_removeWorkloadFromPolicy_reverts_if_not_present() public { - vm.expectRevert(BlockBuilderPolicy.WorkloadNotInPolicy.selector); + vm.expectRevert(IBlockBuilderPolicy.WorkloadNotInPolicy.selector); policy.removeWorkloadFromPolicy(mockf200.workloadId); } @@ -446,7 +448,7 @@ contract BlockBuilderPolicyTest is Test { vm.prank(mockf200.teeAddress); vm.expectRevert( - abi.encodeWithSelector(BlockBuilderPolicy.UnauthorizedBlockBuilder.selector, mockf200.teeAddress) + abi.encodeWithSelector(IBlockBuilderPolicy.UnauthorizedBlockBuilder.selector, mockf200.teeAddress) ); policy.verifyBlockBuilderProof(1, bytes32(0)); } @@ -461,8 +463,13 @@ contract BlockBuilderPolicyTest is Test { bytes32 blockContentHash = bytes32(hex"1234"); vm.expectEmit(address(policy)); - emit BlockBuilderPolicy.BlockBuilderProofVerified( - mockf200.teeAddress, actualWorkloadId, block.number, 1, blockContentHash, mockf200.commitHash + emit IBlockBuilderPolicy.BlockBuilderProofVerified( + mockf200.teeAddress, + WorkloadId.unwrap(actualWorkloadId), + block.number, + 1, + blockContentHash, + mockf200.commitHash ); vm.prank(mockf200.teeAddress); @@ -509,8 +516,13 @@ contract BlockBuilderPolicyTest is Test { // Expect the event to be emitted vm.expectEmit(address(policy)); - emit BlockBuilderPolicy.BlockBuilderProofVerified( - teeAddress, actualWorkloadId, block.number, version, blockContentHash, mock46f6.commitHash + emit IBlockBuilderPolicy.BlockBuilderProofVerified( + teeAddress, + WorkloadId.unwrap(actualWorkloadId), + block.number, + version, + blockContentHash, + mock46f6.commitHash ); // Call the function @@ -540,8 +552,13 @@ contract BlockBuilderPolicyTest is Test { // Expect the event to be emitted vm.expectEmit(address(policy)); - emit BlockBuilderPolicy.BlockBuilderProofVerified( - teeAddress, actualWorkloadId, block.number, version, blockContentHash, mock46f6.commitHash + emit IBlockBuilderPolicy.BlockBuilderProofVerified( + teeAddress, + WorkloadId.unwrap(actualWorkloadId), + block.number, + version, + blockContentHash, + mock46f6.commitHash ); // Call the function @@ -559,8 +576,13 @@ contract BlockBuilderPolicyTest is Test { // Expect the event to be emitted vm.expectEmit(address(policy)); - emit BlockBuilderPolicy.BlockBuilderProofVerified( - teeAddress, actualWorkloadId, block.number, version, blockContentHash, mock46f6.commitHash + emit IBlockBuilderPolicy.BlockBuilderProofVerified( + teeAddress, + WorkloadId.unwrap(actualWorkloadId), + block.number, + version, + blockContentHash, + mock46f6.commitHash ); // Call the function @@ -581,7 +603,7 @@ contract BlockBuilderPolicyTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(invalid_pk, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.UnauthorizedBlockBuilder.selector, invalid_signer)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.UnauthorizedBlockBuilder.selector, invalid_signer)); policy.permitVerifyBlockBuilderProof(version, blockContentHash, 0, signature); } @@ -594,7 +616,7 @@ contract BlockBuilderPolicyTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(mock46f6.privateKey, digest); bytes memory signature = abi.encodePacked(r, s, v); - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.InvalidNonce.selector, 0, 1)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.InvalidNonce.selector, 0, 1)); policy.permitVerifyBlockBuilderProof(version, blockContentHash, 1, signature); } @@ -619,7 +641,7 @@ contract BlockBuilderPolicyTest is Test { policy.permitVerifyBlockBuilderProof(version, blockContentHash, 0, signature); // Try to replay the same signature - vm.expectRevert(abi.encodeWithSelector(BlockBuilderPolicy.InvalidNonce.selector, 1, 0)); + vm.expectRevert(abi.encodeWithSelector(IBlockBuilderPolicy.InvalidNonce.selector, 1, 0)); policy.permitVerifyBlockBuilderProof(version, blockContentHash, 0, signature); } }