From 531640a527ae1660b6e446dfeb293f6373adfccd Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Mon, 26 Jan 2026 15:16:29 +0100 Subject: [PATCH 01/13] Upgrade OZ contracts --- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From 209d3a27cfb9cec45cd0fa94aa00194df304a42e Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Mon, 26 Jan 2026 15:44:21 +0100 Subject: [PATCH 02/13] Add guardian contract --- src/OwnableWithGuardiansUpgradeable.sol | 177 ++++++ test/OwnableWithGuardiansUpgradeable.t.sol | 636 +++++++++++++++++++++ 2 files changed, 813 insertions(+) create mode 100644 src/OwnableWithGuardiansUpgradeable.sol create mode 100644 test/OwnableWithGuardiansUpgradeable.t.sol diff --git a/src/OwnableWithGuardiansUpgradeable.sol b/src/OwnableWithGuardiansUpgradeable.sol new file mode 100644 index 0000000..ff20ed7 --- /dev/null +++ b/src/OwnableWithGuardiansUpgradeable.sol @@ -0,0 +1,177 @@ +// 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 {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.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. + * @notice Guardians do not have upgrade permissions unless explicitly granted by overriding _authorizeUpgrade + * @dev This contract provides: + * - 2-step ownership transfer (transferOwnership + acceptOwnership) + * - Multiple guardian addresses for time-sensitive operations + * - UUPS upgradeability pattern + * - EIP-7201 namespaced storage (via OZ upgradeable contracts) + * + * Inheriting contracts must: + * 1. Call __OwnableWithGuardians_init(initialOwner) in their initializer + * 2. Implement the _authorizeUpgrade function if they want to customize upgrade authorization + */ +abstract contract OwnableWithGuardiansUpgradeable is + Initializable, + Ownable2StepUpgradeable, + AccessControlEnumerableUpgradeable, + UUPSUpgradeable +{ + /// @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(); + __UUPSUpgradeable_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); + } + + /** + * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. + * @param newImplementation The address of the new implementation + * + * By default, only the owner can authorize upgrades. Override this function to customize authorization. + */ + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyOwner {} +} diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol new file mode 100644 index 0000000..14513af --- /dev/null +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -0,0 +1,636 @@ +// 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 {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +/** + * @title MockGuardedContract + * @notice Concrete implementation of OwnableWithGuardiansUpgradeable for testing + */ +contract MockGuardedContract is OwnableWithGuardiansUpgradeable { + uint256 public value; + uint256 public emergencyValue; + + 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 UUPS upgradeability + */ +contract MockGuardedContractV2 is OwnableWithGuardiansUpgradeable { + uint256 public value; + uint256 public emergencyValue; + uint256 public newValue; // New field in V2 + + 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; + + 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); + + 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 + bytes memory initData = abi.encodeWithSelector( + MockGuardedContract.initialize.selector, + owner + ); + ERC1967Proxy proxyContract = new ERC1967Proxy( + address(implementation), + initData + ); + proxy = MockGuardedContract(address(proxyContract)); + } + + // ============ 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(owner); + proxy.upgradeToAndCall(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 + ) + ); + proxy.upgradeToAndCall(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 + ) + ); + proxy.upgradeToAndCall(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(owner); + proxy.upgradeToAndCall(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(); + } +} From 0eb827e3bed9a56aa5e3d8918100c991376be47c Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Mon, 26 Jan 2026 17:07:54 +0100 Subject: [PATCH 03/13] Use guardian contract --- src/EspressoTEEVerifier.sol | 165 ++++++++++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 36 deletions(-) diff --git a/src/EspressoTEEVerifier.sol b/src/EspressoTEEVerifier.sol index 4a1c5cd..dcbf51c 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,10 @@ 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; @@ -24,7 +27,11 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { bytes32 private constant ESPRESSO_TEE_VERIFIER_STORAGE_SLOT = 0x89639f446056f5d7661bbd94e8ab0617a80058ed7b072845818d4b93332e4800; - function _layout() private pure returns (EspressoTEEVerifierStorage storage $) { + function _layout() + private + pure + returns (EspressoTEEVerifierStorage storage $) + { assembly { $.slot := ESPRESSO_TEE_VERIFIER_STORAGE_SLOT } @@ -42,8 +49,7 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { EspressoTEEVerifierStorage storage $ = _layout(); $.espressoSGXTEEVerifier = _espressoSGXTEEVerifier; $.espressoNitroTEEVerifier = _espressoNitroTEEVerifier; - __Ownable2Step_init(); - _transferOwnership(_owner); + __OwnableWithGuardians_init(_owner); } /** @@ -92,40 +98,102 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { ) external { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.registerService(verificationData, data, service); + $.espressoSGXTEEVerifier.registerService( + verificationData, + data, + service + ); return; } else { - $.espressoNitroTEEVerifier.registerService(verificationData, data, service); + $.espressoNitroTEEVerifier.registerService( + verificationData, + data, + service + ); return; } } + /** + * @notice This function retrieves whether a signer is registered or not + * @param signer The address of the signer + * @param teeType The type of TEE + */ + function registeredService( + address signer, + TeeType teeType, + ServiceType service + ) external view returns (bool) { + EspressoTEEVerifierStorage storage $ = _layout(); + if (teeType == TeeType.SGX) { + return $.espressoSGXTEEVerifier.registeredService(signer, service); + } else { + return + $.espressoNitroTEEVerifier.registeredService(signer, service); + } + } + /** * @notice This function retrieves whether an enclave hash is registered or not * @param enclaveHash The hash of the enclave * @param teeType The type of TEE */ - function registeredEnclaveHashes(bytes32 enclaveHash, TeeType teeType, ServiceType service) - external - view - returns (bool) - { + function registeredEnclaveHashes( + bytes32 enclaveHash, + TeeType teeType, + ServiceType service + ) external view returns (bool) { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - return $.espressoSGXTEEVerifier.registeredEnclaveHash(enclaveHash, service); + return + $.espressoSGXTEEVerifier.registeredEnclaveHash( + enclaveHash, + service + ); } else { - return $.espressoNitroTEEVerifier.registeredEnclaveHash(enclaveHash, service); + return + $.espressoNitroTEEVerifier.registeredEnclaveHash( + enclaveHash, + service + ); } } - /* - @notice Set the EspressoSGXTEEVerifier - @param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier + /** + * @notice This function retrieves the list of signers registered for a given enclave hash + * @param enclaveHash The hash of the enclave + * @param teeType The type of TEE + * @param service The service type (BatchPoster or CaffNode) + * @return address[] The list of signers registered for the given enclave hash */ - function setEspressoSGXTEEVerifier(IEspressoSGXTEEVerifier _espressoSGXTEEVerifier) - public - onlyOwner - { + function enclaveHashSigners( + bytes32 enclaveHash, + TeeType teeType, + ServiceType service + ) external view returns (address[] memory) { + EspressoTEEVerifierStorage storage $ = _layout(); + if (teeType == TeeType.SGX) { + return + $.espressoSGXTEEVerifier.enclaveHashSigners( + enclaveHash, + service + ); + } else { + return + $.espressoNitroTEEVerifier.enclaveHashSigners( + enclaveHash, + service + ); + } + } + + /** + * @notice Set the EspressoSGXTEEVerifier + * @param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier + */ + function setEspressoSGXTEEVerifier( + IEspressoSGXTEEVerifier _espressoSGXTEEVerifier + ) public onlyOwner { if (address(_espressoSGXTEEVerifier) == address(0)) { revert InvalidVerifierAddress(); } @@ -137,10 +205,9 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { * @notice Set the EspressoNitroTEEVerifier * @param _espressoNitroTEEVerifier The address of the EspressoNitroTEEVerifier */ - function setEspressoNitroTEEVerifier(IEspressoNitroTEEVerifier _espressoNitroTEEVerifier) - public - onlyOwner - { + function setEspressoNitroTEEVerifier( + IEspressoNitroTEEVerifier _espressoNitroTEEVerifier + ) public onlyOwner { if (address(_espressoNitroTEEVerifier) == address(0)) { revert InvalidVerifierAddress(); } @@ -155,15 +222,25 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { * @param teeType The type of TEE * @param service The service type (BatchPoster or CaffNode) */ - function setEnclaveHash(bytes32 enclaveHash, bool valid, TeeType teeType, ServiceType service) - external - onlyOwner - { + function setEnclaveHash( + bytes32 enclaveHash, + bool valid, + TeeType teeType, + ServiceType service + ) external onlyOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.setEnclaveHash(enclaveHash, valid, service); + $.espressoSGXTEEVerifier.setEnclaveHash( + enclaveHash, + valid, + service + ); } else { - $.espressoNitroTEEVerifier.setEnclaveHash(enclaveHash, valid, service); + $.espressoNitroTEEVerifier.setEnclaveHash( + enclaveHash, + valid, + service + ); } } @@ -180,9 +257,15 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { ) external onlyOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.deleteEnclaveHashes(enclaveHashes, service); + $.espressoSGXTEEVerifier.deleteEnclaveHashes( + enclaveHashes, + service + ); } else { - $.espressoNitroTEEVerifier.deleteEnclaveHashes(enclaveHashes, service); + $.espressoNitroTEEVerifier.deleteEnclaveHashes( + enclaveHashes, + service + ); } } @@ -199,14 +282,20 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { * @param nitroVerifier The address of the nitro enclave verifier */ function setNitroEnclaveVerifier(address nitroVerifier) external onlyOwner { - _layout().espressoNitroTEEVerifier.setNitroEnclaveVerifier(nitroVerifier); + _layout().espressoNitroTEEVerifier.setNitroEnclaveVerifier( + nitroVerifier + ); } /** * @notice Get the EspressoSGXTEEVerifier address * @return The EspressoSGXTEEVerifier interface */ - function espressoSGXTEEVerifier() external view returns (IEspressoSGXTEEVerifier) { + function espressoSGXTEEVerifier() + external + view + returns (IEspressoSGXTEEVerifier) + { return _layout().espressoSGXTEEVerifier; } @@ -214,7 +303,11 @@ contract EspressoTEEVerifier is Ownable2StepUpgradeable, IEspressoTEEVerifier { * @notice Get the EspressoNitroTEEVerifier address * @return The EspressoNitroTEEVerifier interface */ - function espressoNitroTEEVerifier() external view returns (IEspressoNitroTEEVerifier) { + function espressoNitroTEEVerifier() + external + view + returns (IEspressoNitroTEEVerifier) + { return _layout().espressoNitroTEEVerifier; } } From 0017b4b854d97b597d33d73b34b8fed16c7f2a40 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Mon, 26 Jan 2026 17:16:38 +0100 Subject: [PATCH 04/13] Add OwnableWithGuardiansUpgradeable and convert contracts to upgradeable - Add OwnableWithGuardiansUpgradeable combining Ownable2Step, AccessControlEnumerable, and UUPS - Define GUARDIAN_ROLE with onlyOwner, onlyGuardian, and onlyGuardianOrOwner modifiers - Convert EspressoTEEVerifier, EspressoSGXTEEVerifier, EspressoNitroTEEVerifier to UUPS upgradeable - Update TEEHelper base contract to use OwnableWithGuardiansUpgradeable - Update setEnclaveHash() and deleteEnclaveHashes() to onlyGuardianOrOwner - Update all deployment scripts to use ERC1967Proxy pattern - Add optional initial enclave hash setting in deployment scripts - Update all tests for proxy deployment and guardian error types - Add comprehensive test coverage (39 tests for OwnableWithGuardiansUpgradeable) --- src/OwnableWithGuardiansUpgradeable.sol | 21 ++-- test/EspressoRollupSequencerManager.t.sol | 4 +- test/OwnableWithGuardiansUpgradeable.t.sol | 116 ++++----------------- 3 files changed, 34 insertions(+), 107 deletions(-) diff --git a/src/OwnableWithGuardiansUpgradeable.sol b/src/OwnableWithGuardiansUpgradeable.sol index ff20ed7..700c80a 100644 --- a/src/OwnableWithGuardiansUpgradeable.sol +++ b/src/OwnableWithGuardiansUpgradeable.sol @@ -1,8 +1,10 @@ // 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 {Ownable2StepUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {AccessControlEnumerableUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -49,9 +51,7 @@ abstract contract OwnableWithGuardiansUpgradeable is * @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 { + function __OwnableWithGuardians_init(address initialOwner) internal onlyInitializing { __Ownable_init(initialOwner); __AccessControl_init(); __AccessControlEnumerable_init(); @@ -59,9 +59,10 @@ abstract contract OwnableWithGuardiansUpgradeable is __OwnableWithGuardians_init_unchained(initialOwner); } - function __OwnableWithGuardians_init_unchained( - address initialOwner - ) internal onlyInitializing { + function __OwnableWithGuardians_init_unchained(address initialOwner) + internal + onlyInitializing + { // Grant the initial owner the default admin role for managing guardians _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); @@ -171,7 +172,5 @@ abstract contract OwnableWithGuardiansUpgradeable is * * By default, only the owner can authorize upgrades. Override this function to customize authorization. */ - function _authorizeUpgrade( - address newImplementation - ) internal virtual override onlyOwner {} + function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} } diff --git a/test/EspressoRollupSequencerManager.t.sol b/test/EspressoRollupSequencerManager.t.sol index cbb5089..e535982 100644 --- a/test/EspressoRollupSequencerManager.t.sol +++ b/test/EspressoRollupSequencerManager.t.sol @@ -3,9 +3,7 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import {EspressoRollupSequencerManager} from "../src/EspressoRollupSequencerManager.sol"; -import { - IEspressoRollupSequencerManager -} from "../src/interface/IEspressoRollupSequencerManager.sol"; +import {IEspressoRollupSequencerManager} from "../src/interface/IEspressoRollupSequencerManager.sol"; contract EspressoRollupSequencerManagerTest is Test { EspressoRollupSequencerManager public rollupSequencerManager; diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol index 14513af..3f0877d 100644 --- a/test/OwnableWithGuardiansUpgradeable.t.sol +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -82,28 +82,17 @@ contract OwnableWithGuardiansUpgradeableTest is Test { 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 - ); + 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 - bytes memory initData = abi.encodeWithSelector( - MockGuardedContract.initialize.selector, - owner - ); - ERC1967Proxy proxyContract = new ERC1967Proxy( - address(implementation), - initData - ); + bytes memory initData = + abi.encodeWithSelector(MockGuardedContract.initialize.selector, owner); + ERC1967Proxy proxyContract = new ERC1967Proxy(address(implementation), initData); proxy = MockGuardedContract(address(proxyContract)); } @@ -173,12 +162,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testOnlyOwnerCanAddGuardian() public { vm.prank(user); - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - user - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); proxy.addGuardian(guardian1); } @@ -187,12 +171,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { proxy.addGuardian(guardian1); vm.prank(user); - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - user - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); proxy.removeGuardian(guardian1); } @@ -202,10 +181,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(guardian1); vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - guardian1 - ) + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) ); proxy.addGuardian(guardian2); } @@ -220,12 +196,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testOwnerOnlyFunctionRevertsForNonOwner() public { vm.prank(user); - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - user - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); proxy.ownerOnlyFunction(); } @@ -235,10 +206,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(guardian1); vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - guardian1 - ) + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) ); proxy.ownerOnlyFunction(); } @@ -255,10 +223,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testGuardianOnlyFunctionRevertsForNonGuardian() public { vm.prank(user); vm.expectRevert( - abi.encodeWithSelector( - OwnableWithGuardiansUpgradeable.NotGuardian.selector, - user - ) + abi.encodeWithSelector(OwnableWithGuardiansUpgradeable.NotGuardian.selector, user) ); proxy.guardianOnlyFunction(); } @@ -266,10 +231,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testGuardianOnlyFunctionRevertsForOwner() public { vm.prank(owner); vm.expectRevert( - abi.encodeWithSelector( - OwnableWithGuardiansUpgradeable.NotGuardian.selector, - owner - ) + abi.encodeWithSelector(OwnableWithGuardiansUpgradeable.NotGuardian.selector, owner) ); proxy.guardianOnlyFunction(); } @@ -293,8 +255,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(user); vm.expectRevert( abi.encodeWithSelector( - OwnableWithGuardiansUpgradeable.NotGuardianOrOwner.selector, - user + OwnableWithGuardiansUpgradeable.NotGuardianOrOwner.selector, user ) ); proxy.guardianOrOwnerFunction(); @@ -353,12 +314,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { // Old owner should not be able to add guardians vm.prank(owner); - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - owner - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, owner)); proxy.addGuardian(guardian1); } @@ -379,9 +335,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { proxy.upgradeToAndCall(address(newImplementation), ""); // Test that upgrade worked - MockGuardedContractV2 upgradedProxy = MockGuardedContractV2( - address(proxy) - ); + MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); upgradedProxy.newFunction(); assertEq(upgradedProxy.newValue(), 999); @@ -393,12 +347,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); vm.prank(user); - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - user - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); proxy.upgradeToAndCall(address(newImplementation), ""); } @@ -410,10 +359,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(guardian1); vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - guardian1 - ) + abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) ); proxy.upgradeToAndCall(address(newImplementation), ""); } @@ -431,9 +377,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { proxy.upgradeToAndCall(address(newImplementation), ""); // Check guardians are preserved - MockGuardedContractV2 upgradedProxy = MockGuardedContractV2( - address(proxy) - ); + MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); assertEq(upgradedProxy.guardianCount(), 2); assertTrue(upgradedProxy.isGuardian(guardian1)); assertTrue(upgradedProxy.isGuardian(guardian2)); @@ -472,9 +416,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testAddZeroAddressAsGuardian() public { vm.prank(owner); - vm.expectRevert( - OwnableWithGuardiansUpgradeable.InvalidGuardianAddress.selector - ); + vm.expectRevert(OwnableWithGuardiansUpgradeable.InvalidGuardianAddress.selector); proxy.addGuardian(address(0)); } @@ -504,12 +446,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.stopPrank(); // No owner, so can't add/remove guardians - vm.expectRevert( - abi.encodeWithSelector( - Ownable.OwnableUnauthorizedAccount.selector, - owner - ) - ); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, owner)); vm.prank(owner); proxy.addGuardian(guardian2); } @@ -562,11 +499,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(user); vm.expectRevert( abi.encodeWithSelector( - bytes4( - keccak256( - "AccessControlUnauthorizedAccount(address,bytes32)" - ) - ), + bytes4(keccak256("AccessControlUnauthorizedAccount(address,bytes32)")), user, adminRole ) @@ -613,9 +546,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { assertEq(proxy.value(), 200); } - function testFuzz_NonGuardianCannotCallGuardianFunction( - address notGuardian - ) public { + function testFuzz_NonGuardianCannotCallGuardianFunction(address notGuardian) public { vm.assume(notGuardian != address(0)); vm.assume(notGuardian != owner); @@ -627,8 +558,7 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(notGuardian); vm.expectRevert( abi.encodeWithSelector( - OwnableWithGuardiansUpgradeable.NotGuardian.selector, - notGuardian + OwnableWithGuardiansUpgradeable.NotGuardian.selector, notGuardian ) ); proxy.guardianOnlyFunction(); From b76a5e443d9c3f3ebcacb60fb23817cd1de2a9aa Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Mon, 26 Jan 2026 17:32:20 +0100 Subject: [PATCH 05/13] Formatting --- src/OwnableWithGuardiansUpgradeable.sol | 15 +++++++-------- test/EspressoRollupSequencerManager.t.sol | 4 +++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/OwnableWithGuardiansUpgradeable.sol b/src/OwnableWithGuardiansUpgradeable.sol index 700c80a..927ac7e 100644 --- a/src/OwnableWithGuardiansUpgradeable.sol +++ b/src/OwnableWithGuardiansUpgradeable.sol @@ -1,10 +1,12 @@ // 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 { + Ownable2StepUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import { + AccessControlEnumerableUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -59,10 +61,7 @@ abstract contract OwnableWithGuardiansUpgradeable is __OwnableWithGuardians_init_unchained(initialOwner); } - function __OwnableWithGuardians_init_unchained(address initialOwner) - internal - onlyInitializing - { + function __OwnableWithGuardians_init_unchained(address initialOwner) internal onlyInitializing { // Grant the initial owner the default admin role for managing guardians _grantRole(DEFAULT_ADMIN_ROLE, initialOwner); diff --git a/test/EspressoRollupSequencerManager.t.sol b/test/EspressoRollupSequencerManager.t.sol index e535982..cbb5089 100644 --- a/test/EspressoRollupSequencerManager.t.sol +++ b/test/EspressoRollupSequencerManager.t.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import {EspressoRollupSequencerManager} from "../src/EspressoRollupSequencerManager.sol"; -import {IEspressoRollupSequencerManager} from "../src/interface/IEspressoRollupSequencerManager.sol"; +import { + IEspressoRollupSequencerManager +} from "../src/interface/IEspressoRollupSequencerManager.sol"; contract EspressoRollupSequencerManagerTest is Test { EspressoRollupSequencerManager public rollupSequencerManager; From c8121eb1c62acdcccaf4ee951a9be557950cd01e Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Wed, 28 Jan 2026 19:53:39 +0100 Subject: [PATCH 06/13] Use TransparentUpgradeableProxy --- src/EspressoNitroTEEVerifier.sol | 2 +- src/OwnableWithGuardiansUpgradeable.sol | 17 ++------- test/OwnableWithGuardiansUpgradeable.t.sol | 42 ++++++++++++++++------ 3 files changed, 35 insertions(+), 26 deletions(-) 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/OwnableWithGuardiansUpgradeable.sol b/src/OwnableWithGuardiansUpgradeable.sol index 927ac7e..9ff42f9 100644 --- a/src/OwnableWithGuardiansUpgradeable.sol +++ b/src/OwnableWithGuardiansUpgradeable.sol @@ -7,29 +7,25 @@ import { import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.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. - * @notice Guardians do not have upgrade permissions unless explicitly granted by overriding _authorizeUpgrade * @dev This contract provides: * - 2-step ownership transfer (transferOwnership + acceptOwnership) * - Multiple guardian addresses for time-sensitive operations - * - UUPS upgradeability pattern + * - TransparentUpgradeableProxy pattern compatibility * - EIP-7201 namespaced storage (via OZ upgradeable contracts) * * Inheriting contracts must: * 1. Call __OwnableWithGuardians_init(initialOwner) in their initializer - * 2. Implement the _authorizeUpgrade function if they want to customize upgrade authorization */ abstract contract OwnableWithGuardiansUpgradeable is Initializable, Ownable2StepUpgradeable, - AccessControlEnumerableUpgradeable, - UUPSUpgradeable + AccessControlEnumerableUpgradeable { /// @notice Role identifier for guardians who can execute time-sensitive operations bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); @@ -57,7 +53,6 @@ abstract contract OwnableWithGuardiansUpgradeable is __Ownable_init(initialOwner); __AccessControl_init(); __AccessControlEnumerable_init(); - __UUPSUpgradeable_init(); __OwnableWithGuardians_init_unchained(initialOwner); } @@ -164,12 +159,4 @@ abstract contract OwnableWithGuardiansUpgradeable is } _grantRole(DEFAULT_ADMIN_ROLE, newOwner); } - - /** - * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. - * @param newImplementation The address of the new implementation - * - * By default, only the owner can authorize upgrades. Override this function to customize authorization. - */ - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} } diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol index 3f0877d..0450a40 100644 --- a/test/OwnableWithGuardiansUpgradeable.t.sol +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -4,7 +4,13 @@ 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 {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.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"; /** * @title MockGuardedContract @@ -37,7 +43,7 @@ contract MockGuardedContract is OwnableWithGuardiansUpgradeable { /** * @title MockGuardedContractV2 - * @notice Upgraded version for testing UUPS upgradeability + * @notice Upgraded version for testing TransparentUpgradeableProxy upgradeability */ contract MockGuardedContractV2 is OwnableWithGuardiansUpgradeable { uint256 public value; @@ -73,12 +79,15 @@ contract MockGuardedContractV2 is OwnableWithGuardiansUpgradeable { 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); @@ -89,11 +98,16 @@ contract OwnableWithGuardiansUpgradeableTest is Test { // Deploy implementation implementation = new MockGuardedContract(); + // Deploy ProxyAdmin + vm.prank(proxyAdminOwner); + proxyAdmin = new ProxyAdmin(proxyAdminOwner); + // Deploy proxy and initialize bytes memory initData = abi.encodeWithSelector(MockGuardedContract.initialize.selector, owner); - ERC1967Proxy proxyContract = new ERC1967Proxy(address(implementation), initData); - proxy = MockGuardedContract(address(proxyContract)); + transparentProxy = + new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData); + proxy = MockGuardedContract(address(transparentProxy)); } // ============ Initialization Tests ============ @@ -331,8 +345,10 @@ contract OwnableWithGuardiansUpgradeableTest is Test { function testUpgradeAsOwner() public { MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); - vm.prank(owner); - proxy.upgradeToAndCall(address(newImplementation), ""); + vm.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); // Test that upgrade worked MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); @@ -348,7 +364,9 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.prank(user); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user)); - proxy.upgradeToAndCall(address(newImplementation), ""); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); } function testUpgradeRevertsForGuardian() public { @@ -361,7 +379,9 @@ contract OwnableWithGuardiansUpgradeableTest is Test { vm.expectRevert( abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, guardian1) ); - proxy.upgradeToAndCall(address(newImplementation), ""); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); } function testGuardiansPreservedAfterUpgrade() public { @@ -373,8 +393,10 @@ contract OwnableWithGuardiansUpgradeableTest is Test { // Upgrade MockGuardedContractV2 newImplementation = new MockGuardedContractV2(); - vm.prank(owner); - proxy.upgradeToAndCall(address(newImplementation), ""); + vm.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(address(transparentProxy)), address(newImplementation), "" + ); // Check guardians are preserved MockGuardedContractV2 upgradedProxy = MockGuardedContractV2(address(proxy)); From 517dbaa8490cd5d2de5f076c404020f957bb82ca Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Wed, 28 Jan 2026 23:14:37 +0100 Subject: [PATCH 07/13] Fix rebase --- src/EspressoTEEVerifier.sol | 8 ++++---- src/TEEHelper.sol | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EspressoTEEVerifier.sol b/src/EspressoTEEVerifier.sol index dcbf51c..5529181 100644 --- a/src/EspressoTEEVerifier.sol +++ b/src/EspressoTEEVerifier.sol @@ -216,7 +216,7 @@ contract EspressoTEEVerifier is } /** - * @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 @@ -227,7 +227,7 @@ contract EspressoTEEVerifier is bool valid, TeeType teeType, ServiceType service - ) external onlyOwner { + ) external onlyGuardianOrOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { $.espressoSGXTEEVerifier.setEnclaveHash( @@ -245,7 +245,7 @@ contract EspressoTEEVerifier is } /** - * @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) @@ -254,7 +254,7 @@ contract EspressoTEEVerifier is bytes32[] memory enclaveHashes, TeeType teeType, ServiceType service - ) external onlyOwner { + ) external onlyGuardianOrOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { $.espressoSGXTEEVerifier.deleteEnclaveHashes( 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; From 2d0e602d34d9fde3b3754892008435a8b3c8efa5 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Wed, 28 Jan 2026 23:50:11 +0100 Subject: [PATCH 08/13] Fix rebase x2 --- src/EspressoTEEVerifier.sol | 154 ++++----------------- test/EspressoTEEVerifier.t.sol | 5 +- test/OwnableWithGuardiansUpgradeable.t.sol | 22 ++- test/SignerValidation.t.sol | 15 +- test/TEEHelper_DoSFix.t.sol | 14 +- 5 files changed, 66 insertions(+), 144 deletions(-) diff --git a/src/EspressoTEEVerifier.sol b/src/EspressoTEEVerifier.sol index 5529181..5030903 100644 --- a/src/EspressoTEEVerifier.sol +++ b/src/EspressoTEEVerifier.sol @@ -13,10 +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 - OwnableWithGuardiansUpgradeable, - IEspressoTEEVerifier -{ +contract EspressoTEEVerifier is OwnableWithGuardiansUpgradeable, IEspressoTEEVerifier { /// @custom:storage-location erc7201:espresso.storage.EspressoTEEVerifier struct EspressoTEEVerifierStorage { IEspressoSGXTEEVerifier espressoSGXTEEVerifier; @@ -27,11 +24,7 @@ contract EspressoTEEVerifier is bytes32 private constant ESPRESSO_TEE_VERIFIER_STORAGE_SLOT = 0x89639f446056f5d7661bbd94e8ab0617a80058ed7b072845818d4b93332e4800; - function _layout() - private - pure - returns (EspressoTEEVerifierStorage storage $) - { + function _layout() private pure returns (EspressoTEEVerifierStorage storage $) { assembly { $.slot := ESPRESSO_TEE_VERIFIER_STORAGE_SLOT } @@ -98,92 +91,29 @@ contract EspressoTEEVerifier is ) external { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.registerService( - verificationData, - data, - service - ); + $.espressoSGXTEEVerifier.registerService(verificationData, data, service); return; } else { - $.espressoNitroTEEVerifier.registerService( - verificationData, - data, - service - ); + $.espressoNitroTEEVerifier.registerService(verificationData, data, service); return; } } - /** - * @notice This function retrieves whether a signer is registered or not - * @param signer The address of the signer - * @param teeType The type of TEE - */ - function registeredService( - address signer, - TeeType teeType, - ServiceType service - ) external view returns (bool) { - EspressoTEEVerifierStorage storage $ = _layout(); - if (teeType == TeeType.SGX) { - return $.espressoSGXTEEVerifier.registeredService(signer, service); - } else { - return - $.espressoNitroTEEVerifier.registeredService(signer, service); - } - } - /** * @notice This function retrieves whether an enclave hash is registered or not * @param enclaveHash The hash of the enclave * @param teeType The type of TEE */ - function registeredEnclaveHashes( - bytes32 enclaveHash, - TeeType teeType, - ServiceType service - ) external view returns (bool) { - EspressoTEEVerifierStorage storage $ = _layout(); - if (teeType == TeeType.SGX) { - return - $.espressoSGXTEEVerifier.registeredEnclaveHash( - enclaveHash, - service - ); - } else { - return - $.espressoNitroTEEVerifier.registeredEnclaveHash( - enclaveHash, - service - ); - } - } - - /** - * @notice This function retrieves the list of signers registered for a given enclave hash - * @param enclaveHash The hash of the enclave - * @param teeType The type of TEE - * @param service The service type (BatchPoster or CaffNode) - * @return address[] The list of signers registered for the given enclave hash - */ - function enclaveHashSigners( - bytes32 enclaveHash, - TeeType teeType, - ServiceType service - ) external view returns (address[] memory) { + function registeredEnclaveHashes(bytes32 enclaveHash, TeeType teeType, ServiceType service) + external + view + returns (bool) + { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - return - $.espressoSGXTEEVerifier.enclaveHashSigners( - enclaveHash, - service - ); + return $.espressoSGXTEEVerifier.registeredEnclaveHash(enclaveHash, service); } else { - return - $.espressoNitroTEEVerifier.enclaveHashSigners( - enclaveHash, - service - ); + return $.espressoNitroTEEVerifier.registeredEnclaveHash(enclaveHash, service); } } @@ -191,9 +121,10 @@ contract EspressoTEEVerifier is * @notice Set the EspressoSGXTEEVerifier * @param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier */ - function setEspressoSGXTEEVerifier( - IEspressoSGXTEEVerifier _espressoSGXTEEVerifier - ) public onlyOwner { + function setEspressoSGXTEEVerifier(IEspressoSGXTEEVerifier _espressoSGXTEEVerifier) + public + onlyOwner + { if (address(_espressoSGXTEEVerifier) == address(0)) { revert InvalidVerifierAddress(); } @@ -205,9 +136,10 @@ contract EspressoTEEVerifier is * @notice Set the EspressoNitroTEEVerifier * @param _espressoNitroTEEVerifier The address of the EspressoNitroTEEVerifier */ - function setEspressoNitroTEEVerifier( - IEspressoNitroTEEVerifier _espressoNitroTEEVerifier - ) public onlyOwner { + function setEspressoNitroTEEVerifier(IEspressoNitroTEEVerifier _espressoNitroTEEVerifier) + public + onlyOwner + { if (address(_espressoNitroTEEVerifier) == address(0)) { revert InvalidVerifierAddress(); } @@ -222,25 +154,15 @@ contract EspressoTEEVerifier is * @param teeType The type of TEE * @param service The service type (BatchPoster or CaffNode) */ - function setEnclaveHash( - bytes32 enclaveHash, - bool valid, - TeeType teeType, - ServiceType service - ) external onlyGuardianOrOwner { + function setEnclaveHash(bytes32 enclaveHash, bool valid, TeeType teeType, ServiceType service) + external + onlyGuardianOrOwner + { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.setEnclaveHash( - enclaveHash, - valid, - service - ); + $.espressoSGXTEEVerifier.setEnclaveHash(enclaveHash, valid, service); } else { - $.espressoNitroTEEVerifier.setEnclaveHash( - enclaveHash, - valid, - service - ); + $.espressoNitroTEEVerifier.setEnclaveHash(enclaveHash, valid, service); } } @@ -257,15 +179,9 @@ contract EspressoTEEVerifier is ) external onlyGuardianOrOwner { EspressoTEEVerifierStorage storage $ = _layout(); if (teeType == TeeType.SGX) { - $.espressoSGXTEEVerifier.deleteEnclaveHashes( - enclaveHashes, - service - ); + $.espressoSGXTEEVerifier.deleteEnclaveHashes(enclaveHashes, service); } else { - $.espressoNitroTEEVerifier.deleteEnclaveHashes( - enclaveHashes, - service - ); + $.espressoNitroTEEVerifier.deleteEnclaveHashes(enclaveHashes, service); } } @@ -282,20 +198,14 @@ contract EspressoTEEVerifier is * @param nitroVerifier The address of the nitro enclave verifier */ function setNitroEnclaveVerifier(address nitroVerifier) external onlyOwner { - _layout().espressoNitroTEEVerifier.setNitroEnclaveVerifier( - nitroVerifier - ); + _layout().espressoNitroTEEVerifier.setNitroEnclaveVerifier(nitroVerifier); } /** * @notice Get the EspressoSGXTEEVerifier address * @return The EspressoSGXTEEVerifier interface */ - function espressoSGXTEEVerifier() - external - view - returns (IEspressoSGXTEEVerifier) - { + function espressoSGXTEEVerifier() external view returns (IEspressoSGXTEEVerifier) { return _layout().espressoSGXTEEVerifier; } @@ -303,11 +213,7 @@ contract EspressoTEEVerifier is * @notice Get the EspressoNitroTEEVerifier address * @return The EspressoNitroTEEVerifier interface */ - function espressoNitroTEEVerifier() - external - view - returns (IEspressoNitroTEEVerifier) - { + function espressoNitroTEEVerifier() external view returns (IEspressoNitroTEEVerifier) { return _layout().espressoNitroTEEVerifier; } } diff --git a/test/EspressoTEEVerifier.t.sol b/test/EspressoTEEVerifier.t.sol index 9820a3b..a9fed84 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); diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol index 0450a40..be30ccc 100644 --- a/test/OwnableWithGuardiansUpgradeable.t.sol +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -11,6 +11,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; /** * @title MockGuardedContract @@ -20,6 +21,10 @@ contract MockGuardedContract is OwnableWithGuardiansUpgradeable { uint256 public value; uint256 public emergencyValue; + constructor() { + _disableInitializers(); + } + function initialize(address initialOwner) public initializer { __OwnableWithGuardians_init(initialOwner); } @@ -50,6 +55,10 @@ contract MockGuardedContractV2 is OwnableWithGuardiansUpgradeable { uint256 public emergencyValue; uint256 public newValue; // New field in V2 + constructor() { + _disableInitializers(); + } + function initialize(address initialOwner) public initializer { __OwnableWithGuardians_init(initialOwner); } @@ -98,16 +107,19 @@ contract OwnableWithGuardiansUpgradeableTest is Test { // Deploy implementation implementation = new MockGuardedContract(); - // Deploy ProxyAdmin - vm.prank(proxyAdminOwner); - proxyAdmin = new ProxyAdmin(proxyAdminOwner); - // 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), address(proxyAdmin), initData); + 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 ============ 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_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)); } /** From 8df3377af53f02dcac4f324135fc9a391c64ea39 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Wed, 28 Jan 2026 23:53:49 +0100 Subject: [PATCH 09/13] Formatting --- test/OwnableWithGuardiansUpgradeable.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/OwnableWithGuardiansUpgradeable.t.sol b/test/OwnableWithGuardiansUpgradeable.t.sol index be30ccc..4d24604 100644 --- a/test/OwnableWithGuardiansUpgradeable.t.sol +++ b/test/OwnableWithGuardiansUpgradeable.t.sol @@ -118,7 +118,8 @@ contract OwnableWithGuardiansUpgradeableTest is Test { // 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)))); + address adminAddress = + address(uint160(uint256(vm.load(address(transparentProxy), adminSlot)))); proxyAdmin = ProxyAdmin(adminAddress); } From 2c89abc15314ffd3652c0f606c92c2f1df0c0621 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Thu, 29 Jan 2026 01:28:12 +0100 Subject: [PATCH 10/13] Fix tests --- test/EspressoNitroTEEVerifier.t.sol | 19 ++++++++++--------- test/EspressoSGXTEEVerifier.t.sol | 12 ++++++------ test/TEEHelper.t.sol | 19 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/test/EspressoNitroTEEVerifier.t.sol b/test/EspressoNitroTEEVerifier.t.sol index 3e39e9f..961a9da 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 + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + proxyAdminOwner, + abi.encodeCall( + EspressoNitroTEEVerifier.initialize, + (teeVerifier, INitroEnclaveVerifier(0x2D7fbBAD6792698Ba92e67b7e180f8010B9Ec788)) + ) ); - return proxied; + 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 diff --git a/test/EspressoSGXTEEVerifier.t.sol b/test/EspressoSGXTEEVerifier.t.sol index c04979e..6522c8b 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 { 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))) + ); } } From 7463a7fc2274040480ed8f6cd0b2ff42671fb4f2 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Thu, 29 Jan 2026 20:14:12 +0100 Subject: [PATCH 11/13] More guardian tests --- test/EspressoNitroTEEVerifier.t.sol | 47 +++++++++++ test/EspressoSGXTEEVerifier.t.sol | 47 +++++++++++ test/EspressoTEEVerifier.t.sol | 118 ++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) diff --git a/test/EspressoNitroTEEVerifier.t.sol b/test/EspressoNitroTEEVerifier.t.sol index 961a9da..55373aa 100644 --- a/test/EspressoNitroTEEVerifier.t.sol +++ b/test/EspressoNitroTEEVerifier.t.sol @@ -332,4 +332,51 @@ 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(55555)); + 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(44444)); + 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 6522c8b..600c9be 100644 --- a/test/EspressoSGXTEEVerifier.t.sol +++ b/test/EspressoSGXTEEVerifier.t.sol @@ -405,4 +405,51 @@ 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(77777)); + 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(66666)); + 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 a9fed84..69d9968 100644 --- a/test/EspressoTEEVerifier.t.sol +++ b/test/EspressoTEEVerifier.t.sol @@ -531,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(12345)); + 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(54321)); + 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(99999)); + 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(88888)); + 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 + ) + ); + } } From 56866daa2ceeddb501f5c23211d4199a85485415 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Thu, 29 Jan 2026 20:29:38 +0100 Subject: [PATCH 12/13] Add guardians in deploy scripts --- scripts/DeployAllTEEVerifiers.s.sol | 13 +++++++++++++ scripts/DeployTEEVerifier.s.sol | 14 ++++++++++++++ 2 files changed, 27 insertions(+) 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) From 2d04d4d8c220cc4872964c24f06c239881d22461 Mon Sep 17 00:00:00 2001 From: Artemii Gerasimovich Date: Thu, 29 Jan 2026 20:32:40 +0100 Subject: [PATCH 13/13] Formatting --- test/EspressoNitroTEEVerifier.t.sol | 28 +++++++++++--------- test/EspressoSGXTEEVerifier.t.sol | 28 +++++++++++--------- test/EspressoTEEVerifier.t.sol | 40 ++++++++++++++--------------- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/test/EspressoNitroTEEVerifier.t.sol b/test/EspressoNitroTEEVerifier.t.sol index 55373aa..3c3579e 100644 --- a/test/EspressoNitroTEEVerifier.t.sol +++ b/test/EspressoNitroTEEVerifier.t.sol @@ -335,39 +335,41 @@ contract EspressoNitroTEEVerifierTest is Test { 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(55555)); + 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(44444)); + 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)); - + assertTrue( + espressoNitroTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.CaffNode) + ); + // Guardian should be able to delete it via TEEVerifier bytes32[] memory hashes = new bytes32[](1); hashes[0] = hashToDelete; @@ -375,8 +377,10 @@ contract EspressoNitroTEEVerifierTest is Test { espressoTEEVerifier.deleteEnclaveHashes( hashes, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode ); - + // Verify it's deleted - assertFalse(espressoNitroTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.CaffNode)); + assertFalse( + espressoNitroTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.CaffNode) + ); } } diff --git a/test/EspressoSGXTEEVerifier.t.sol b/test/EspressoSGXTEEVerifier.t.sol index 600c9be..21fad21 100644 --- a/test/EspressoSGXTEEVerifier.t.sol +++ b/test/EspressoSGXTEEVerifier.t.sol @@ -408,39 +408,41 @@ contract EspressoSGXTEEVerifierTest is Test { 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(77777)); + 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(66666)); + 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)); - + assertTrue( + espressoSGXTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.BatchPoster) + ); + // Guardian should be able to delete it via TEEVerifier bytes32[] memory hashes = new bytes32[](1); hashes[0] = hashToDelete; @@ -448,8 +450,10 @@ contract EspressoSGXTEEVerifierTest is Test { espressoTEEVerifier.deleteEnclaveHashes( hashes, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster ); - + // Verify it's deleted - assertFalse(espressoSGXTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.BatchPoster)); + assertFalse( + espressoSGXTEEVerifier.registeredEnclaveHash(hashToDelete, ServiceType.BatchPoster) + ); } } diff --git a/test/EspressoTEEVerifier.t.sol b/test/EspressoTEEVerifier.t.sol index 69d9968..42cf6c0 100644 --- a/test/EspressoTEEVerifier.t.sol +++ b/test/EspressoTEEVerifier.t.sol @@ -534,18 +534,18 @@ contract EspressoTEEVerifierTest is Test { 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(12345)); + 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( @@ -556,18 +556,18 @@ contract EspressoTEEVerifierTest is Test { 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(54321)); + 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( @@ -578,25 +578,25 @@ contract EspressoTEEVerifierTest is Test { 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(99999)); + 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; @@ -604,7 +604,7 @@ contract EspressoTEEVerifierTest is Test { espressoTEEVerifier.deleteEnclaveHashes( hashes, IEspressoTEEVerifier.TeeType.SGX, ServiceType.BatchPoster ); - + // Verify it's deleted assertFalse( espressoTEEVerifier.registeredEnclaveHashes( @@ -615,25 +615,25 @@ contract EspressoTEEVerifierTest is Test { 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(88888)); + 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; @@ -641,7 +641,7 @@ contract EspressoTEEVerifierTest is Test { espressoTEEVerifier.deleteEnclaveHashes( hashes, IEspressoTEEVerifier.TeeType.NITRO, ServiceType.CaffNode ); - + // Verify it's deleted assertFalse( espressoTEEVerifier.registeredEnclaveHashes(