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); } }