diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index fcbae53..239795b 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit fcbae5394ae8ad52d8e580a3477db99814b9d565 +Subproject commit 239795bea728c8dca4deb6c66856dd58a6991112 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index aa677e9..dd89bed 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit aa677e9d28ed78fc427ec47ba2baef2030c58e7c +Subproject commit dd89bed956f7ca2f72f51b62bd926f3955695fee diff --git a/scripts/DeployAllTEEVerifiers.s.sol b/scripts/DeployAllTEEVerifiers.s.sol index b03228e..4aadef0 100644 --- a/scripts/DeployAllTEEVerifiers.s.sol +++ b/scripts/DeployAllTEEVerifiers.s.sol @@ -45,6 +45,11 @@ contract DeployAllTEEVerifiers is Script { // Owner address for the auto-deployed ProxyAdmin contracts address proxyAdminOwner = vm.envOr("PROXY_ADMIN_OWNER", msg.sender); + // Optional guardian addresses (comma-separated) + // Uses Forge's built-in envOr with comma delimiter to parse address arrays + address[] memory emptyGuardians = new address[](0); + address[] memory guardians = vm.envOr("GUARDIANS", ",", emptyGuardians); + // ============ Step 1: Deploy TEEVerifier ============ // Deploy implementation EspressoTEEVerifier teeVerifierImpl = new EspressoTEEVerifier(); @@ -131,6 +136,14 @@ contract DeployAllTEEVerifiers is Script { "TEEVerifier updated with SGX and Nitro verifier addresses" ); + // Add guardians if provided + for (uint256 i = 0; i < guardians.length; i++) { + if (guardians[i] != address(0)) { + teeVerifier.addGuardian(guardians[i]); + console2.log("Added guardian:", guardians[i]); + } + } + vm.stopBroadcast(); string memory chainId = vm.toString(block.chainid); diff --git a/scripts/DeployTEEVerifier.s.sol b/scripts/DeployTEEVerifier.s.sol index b2abdcf..9bda7bf 100644 --- a/scripts/DeployTEEVerifier.s.sol +++ b/scripts/DeployTEEVerifier.s.sol @@ -27,6 +27,11 @@ contract DeployTEEVerifier is Script { // If not set, defaults to msg.sender address proxyAdminOwner = vm.envOr("PROXY_ADMIN_OWNER", msg.sender); + // Optional guardian addresses (comma-separated) + // Uses Forge's built-in envOr with comma delimiter to parse address arrays + address[] memory emptyGuardians = new address[](0); + address[] memory guardians = vm.envOr("GUARDIANS", ",", emptyGuardians); + // 1. Deploy TEEVerifier implementation EspressoTEEVerifier teeVerifierImpl = new EspressoTEEVerifier(); console2.log( @@ -51,6 +56,15 @@ contract DeployTEEVerifier is Script { ); console2.log("TEEVerifier proxy deployed at:", address(proxy)); + // Add guardians if provided + EspressoTEEVerifier teeVerifier = EspressoTEEVerifier(address(proxy)); + for (uint256 i = 0; i < guardians.length; i++) { + if (guardians[i] != address(0)) { + teeVerifier.addGuardian(guardians[i]); + console2.log("Added guardian:", guardians[i]); + } + } + vm.stopBroadcast(); // Save deployment artifacts (outside of broadcast to avoid gas costs) diff --git a/src/EspressoNitroTEEVerifier.sol b/src/EspressoNitroTEEVerifier.sol index ad6cc7c..644d49d 100644 --- a/src/EspressoNitroTEEVerifier.sol +++ b/src/EspressoNitroTEEVerifier.sol @@ -117,7 +117,7 @@ contract EspressoNitroTEEVerifier is IEspressoNitroTEEVerifier, TEEHelper { } function _setNitroEnclaveVerifier(address nitroEnclaveVerifier_) internal { - if (nitroEnclaveVerifier_ == address(0)) { + if (nitroEnclaveVerifier_ == address(0) || nitroEnclaveVerifier_.code.length == 0) { revert InvalidNitroEnclaveVerifierAddress(); } _nitroLayout().nitroEnclaveVerifier = INitroEnclaveVerifier(nitroEnclaveVerifier_); diff --git a/src/EspressoTEEVerifier.sol b/src/EspressoTEEVerifier.sol index 4a1c5cd..5030903 100644 --- a/src/EspressoTEEVerifier.sol +++ b/src/EspressoTEEVerifier.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {OwnableWithGuardiansUpgradeable} from "./OwnableWithGuardiansUpgradeable.sol"; import {IEspressoSGXTEEVerifier} from "./interface/IEspressoSGXTEEVerifier.sol"; import {IEspressoNitroTEEVerifier} from "./interface/IEspressoNitroTEEVerifier.sol"; import {IEspressoTEEVerifier} from "./interface/IEspressoTEEVerifier.sol"; @@ -13,7 +13,7 @@ import {ServiceType} from "./types/Types.sol"; * @author Espresso Systems (https://espresso.systems) * @notice This contract is used to resgister a signer which has been attested by the TEE */ -contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { +contract EspressoTEEVerifier is OwnableWithGuardiansUpgradeable, IEspressoTEEVerifier { /// @custom:storage-location erc7201:espresso.storage.EspressoTEEVerifier struct EspressoTEEVerifierStorage { IEspressoSGXTEEVerifier espressoSGXTEEVerifier; @@ -42,8 +42,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { EspressoTEEVerifierStorage storage $ = _layout(); $.espressoSGXTEEVerifier = _espressoSGXTEEVerifier; $.espressoNitroTEEVerifier = _espressoNitroTEEVerifier; - __Ownable2Step_init(); - _transferOwnership(_owner); + __OwnableWithGuardians_init(_owner); } /** @@ -118,9 +117,9 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { } } - /* - @notice Set the EspressoSGXTEEVerifier - @param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier + /** + * @notice Set the EspressoSGXTEEVerifier + * @param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier */ function setEspressoSGXTEEVerifier(IEspressoSGXTEEVerifier _espressoSGXTEEVerifier) public @@ -149,7 +148,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { } /** - * @notice Allows the owner to set enclave hashes + * @notice Allows the owner or guardian to set enclave hashes * @param enclaveHash The enclave hash to set * @param valid Whether the enclave hash is valid or not * @param teeType The type of TEE @@ -157,7 +156,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { */ function setEnclaveHash(bytes32 enclaveHash, bool valid, TeeType teeType, ServiceType service) external - onlyOwner + onlyGuardianOrOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { @@ -168,7 +167,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { } /** - * @notice Allows the owner to delete enclave hashes + * @notice Allows the owner or guardian to delete enclave hashes * @param enclaveHashes The list of enclave hashes to delete * @param teeType The type of TEE * @param service The service type (BatchPoster or CaffNode) @@ -177,7 +176,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { bytes32[] memory enclaveHashes, TeeType teeType, ServiceType service - ) external onlyOwner { + ) external onlyGuardianOrOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { $.espressoSGXTEEVerifier.deleteEnclaveHashes(enclaveHashes, service); diff --git a/src/OwnableWithGuardiansUpgradeable.sol b/src/OwnableWithGuardiansUpgradeable.sol new file mode 100644 index 0000000..9ff42f9 --- /dev/null +++ b/src/OwnableWithGuardiansUpgradeable.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { + Ownable2StepUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import { + AccessControlEnumerableUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title OwnableWithGuardiansUpgradeable + * @author Espresso Systems (https://espresso.systems) + * @notice Abstract contract combining Ownable2Step with a guardian role system for emergency operations. + * @dev This contract provides: + * - 2-step ownership transfer (transferOwnership + acceptOwnership) + * - Multiple guardian addresses for time-sensitive operations + * - TransparentUpgradeableProxy pattern compatibility + * - EIP-7201 namespaced storage (via OZ upgradeable contracts) + * + * Inheriting contracts must: + * 1. Call __OwnableWithGuardians_init(initialOwner) in their initializer + */ +abstract contract OwnableWithGuardiansUpgradeable is + Initializable, + Ownable2StepUpgradeable, + AccessControlEnumerableUpgradeable +{ + /// @notice Role identifier for guardians who can execute time-sensitive operations + bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); + + /// @notice Emitted when a guardian is added + event GuardianAdded(address indexed guardian); + + /// @notice Emitted when a guardian is removed + event GuardianRemoved(address indexed guardian); + + /// @notice Error thrown when caller is not a guardian + error NotGuardian(address caller); + + /// @notice Error thrown when caller is neither guardian nor owner + error NotGuardianOrOwner(address caller); + + /// @notice Error thrown when trying to add zero address as guardian + error InvalidGuardianAddress(); + + /** + * @dev Initializes the contract with an initial owner. + * @param initialOwner The address that will be set as the initial owner and admin + */ + function __OwnableWithGuardians_init(address initialOwner) internal onlyInitializing { + __Ownable_init(initialOwner); + __AccessControl_init(); + __AccessControlEnumerable_init(); + __OwnableWithGuardians_init_unchained(initialOwner); + } + + function __OwnableWithGuardians_init_unchained(address initialOwner) internal onlyInitializing { + // Grant the initial owner the default admin role for managing guardians + _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); + + // Explicitly set DEFAULT_ADMIN_ROLE as the admin of GUARDIAN_ROLE for clarity + _setRoleAdmin(GUARDIAN_ROLE, DEFAULT_ADMIN_ROLE); + } + + /** + * @notice Modifier that restricts function access to guardian addresses only + */ + modifier onlyGuardian() { + if (!hasRole(GUARDIAN_ROLE, msg.sender)) { + revert NotGuardian(msg.sender); + } + _; + } + + /** + * @notice Modifier that restricts function access to either guardian or owner + * @dev Useful for time-sensitive operations that can be performed by either role + */ + modifier onlyGuardianOrOwner() { + if (!hasRole(GUARDIAN_ROLE, msg.sender) && msg.sender != owner()) { + revert NotGuardianOrOwner(msg.sender); + } + _; + } + + /** + * @notice Adds a new guardian address + * @dev Only callable by the contract owner. Reverts if guardian is zero address. + * No-op if already a guardian. + * @param guardian The address to grant guardian privileges + */ + function addGuardian(address guardian) external onlyOwner { + if (guardian == address(0)) { + revert InvalidGuardianAddress(); + } + if (hasRole(GUARDIAN_ROLE, guardian)) { + return; // Already a guardian, no-op + } + grantRole(GUARDIAN_ROLE, guardian); + emit GuardianAdded(guardian); + } + + /** + * @notice Removes a guardian address + * @dev Only callable by the contract owner. No-op if address is not a guardian. + * @param guardian The address to revoke guardian privileges from + */ + function removeGuardian(address guardian) external onlyOwner { + if (!hasRole(GUARDIAN_ROLE, guardian)) { + return; // Not a guardian, no-op + } + revokeRole(GUARDIAN_ROLE, guardian); + emit GuardianRemoved(guardian); + } + + /** + * @notice Checks if an address is a guardian + * @param account The address to check + * @return bool True if the address is a guardian, false otherwise + */ + function isGuardian(address account) public view returns (bool) { + return hasRole(GUARDIAN_ROLE, account); + } + + /** + * @notice Returns all guardian addresses + * @return address[] Array of all guardian addresses + */ + function getGuardians() public view returns (address[] memory) { + uint256 count = getRoleMemberCount(GUARDIAN_ROLE); + address[] memory guardians = new address[](count); + for (uint256 i = 0; i < count; i++) { + guardians[i] = getRoleMember(GUARDIAN_ROLE, i); + } + return guardians; + } + + /** + * @notice Returns the total number of guardians + * @return uint256 The count of guardian addresses + */ + function guardianCount() public view returns (uint256) { + return getRoleMemberCount(GUARDIAN_ROLE); + } + + /** + * @dev Override required by Solidity for multiple inheritance + * @notice Ensures owner retains admin role even after ownership transfer + */ + function _transferOwnership(address newOwner) internal virtual override { + address previousOwner = owner(); + super._transferOwnership(newOwner); + + // Transfer admin role to new owner + if (previousOwner != address(0)) { + _revokeRole(DEFAULT_ADMIN_ROLE, previousOwner); + } + _grantRole(DEFAULT_ADMIN_ROLE, newOwner); + } +} diff --git a/src/TEEHelper.sol b/src/TEEHelper.sol index de77a34..d754731 100644 --- a/src/TEEHelper.sol +++ b/src/TEEHelper.sol @@ -6,6 +6,7 @@ import {ServiceType} from "./types/Types.sol"; import "./interface/ITEEHelper.sol"; abstract contract TEEHelper is ITEEHelper, Initializable { + /// @custom:storage-location erc7201:espresso.storage.TEEHelper struct TEEHelperStorage { mapping(ServiceType => mapping(bytes32 enclaveHash => bool valid)) registeredEnclaveHashes; mapping(ServiceType => mapping(address signer => bool valid)) registeredServices; diff --git a/test/EspressoNitroTEEVerifier.t.sol b/test/EspressoNitroTEEVerifier.t.sol index 3e39e9f..3c3579e 100644 --- a/test/EspressoNitroTEEVerifier.t.sol +++ b/test/EspressoNitroTEEVerifier.t.sol @@ -68,15 +68,15 @@ contract EspressoNitroTEEVerifierTest is Test { function _deployNitro(address teeVerifier) internal returns (EspressoNitroTEEVerifier) { EspressoNitroTEEVerifier impl = new EspressoNitroTEEVerifier(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); - EspressoNitroTEEVerifier proxied = EspressoNitroTEEVerifier(address(proxy)); - vm.prank(teeVerifier); - proxied.initialize( - teeVerifier, - INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788) // Sepolia Nitro Enclave Verifier address - ); - return proxied; + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall( + EspressoNitroTEEVerifier.initialize, + (teeVerifier, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788)) + ) + ); + return EspressoNitroTEEVerifier(address(proxy)); } /** @@ -310,7 +310,8 @@ contract EspressoNitroTEEVerifierTest is Test { // Test setting Nitro Enclave Verifier address for tee verifier and non-tee verifier function testSetNitroEnclaveVerifierAddress() public { vm.startPrank(adminTEE); - address newVerifierAddress = 0x1234567890123456789012345678901234567890; + // Use the actual Sepolia Nitro Enclave Verifier address which has deployed code + address newVerifierAddress = 0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788; espressoTEEVerifier.setNitroEnclaveVerifier(newVerifierAddress); vm.stopPrank(); // Check that only tee verifier can set the address @@ -331,4 +332,55 @@ contract EspressoNitroTEEVerifierTest is Test { adminTEE, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788) ); } + + function testGuardianCanSetEnclaveHash() public { + address guardian = address(0x777); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // Guardian should be able to set enclave hash via TEEVerifier + bytes32 newHash = bytes32(uint256(55_555)); + vm.prank(guardian); + espressoTEEVerifier.setEnclaveHash( + newHash, true, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify the hash was set + assertTrue(espressoNitroTEEVerifier.registeredEnclaveHash(newHash, ServiceType.CaffNode)); + } + + function testGuardianCanDeleteEnclaveHashes() public { + address guardian = address(0x777); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // First set a hash as owner + bytes32 hashToDelete = bytes32(uint256(44_444)); + vm.prank(adminTEE); + espressoTEEVerifier.setEnclaveHash( + hashToDelete, true, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify it's set + assertTrue( + espressoNitroTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.CaffNode) + ); + + // Guardian should be able to delete it via TEEVerifier + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = hashToDelete; + vm.prank(guardian); + espressoTEEVerifier.deleteEnclaveHashes( + hashes, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify it's deleted + assertFalse( + espressoNitroTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.CaffNode) + ); + } } diff --git a/test/EspressoSGXTEEVerifier.t.sol b/test/EspressoSGXTEEVerifier.t.sol index c04979e..21fad21 100644 --- a/test/EspressoSGXTEEVerifier.t.sol +++ b/test/EspressoSGXTEEVerifier.t.sol @@ -72,12 +72,12 @@ contract EspressoSGXTEEVerifierTest is Test { function _deploySGX(address teeVerifier) internal returns (EspressoSGXTEEVerifier) { EspressoSGXTEEVerifier impl = new EspressoSGXTEEVerifier(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); - EspressoSGXTEEVerifier proxied = EspressoSGXTEEVerifier(address(proxy)); - vm.prank(teeVerifier); - proxied.initialize(teeVerifier, v3QuoteVerifier); - return proxied; + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall(EspressoSGXTEEVerifier.initialize, (teeVerifier, v3QuoteVerifier)) + ); + return EspressoSGXTEEVerifier(address(proxy)); } function testRegisterBatchPoster() public { @@ -405,4 +405,55 @@ contract EspressoSGXTEEVerifierTest is Test { vm.expectRevert(Initializable.InvalidInitialization.selector); espressoSGXTEEVerifier.initialize(adminTEE, v3QuoteVerifier); } + + function testGuardianCanSetEnclaveHash() public { + address guardian = address(0x888); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // Guardian should be able to set enclave hash via TEEVerifier + bytes32 newHash = bytes32(uint256(77_777)); + vm.prank(guardian); + espressoTEEVerifier.setEnclaveHash( + newHash, true, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify the hash was set + assertTrue(espressoSGXTEEVerifier.registeredEnclaveHash(newHash, ServiceType.BatchPoster)); + } + + function testGuardianCanDeleteEnclaveHashes() public { + address guardian = address(0x888); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // First set a hash as owner + bytes32 hashToDelete = bytes32(uint256(66_666)); + vm.prank(adminTEE); + espressoTEEVerifier.setEnclaveHash( + hashToDelete, true, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify it's set + assertTrue( + espressoSGXTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.BatchPoster) + ); + + // Guardian should be able to delete it via TEEVerifier + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = hashToDelete; + vm.prank(guardian); + espressoTEEVerifier.deleteEnclaveHashes( + hashes, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify it's deleted + assertFalse( + espressoSGXTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.BatchPoster) + ); + } } diff --git a/test/EspressoTEEVerifier.t.sol b/test/EspressoTEEVerifier.t.sol index 9820a3b..42cf6c0 100644 --- a/test/EspressoTEEVerifier.t.sol +++ b/test/EspressoTEEVerifier.t.sol @@ -8,6 +8,7 @@ import { import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {OwnableWithGuardiansUpgradeable} from "../src/OwnableWithGuardiansUpgradeable.sol"; import {EspressoTEEVerifier} from "../src/EspressoTEEVerifier.sol"; import {IEspressoTEEVerifier} from "../src/interface/IEspressoTEEVerifier.sol"; import {EspressoSGXTEEVerifier} from "../src/EspressoSGXTEEVerifier.sol"; @@ -437,7 +438,7 @@ contract EspressoTEEVerifierTest is Test { vm.startPrank(fakeAddress); vm.expectRevert( abi.encodeWithSelector( - OwnableUpgradeable.OwnableUnauthorizedAccount.selector, fakeAddress + OwnableWithGuardiansUpgradeable.NotGuardianOrOwner.selector, fakeAddress ) ); espressoTEEVerifier.setEnclaveHash( @@ -445,7 +446,7 @@ contract EspressoTEEVerifierTest is Test { ); vm.expectRevert( abi.encodeWithSelector( - OwnableUpgradeable.OwnableUnauthorizedAccount.selector, fakeAddress + OwnableWithGuardiansUpgradeable.NotGuardianOrOwner.selector, fakeAddress ) ); bytes32[] memory hashes = new bytes32[](1); @@ -530,4 +531,122 @@ contract EspressoTEEVerifierTest is Test { assertEq(address(espressoSGXTEEVerifier), sgxAddr); vm.stopPrank(); } + + function testGuardianCanSetEnclaveHashSGX() public { + address guardian = address(0x999); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // Guardian should be able to set enclave hash + bytes32 newHash = bytes32(uint256(12_345)); + vm.prank(guardian); + espressoTEEVerifier.setEnclaveHash( + newHash, true, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify the hash was set + assertTrue( + espressoTEEVerifier.registeredEnclaveHashes( + newHash, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ) + ); + } + + function testGuardianCanSetEnclaveHashNitro() public { + address guardian = address(0x999); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // Guardian should be able to set enclave hash + bytes32 newHash = bytes32(uint256(54_321)); + vm.prank(guardian); + espressoTEEVerifier.setEnclaveHash( + newHash, true, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify the hash was set + assertTrue( + espressoTEEVerifier.registeredEnclaveHashes( + newHash, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ) + ); + } + + function testGuardianCanDeleteEnclaveHashesSGX() public { + address guardian = address(0x999); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // First set a hash as owner + bytes32 hashToDelete = bytes32(uint256(99_999)); + vm.prank(adminTEE); + espressoTEEVerifier.setEnclaveHash( + hashToDelete, true, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify it's set + assertTrue( + espressoTEEVerifier.registeredEnclaveHashes( + hashToDelete, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ) + ); + + // Guardian should be able to delete it + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = hashToDelete; + vm.prank(guardian); + espressoTEEVerifier.deleteEnclaveHashes( + hashes, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ); + + // Verify it's deleted + assertFalse( + espressoTEEVerifier.registeredEnclaveHashes( + hashToDelete, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster + ) + ); + } + + function testGuardianCanDeleteEnclaveHashesNitro() public { + address guardian = address(0x999); + + // Add guardian as owner + vm.prank(adminTEE); + espressoTEEVerifier.addGuardian(guardian); + + // First set a hash as owner + bytes32 hashToDelete = bytes32(uint256(88_888)); + vm.prank(adminTEE); + espressoTEEVerifier.setEnclaveHash( + hashToDelete, true, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify it's set + assertTrue( + espressoTEEVerifier.registeredEnclaveHashes( + hashToDelete, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ) + ); + + // Guardian should be able to delete it + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = hashToDelete; + vm.prank(guardian); + espressoTEEVerifier.deleteEnclaveHashes( + hashes, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ); + + // Verify it's deleted + assertFalse( + espressoTEEVerifier.registeredEnclaveHashes( + hashToDelete, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode + ) + ); + } } diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol new file mode 100644 index 0000000..4d24604 --- /dev/null +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {OwnableWithGuardiansUpgradeable} from "../src/OwnableWithGuardiansUpgradeable.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import { + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; + +/** + * @title MockGuardedContract + * @notice Concrete implementation of OwnableWithGuardiansUpgradeable for testing + */ +contract MockGuardedContract is OwnableWithGuardiansUpgradeable { + uint256 public value; + uint256 public emergencyValue; + + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __OwnableWithGuardians_init(initialOwner); + } + + function ownerOnlyFunction() external onlyOwner { + value = 100; + } + + function guardianOnlyFunction() external onlyGuardian { + value = 200; + } + + function guardianOrOwnerFunction() external onlyGuardianOrOwner { + emergencyValue = 300; + } + + function publicFunction() external { + value = 400; + } +} + +/** + * @title MockGuardedContractV2 + * @notice Upgraded version for testing TransparentUpgradeableProxy upgradeability + */ +contract MockGuardedContractV2 is OwnableWithGuardiansUpgradeable { + uint256 public value; + uint256 public emergencyValue; + uint256 public newValue; // New field in V2 + + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner) public initializer { + __OwnableWithGuardians_init(initialOwner); + } + + function ownerOnlyFunction() external onlyOwner { + value = 100; + } + + function guardianOnlyFunction() external onlyGuardian { + value = 200; + } + + function guardianOrOwnerFunction() external onlyGuardianOrOwner { + emergencyValue = 300; + } + + function publicFunction() external { + value = 400; + } + + // New function in V2 + function newFunction() external { + newValue = 999; + } +} + +contract OwnableWithGuardiansUpgradeableTest is Test { + MockGuardedContract public implementation; + MockGuardedContract public proxy; + ProxyAdmin public proxyAdmin; + TransparentUpgradeableProxy public transparentProxy; + + address public owner = address(0x1); + address public guardian1 = address(0x2); + address public guardian2 = address(0x3); + address public user = address(0x4); + address public newOwner = address(0x5); + address public proxyAdminOwner = address(0x6); + + event GuardianAdded(address indexed guardian); + event GuardianRemoved(address indexed guardian); + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + function setUp() public { + // Deploy implementation + implementation = new MockGuardedContract(); + + // Deploy proxy and initialize + // In OZ 5.x, TransparentUpgradeableProxy creates its own internal ProxyAdmin + bytes memory initData = + abi.encodeWithSelector(MockGuardedContract.initialize.selector, owner); + transparentProxy = + new TransparentUpgradeableProxy(address(implementation), proxyAdminOwner, initData); + proxy = MockGuardedContract(address(transparentProxy)); + + // Get the ProxyAdmin address using ERC1967 storage slot + // ERC1967 admin slot is: keccak256("eip1967.proxy.admin") - 1 + bytes32 adminSlot = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); + address adminAddress = + address(uint160(uint256(vm.load(address(transparentProxy), adminSlot)))); + proxyAdmin = ProxyAdmin(adminAddress); + } + + // ============ Initialization Tests ============ + + function testInitialization() public view { + assertEq(proxy.owner(), owner); + assertEq(proxy.guardianCount(), 0); + assertTrue(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), owner)); + } + + function testCannotReinitialize() public { + vm.expectRevert(); + proxy.initialize(address(0x999)); + } + + // ============ Guardian Management Tests ============ + + function testAddGuardian() public { + vm.startPrank(owner); + + vm.expectEmit(true, false, false, false); + emit GuardianAdded(guardian1); + proxy.addGuardian(guardian1); + + assertTrue(proxy.isGuardian(guardian1)); + assertEq(proxy.guardianCount(), 1); + + address[] memory guardians = proxy.getGuardians(); + assertEq(guardians.length, 1); + assertEq(guardians[0], guardian1); + + vm.stopPrank(); + } + + function testAddMultipleGuardians() public { + vm.startPrank(owner); + + proxy.addGuardian(guardian1); + proxy.addGuardian(guardian2); + + assertEq(proxy.guardianCount(), 2); + assertTrue(proxy.isGuardian(guardian1)); + assertTrue(proxy.isGuardian(guardian2)); + + address[] memory guardians = proxy.getGuardians(); + assertEq(guardians.length, 2); + + vm.stopPrank(); + } + + function testRemoveGuardian() public { + vm.startPrank(owner); + + proxy.addGuardian(guardian1); + assertTrue(proxy.isGuardian(guardian1)); + + vm.expectEmit(true, false, false, false); + emit GuardianRemoved(guardian1); + proxy.removeGuardian(guardian1); + + assertFalse(proxy.isGuardian(guardian1)); + assertEq(proxy.guardianCount(), 0); + + vm.stopPrank(); + } + + function testOnlyOwnerCanAddGuardian() public { + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + proxy.addGuardian(guardian1); + } + + function testOnlyOwnerCanRemoveGuardian() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + proxy.removeGuardian(guardian1); + } + + function testGuardianCannotAddOtherGuardians() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(guardian1); + vm.expectRevert( + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) + ); + proxy.addGuardian(guardian2); + } + + // ============ Access Control Tests ============ + + function testOwnerOnlyFunction() public { + vm.prank(owner); + proxy.ownerOnlyFunction(); + assertEq(proxy.value(), 100); + } + + function testOwnerOnlyFunctionRevertsForNonOwner() public { + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + proxy.ownerOnlyFunction(); + } + + function testOwnerOnlyFunctionRevertsForGuardian() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(guardian1); + vm.expectRevert( + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) + ); + proxy.ownerOnlyFunction(); + } + + function testGuardianOnlyFunction() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(guardian1); + proxy.guardianOnlyFunction(); + assertEq(proxy.value(), 200); + } + + function testGuardianOnlyFunctionRevertsForNonGuardian() public { + vm.prank(user); + vm.expectRevert( + abi.encodeWithSelector(OwnableWithGuardiansUpgradeable.NotGuardian.selector, user) + ); + proxy.guardianOnlyFunction(); + } + + function testGuardianOnlyFunctionRevertsForOwner() public { + vm.prank(owner); + vm.expectRevert( + abi.encodeWithSelector(OwnableWithGuardiansUpgradeable.NotGuardian.selector, owner) + ); + proxy.guardianOnlyFunction(); + } + + function testGuardianOrOwnerFunctionAsOwner() public { + vm.prank(owner); + proxy.guardianOrOwnerFunction(); + assertEq(proxy.emergencyValue(), 300); + } + + function testGuardianOrOwnerFunctionAsGuardian() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(guardian1); + proxy.guardianOrOwnerFunction(); + assertEq(proxy.emergencyValue(), 300); + } + + function testGuardianOrOwnerFunctionRevertsForUser() public { + vm.prank(user); + vm.expectRevert( + abi.encodeWithSelector( + OwnableWithGuardiansUpgradeable.NotGuardianOrOwner.selector, user + ) + ); + proxy.guardianOrOwnerFunction(); + } + + // ============ Ownership Transfer Tests ============ + + function testTransferOwnership() public { + vm.startPrank(owner); + + vm.expectEmit(true, true, false, false); + emit OwnershipTransferStarted(owner, newOwner); + proxy.transferOwnership(newOwner); + + assertEq(proxy.pendingOwner(), newOwner); + assertEq(proxy.owner(), owner); // Still old owner until accepted + + vm.stopPrank(); + + vm.startPrank(newOwner); + + vm.expectEmit(true, true, false, false); + emit OwnershipTransferred(owner, newOwner); + proxy.acceptOwnership(); + + assertEq(proxy.owner(), newOwner); + assertEq(proxy.pendingOwner(), address(0)); + assertTrue(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), newOwner)); + assertFalse(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), owner)); + + vm.stopPrank(); + } + + function testNewOwnerCanManageGuardians() public { + // Transfer ownership + vm.prank(owner); + proxy.transferOwnership(newOwner); + + vm.prank(newOwner); + proxy.acceptOwnership(); + + // New owner should be able to add guardians + vm.prank(newOwner); + proxy.addGuardian(guardian1); + + assertTrue(proxy.isGuardian(guardian1)); + } + + function testOldOwnerCannotManageGuardiansAfterTransfer() public { + // Transfer ownership + vm.prank(owner); + proxy.transferOwnership(newOwner); + + vm.prank(newOwner); + proxy.acceptOwnership(); + + // Old owner should not be able to add guardians + vm.prank(owner); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, owner)); + proxy.addGuardian(guardian1); + } + + function testRenounceOwnership() public { + vm.prank(owner); + proxy.renounceOwnership(); + + assertEq(proxy.owner(), address(0)); + assertFalse(proxy.hasRole(proxy.DEFAULT_ADMIN_ROLE(), owner)); + } + + // ============ Upgradeability Tests ============ + + function testUpgradeAsOwner() public { + MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); + + vm.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); + + // Test that upgrade worked + MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); + upgradedProxy.newFunction(); + assertEq(upgradedProxy.newValue(), 999); + + // Test that state is preserved + assertEq(upgradedProxy.owner(), owner); + } + + function testUpgradeRevertsForNonOwner() public { + MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); + + vm.prank(user); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); + } + + function testUpgradeRevertsForGuardian() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); + + vm.prank(guardian1); + vm.expectRevert( + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) + ); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); + } + + function testGuardiansPreservedAfterUpgrade() public { + // Add guardians + vm.startPrank(owner); + proxy.addGuardian(guardian1); + proxy.addGuardian(guardian2); + vm.stopPrank(); + + // Upgrade + MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); + vm.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); + + // Check guardians are preserved + MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); + assertEq(upgradedProxy.guardianCount(), 2); + assertTrue(upgradedProxy.isGuardian(guardian1)); + assertTrue(upgradedProxy.isGuardian(guardian2)); + } + + // ============ Edge Cases ============ + + function testGetGuardiansWhenEmpty() public view { + address[] memory guardians = proxy.getGuardians(); + assertEq(guardians.length, 0); + } + + function testIsGuardianReturnsFalseForNonGuardian() public view { + assertFalse(proxy.isGuardian(user)); + } + + function testAddSameGuardianTwice() public { + vm.startPrank(owner); + proxy.addGuardian(guardian1); + + // Adding the same guardian again should not increase count or emit event + proxy.addGuardian(guardian1); + + // Count should still be 1 + assertEq(proxy.guardianCount(), 1); + vm.stopPrank(); + } + + function testRemoveNonGuardian() public { + vm.prank(owner); + // Removing non-guardian should be a no-op (no revert, no event) + proxy.removeGuardian(guardian1); + + assertEq(proxy.guardianCount(), 0); + } + + function testAddZeroAddressAsGuardian() public { + vm.prank(owner); + vm.expectRevert(OwnableWithGuardiansUpgradeable.InvalidGuardianAddress.selector); + proxy.addGuardian(address(0)); + } + + function testCanAddManyGuardians() public { + vm.startPrank(owner); + + // Add 50 guardians - no limit + for (uint160 i = 1; i <= 50; i++) { + proxy.addGuardian(address(i)); + } + + assertEq(proxy.guardianCount(), 50); + + // getGuardians() should still work (though may be gas-intensive) + address[] memory guardians = proxy.getGuardians(); + assertEq(guardians.length, 50); + assertEq(guardians[0], address(1)); + assertEq(guardians[49], address(50)); + + vm.stopPrank(); + } + + function testRenounceOwnershipPreventsGuardianManagement() public { + vm.startPrank(owner); + proxy.addGuardian(guardian1); + proxy.renounceOwnership(); + vm.stopPrank(); + + // No owner, so can't add/remove guardians + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, owner)); + vm.prank(owner); + proxy.addGuardian(guardian2); + } + + function testGuardianOperationsStillWorkAfterOwnershipRenounced() public { + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(owner); + proxy.renounceOwnership(); + + // Guardian should still be able to call guardian functions + vm.prank(guardian1); + proxy.guardianOnlyFunction(); + assertEq(proxy.value(), 200); + } + + function testDirectAdminRoleManipulationIsNotAllowed() public { + bytes32 adminRole = proxy.DEFAULT_ADMIN_ROLE(); + + // Non-admin user cannot grant admin role + vm.prank(user); + vm.expectRevert(); + proxy.grantRole(adminRole, user); + + // Guardian can't grant admin role either + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.prank(guardian1); + vm.expectRevert(); + proxy.grantRole(adminRole, guardian1); + + // Only current admin (owner) can grant admin role + vm.prank(owner); + proxy.grantRole(adminRole, newOwner); + assertTrue(proxy.hasRole(adminRole, newOwner)); + } + + function testDirectGuardianRoleManipulationViaAccessControl() public { + bytes32 guardianRole = proxy.GUARDIAN_ROLE(); + bytes32 adminRole = proxy.DEFAULT_ADMIN_ROLE(); + + // Owner (who has admin role) can use AccessControl's grantRole directly + vm.prank(owner); + proxy.grantRole(guardianRole, guardian1); + assertTrue(proxy.isGuardian(guardian1)); + + // Non-admin cannot grant guardian role - must use proper error encoding + vm.prank(user); + vm.expectRevert( + abi.encodeWithSelector( + bytes4(keccak256("AccessControlUnauthorizedAccount(address,bytes32)")), + user, + adminRole + ) + ); + proxy.grantRole(guardianRole, guardian2); + } + + // ============ Fuzz Tests ============ + + function testFuzz_AddGuardian(address guardian) public { + vm.assume(guardian != address(0)); + vm.assume(guardian != owner); + + vm.prank(owner); + proxy.addGuardian(guardian); + + assertTrue(proxy.isGuardian(guardian)); + assertEq(proxy.guardianCount(), 1); + } + + function testFuzz_RemoveGuardian(address guardian) public { + vm.assume(guardian != address(0)); + vm.assume(guardian != owner); + + vm.startPrank(owner); + proxy.addGuardian(guardian); + assertTrue(proxy.isGuardian(guardian)); + + proxy.removeGuardian(guardian); + assertFalse(proxy.isGuardian(guardian)); + assertEq(proxy.guardianCount(), 0); + vm.stopPrank(); + } + + function testFuzz_GuardianCanCallGuardianFunction(address guardian) public { + vm.assume(guardian != address(0)); + vm.assume(guardian != owner); + + vm.prank(owner); + proxy.addGuardian(guardian); + + vm.prank(guardian); + proxy.guardianOnlyFunction(); + assertEq(proxy.value(), 200); + } + + function testFuzz_NonGuardianCannotCallGuardianFunction(address notGuardian) public { + vm.assume(notGuardian != address(0)); + vm.assume(notGuardian != owner); + + vm.prank(owner); + proxy.addGuardian(guardian1); + + vm.assume(notGuardian != guardian1); + + vm.prank(notGuardian); + vm.expectRevert( + abi.encodeWithSelector( + OwnableWithGuardiansUpgradeable.NotGuardian.selector, notGuardian + ) + ); + proxy.guardianOnlyFunction(); + } +} diff --git a/test/SignerValidation.t.sol b/test/SignerValidation.t.sol index 6082167..2cf41c3 100644 --- a/test/SignerValidation.t.sol +++ b/test/SignerValidation.t.sol @@ -27,14 +27,15 @@ contract SignerValidationTest is Test { ); EspressoNitroTEEVerifier impl = new EspressoNitroTEEVerifier(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); - espressoNitroTEEVerifier = EspressoNitroTEEVerifier(address(proxy)); - - vm.prank(adminTEE); - espressoNitroTEEVerifier.initialize( - adminTEE, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788) + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall( + EspressoNitroTEEVerifier.initialize, + (adminTEE, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788)) + ) ); + espressoNitroTEEVerifier = EspressoNitroTEEVerifier(address(proxy)); vm.prank(adminTEE); espressoNitroTEEVerifier.setEnclaveHash(pcr0Hash, true, ServiceType.BatchPoster); diff --git a/test/TEEHelper.t.sol b/test/TEEHelper.t.sol index 291460b..5b434af 100644 --- a/test/TEEHelper.t.sol +++ b/test/TEEHelper.t.sol @@ -26,11 +26,12 @@ contract TEEHelperTest is Test { function setUp() public { TEEHelperImplementation impl = new TEEHelperImplementation(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall(TEEHelperImplementation.initialize, (initialTEEVerifier)) + ); helper = TEEHelperImplementation(address(proxy)); - vm.prank(initialTEEVerifier); - helper.initialize(initialTEEVerifier); } function testOnlyTEEVerifierCanSetEnclaveHash() public { @@ -50,11 +51,11 @@ contract TEEHelperTest is Test { function testInitializeZeroAddressReverts() public { TEEHelperImplementation impl = new TEEHelperImplementation(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); - TEEHelperImplementation localHelper = TEEHelperImplementation(address(proxy)); - vm.prank(initialTEEVerifier); vm.expectRevert(ITEEHelper.InvalidTEEVerifierAddress.selector); - localHelper.initialize(address(0)); + new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall(TEEHelperImplementation.initialize, (address(0))) + ); } } diff --git a/test/TEEHelper_DoSFix.t.sol b/test/TEEHelper_DoSFix.t.sol index fb6b4f7..7cfd3f6 100644 --- a/test/TEEHelper_DoSFix.t.sol +++ b/test/TEEHelper_DoSFix.t.sol @@ -29,13 +29,15 @@ contract TEEHelperDoSFixTest is Test { owner = address(this); EspressoNitroTEEVerifier impl = new EspressoNitroTEEVerifier(); - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy(address(impl), proxyAdminOwner, ""); - verifier = EspressoNitroTEEVerifier(address(proxy)); - - verifier.initialize( - owner, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788) + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall( + EspressoNitroTEEVerifier.initialize, + (owner, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788)) + ) ); + verifier = EspressoNitroTEEVerifier(address(proxy)); } /**