From 32c0b2a949e5ff7eb09283e9461d4dcaa222913e Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Mon, 20 Oct 2025 18:48:10 -0400 Subject: [PATCH 01/21] rework enclave contract to better support multiple accepted measurements --- .gitmodules | 3 + .../contracts/MultisigUpgradeOperator.sol | 447 ++++++++++++------ .../contracts/UpgradeOperator.sol | 186 ++++++-- .../enclave-contract/contracts/foundry.toml | 4 - crates/enclave-contract/foundry.toml | 4 + crates/enclave-contract/lib/forge-std | 1 + .../test/MultisigUpgradeOperator.t.sol | 389 +++++++++++++++ .../{tests => test}/multisig_test.rs | 0 8 files changed, 839 insertions(+), 195 deletions(-) create mode 100644 .gitmodules delete mode 100644 crates/enclave-contract/contracts/foundry.toml create mode 100644 crates/enclave-contract/foundry.toml create mode 160000 crates/enclave-contract/lib/forge-std create mode 100644 crates/enclave-contract/test/MultisigUpgradeOperator.t.sol rename crates/enclave-contract/{tests => test}/multisig_test.rs (100%) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..337b9c9c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "crates/enclave-contract/lib/forge-std"] + path = crates/enclave-contract/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol index dba1f0d3..5d8730b5 100644 --- a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol +++ b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol @@ -9,181 +9,332 @@ import "./UpgradeOperator.sol"; * Uses the ANVIL test keys as the three signers */ contract MultisigUpgradeOperator { - // The three signers (ANVIL keys) - address public constant signer1 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // Alice (0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80) - address public constant signer2 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // Bob (0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d) - address public constant signer3 = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; // Charlie (0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a) - + address public constant signer1 = + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // Alice (0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80) + address public constant signer2 = + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // Bob (0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d) + address public constant signer3 = + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; // Charlie (0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a) + // The UpgradeOperator contract being controlled - UpgradeOperator constant public upgradeOperator = UpgradeOperator(0x1000000000000000000000000000000000000001); // Set in seismic-reth genesis - + UpgradeOperator public immutable upgradeOperator = + UpgradeOperator(0x1000000000000000000000000000000000000001); // Set in seismic-reth genesis + // Nonce counter for proposal uniqueness uint256 public proposalNonce; - - // Mapping to track votes for each proposal - mapping(bytes32 => mapping(address => bool)) public votes; - - // Mapping to track proposal execution status - mapping(bytes32 => bool) public executed; - - // Event emitted when a proposal is created (version 1) - event ProposalCreatedV1(bytes32 indexed proposalId, uint256 nonce, bytes mrtd, bytes mrseam, bytes pcr4, bool status); - - // Event emitted when a vote is cast - event VoteCast(bytes32 indexed proposalId, address indexed voter, bool approved); - - // Event emitted when a proposal is executed - event ProposalExecuted(bytes32 indexed proposalId); - - // Event emitted when upgrade operator is set - event UpgradeOperatorSet(address indexed upgradeOperator); - + + // Enum for proposal types + enum ProposalType { + ADD_MEASUREMENTS, + DEPRECATE_MEASUREMENTS, + REINSTATE_MEASUREMENTS + } + + // Proposal struct + struct Proposal { + ProposalType proposalType; + bytes32 tagHash; + UpgradeOperator.Measurements measurements; + bool executed; + uint256 voteCount; + mapping(address => bool) hasVoted; + } + + // Mapping to track proposals + mapping(bytes32 => Proposal) public proposals; + + // Events + event ProposalCreated( + bytes32 indexed proposalId, + ProposalType indexed proposalType, + string tag, + uint256 nonce + ); + + event VoteCast(bytes32 indexed proposalId, address indexed voter); + + event ProposalExecuted( + bytes32 indexed proposalId, + ProposalType indexed proposalType + ); + + modifier onlySigner() { + require( + msg.sender == signer1 || + msg.sender == signer2 || + msg.sender == signer3, + "Not authorized" + ); + _; + } + /** - * @dev Creates a proposal to set defining attributes (version 1) in the UpgradeOperator - * @param mrtd The MRTD value (48 bytes) - * @param mrseam The MRSEAM value (48 bytes) - * @param pcr4 The PCR4 value (32 bytes) - * @param status The status to set + * @dev Creates a proposal to add new measurements + * @param measurements The measurements to add * @return proposalId The unique identifier for this proposal */ - function createProposalV1( - bytes memory mrtd, - bytes memory mrseam, - bytes memory pcr4, - bool status - ) public returns (bytes32 proposalId) { - require(mrtd.length == 48, "Invalid mrtd length"); - require(mrseam.length == 48, "Invalid mrseam length"); - require(pcr4.length == 32, "Invalid pcr4 length"); - - // Increment nonce and use it in proposal ID calculation + function proposeAddMeasurements( + UpgradeOperator.Measurements calldata measurements + ) external onlySigner returns (bytes32 proposalId) { + // Validate inputs + require(bytes(measurements.tag).length > 0, "Tag cannot be empty"); + require(measurements.mrtd.length > 0, "MRTD cannot be empty"); + require(measurements.mrseam.length > 0, "MRSEAM cannot be empty"); + require( + measurements.registrar_slots.length == + measurements.registrar_values.length, + "Registrar arrays length mismatch" + ); + proposalNonce++; - proposalId = computeProposalIdV1(mrtd, mrseam, pcr4, status, proposalNonce); + proposalId = keccak256( + abi.encodePacked( + ProposalType.ADD_MEASUREMENTS, + measurements.tag, + measurements.mrtd, + measurements.mrseam, + proposalNonce + ) + ); + + Proposal storage proposal = proposals[proposalId]; + require(!proposal.executed, "Proposal already exists"); + + proposal.proposalType = ProposalType.ADD_MEASUREMENTS; + proposal.tagHash = keccak256(abi.encodePacked(measurements.tag)); + proposal.measurements = measurements; + + emit ProposalCreated( + proposalId, + ProposalType.ADD_MEASUREMENTS, + measurements.tag, + proposalNonce + ); + + // Auto-vote for proposer + _vote(proposalId); - require(!executed[proposalId], "Proposal already executed"); - - emit ProposalCreatedV1(proposalId, proposalNonce, mrtd, mrseam, pcr4, status); - return proposalId; } - + + /** + * @dev Creates a proposal to deprecate measurements + * @param tag The tag of measurements to deprecate + * @return proposalId The unique identifier for this proposal + */ + function proposeDeprecateMeasurements( + string calldata tag + ) external onlySigner returns (bytes32 proposalId) { + require(bytes(tag).length > 0, "Tag cannot be empty"); + + proposalNonce++; + proposalId = keccak256( + abi.encodePacked( + ProposalType.DEPRECATE_MEASUREMENTS, + tag, + proposalNonce + ) + ); + + Proposal storage proposal = proposals[proposalId]; + require(!proposal.executed, "Proposal already exists"); + + proposal.proposalType = ProposalType.DEPRECATE_MEASUREMENTS; + proposal.tagHash = keccak256(abi.encodePacked(tag)); + // Store tag in measurements.tag for execution + proposal.measurements.tag = tag; + + emit ProposalCreated( + proposalId, + ProposalType.DEPRECATE_MEASUREMENTS, + tag, + proposalNonce + ); + + // Auto-vote for proposer + _vote(proposalId); + + return proposalId; + } + /** - * @dev Casts a vote on a proposal + * @dev Creates a proposal to reinstate measurements + * @param tag The tag of measurements to reinstate + * @return proposalId The unique identifier for this proposal + */ + function proposeReinstateMeasurements( + string calldata tag + ) external onlySigner returns (bytes32 proposalId) { + require(bytes(tag).length > 0, "Tag cannot be empty"); + + proposalNonce++; + proposalId = keccak256( + abi.encodePacked( + ProposalType.REINSTATE_MEASUREMENTS, + tag, + proposalNonce + ) + ); + + Proposal storage proposal = proposals[proposalId]; + require(!proposal.executed, "Proposal already exists"); + + proposal.proposalType = ProposalType.REINSTATE_MEASUREMENTS; + proposal.tagHash = keccak256(abi.encodePacked(tag)); + // Store tag in measurements.tag for execution + proposal.measurements.tag = tag; + + emit ProposalCreated( + proposalId, + ProposalType.REINSTATE_MEASUREMENTS, + tag, + proposalNonce + ); + + // Auto-vote for proposer + _vote(proposalId); + + return proposalId; + } + + /** + * @dev Vote on a proposal * @param proposalId The proposal to vote on - * @param approved Whether to approve the proposal */ - function vote(bytes32 proposalId, bool approved) public { - require(msg.sender == signer1 || msg.sender == signer2 || msg.sender == signer3, "Not authorized to vote"); - require(!executed[proposalId], "Proposal already executed"); - require(!votes[proposalId][msg.sender], "Already voted"); - - votes[proposalId][msg.sender] = approved; - - emit VoteCast(proposalId, msg.sender, approved); + function vote(bytes32 proposalId) external onlySigner { + _vote(proposalId); } - + /** - * @dev Executes a proposal if it has enough votes (version 1) - * @param mrtd The MRTD value (48 bytes) - * @param mrseam The MRSEAM value (48 bytes) - * @param pcr4 The PCR4 value (32 bytes) - * @param status The status to set - * @param nonce The nonce used when creating the proposal + * @dev Internal vote logic */ - function executeProposalV1( - bytes memory mrtd, - bytes memory mrseam, - bytes memory pcr4, - bool status, - uint256 nonce - ) public { - bytes32 proposalId = computeProposalIdV1(mrtd, mrseam, pcr4, status, nonce); - - require(!executed[proposalId], "Proposal already executed"); - - uint256 approvalCount = 0; - if (votes[proposalId][signer1]) approvalCount++; - if (votes[proposalId][signer2]) approvalCount++; - if (votes[proposalId][signer3]) approvalCount++; - - require(approvalCount >= 2, "Insufficient votes"); - - executed[proposalId] = true; - - // Execute the actual set_id_status_v1 call on the UpgradeOperator - upgradeOperator.set_id_status_v1(mrtd, mrseam, pcr4, status); - - emit ProposalExecuted(proposalId); + function _vote(bytes32 proposalId) internal { + Proposal storage proposal = proposals[proposalId]; + + require(!proposal.executed, "Proposal already executed"); + require(!proposal.hasVoted[msg.sender], "Already voted"); + + proposal.hasVoted[msg.sender] = true; + proposal.voteCount++; + + emit VoteCast(proposalId, msg.sender); + + // todo we probably dont want auto execute + // Auto-execute if threshold reached + // if (proposal.voteCount >= 2) { + // _executeProposal(proposalId); + // } } - + /** - * @dev Gets the vote count for a proposal - * @param proposalId The proposal to check - * @return approvalCount Number of approvals - * @return totalVotes Total number of votes cast + * @dev Execute a proposal that has enough votes + * @param proposalId The proposal to execute */ - function getVoteCount(bytes32 proposalId) public view returns (uint256 approvalCount, uint256 totalVotes) { - if (votes[proposalId][signer1]) { - approvalCount++; - totalVotes++; - } - if (votes[proposalId][signer2]) { - approvalCount++; - totalVotes++; - } - if (votes[proposalId][signer3]) { - approvalCount++; - totalVotes++; + function executeProposal(bytes32 proposalId) external { + Proposal storage proposal = proposals[proposalId]; + + require(!proposal.executed, "Proposal already executed"); + require(proposal.voteCount >= 2, "Insufficient votes"); + _executeProposal(proposalId); + } + + /** + * @dev Internal execution logic + */ + function _executeProposal(bytes32 proposalId) internal { + Proposal storage proposal = proposals[proposalId]; + + proposal.executed = true; + + if (proposal.proposalType == ProposalType.ADD_MEASUREMENTS) { + upgradeOperator.addAcceptedMeasurements(proposal.measurements); + } else if ( + proposal.proposalType == ProposalType.DEPRECATE_MEASUREMENTS + ) { + upgradeOperator.deprecateMeasurements(proposal.measurements.tag); + } else if ( + proposal.proposalType == ProposalType.REINSTATE_MEASUREMENTS + ) { + upgradeOperator.reinstateMeasurement(proposal.measurements.tag); } - - return (approvalCount, totalVotes); + + emit ProposalExecuted(proposalId, proposal.proposalType); } - + /** - * @dev Checks if a proposal can be executed + * @dev Get vote status for a proposal * @param proposalId The proposal to check - * @return True if the proposal has enough votes to be executed + * @return voteCount Number of votes + * @return hasVoted1 Whether signer1 voted + * @return hasVoted2 Whether signer2 voted + * @return hasVoted3 Whether signer3 voted + * @return canExecute Whether proposal can be executed */ - function canExecute(bytes32 proposalId) public view returns (bool) { - if (executed[proposalId]) return false; - - uint256 approvalCount = 0; - if (votes[proposalId][signer1]) approvalCount++; - if (votes[proposalId][signer2]) approvalCount++; - if (votes[proposalId][signer3]) approvalCount++; - - return approvalCount >= 2; + function getVoteStatus( + bytes32 proposalId + ) + external + view + returns ( + uint256 voteCount, + bool hasVoted1, + bool hasVoted2, + bool hasVoted3, + bool canExecute + ) + { + Proposal storage proposal = proposals[proposalId]; + + voteCount = proposal.voteCount; + hasVoted1 = proposal.hasVoted[signer1]; + hasVoted2 = proposal.hasVoted[signer2]; + hasVoted3 = proposal.hasVoted[signer3]; + canExecute = !proposal.executed && proposal.voteCount >= 2; } - + /** - * @dev Computes the proposal ID for given parameters and nonce (version 1) - * Uses the UpgradeOperator's computeIdV1 method for the base ID calculation - * @param mrtd The MRTD value (48 bytes) - * @param mrseam The MRSEAM value (48 bytes) - * @param pcr4 The PCR4 value (32 bytes) - * @param status The status to set - * @param nonce The nonce to use - * @return The computed proposal ID + * @dev Get proposal details + * @param proposalId The proposal to query + * @return proposalType The type of proposal + * @return tag The measurement tag + * @return executed Whether the proposal has been executed + * @return voteCount Number of votes */ - function computeProposalIdV1( - bytes memory mrtd, - bytes memory mrseam, - bytes memory pcr4, - bool status, - uint256 nonce - ) public pure returns (bytes32) { - // Create the DefiningAttributesV1 struct and use the UpgradeOperator's computeIdV1 method - UpgradeOperator.DefiningAttributesV1 memory attrs = UpgradeOperator.DefiningAttributesV1(mrtd, mrseam, pcr4); - - bytes32 baseId; - try upgradeOperator.computeIdV1(attrs) returns (bytes32 result) { - baseId = result; - } catch { - revert("upgradeOperator.computeIdV1 failed"); - } + function getProposalInfo( + bytes32 proposalId + ) + external + view + returns ( + ProposalType proposalType, + string memory tag, + bool executed, + uint256 voteCount + ) + { + Proposal storage proposal = proposals[proposalId]; - // Combine with status and nonce for proposal uniqueness - return keccak256(abi.encodePacked(baseId, status, nonce)); + return ( + proposal.proposalType, + proposal.measurements.tag, + proposal.executed, + proposal.voteCount + ); + } + + /** + * @dev Get full measurement details for an add proposal + * @param proposalId The proposal to query + * @return measurements The full measurements struct + */ + function getProposalMeasurements( + bytes32 proposalId + ) external view returns (UpgradeOperator.Measurements memory) { + require( + proposals[proposalId].proposalType == ProposalType.ADD_MEASUREMENTS, + "Not an add measurements proposal" + ); + return proposals[proposalId].measurements; } -} \ No newline at end of file +} diff --git a/crates/enclave-contract/contracts/UpgradeOperator.sol b/crates/enclave-contract/contracts/UpgradeOperator.sol index 8ffcfa56..d50eaa1e 100644 --- a/crates/enclave-contract/contracts/UpgradeOperator.sol +++ b/crates/enclave-contract/contracts/UpgradeOperator.sol @@ -1,70 +1,170 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -/* - The Upgrade Operator is responsible for defining the - configuration to upgrade. -*/ contract UpgradeOperator { - - struct DefiningAttributesV1 { + struct Measurements { + string tag; bytes mrtd; bytes mrseam; - bytes pcr4; + uint8[] registrar_slots; + bytes[] registrar_values; } - struct DefiningAttributesV2 { - bytes mrtd; - bytes mrseam; - bytes pcr4; - bytes pcr7; + mapping(bytes32 => Measurements) public acceptedMeasurements; + mapping(bytes32 => Measurements) public deprecatedMeasurements; + + // Keep track of all tags for enumeration if needed + bytes32[] public acceptedTags; + bytes32[] public deprecatedTags; + + // Track if a tag exists to prevent duplicates + mapping(bytes32 => bool) public tagExists; + + address public constant OWNER = 0x1000000000000000000000000000000000000002; + + event MeasurementAdded(string indexed tag, bytes32 indexed tagHash); + event MeasurementDeprecated(string indexed tag, bytes32 indexed tagHash); + + modifier onlyNetworkMultisig() virtual { + require(msg.sender == OWNER, "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Add a set of measurements the network allows + * @param measurements The measurements to add + */ + function addAcceptedMeasurements( + Measurements calldata measurements + ) external onlyNetworkMultisig { + bytes32 tagHash = keccak256(abi.encodePacked(measurements.tag)); + + // Check uniqueness + require(!tagExists[tagHash], "Measurement tag already exists"); + + // Validate inputs TODO: assert actual length these measurements should be + require(bytes(measurements.tag).length > 0, "Tag cannot be empty"); + require(measurements.mrtd.length > 0, "MRTD cannot be empty"); + require(measurements.mrseam.length > 0, "MRSEAM cannot be empty"); + require( + measurements.registrar_slots.length == + measurements.registrar_values.length, + "Registrar arrays length mismatch" + ); + + // Store the measurement + acceptedMeasurements[tagHash] = measurements; + acceptedTags.push(tagHash); + tagExists[tagHash] = true; + + emit MeasurementAdded(measurements.tag, tagHash); + } + /** + * @dev accept a currently deprecated set of measurements + * @param tag The tag of the measurement to reinstate + */ + function reinstateMeasurement( + string calldata tag + ) external onlyNetworkMultisig { + bytes32 tagHash = keccak256(abi.encodePacked(tag)); + + // Check if measurement exists in accepted + require( + bytes(deprecatedMeasurements[tagHash].tag).length > 0, + "No deprecated measurement with that tag" + ); + + // Move from deprecated to accepted + Measurements memory measurement = deprecatedMeasurements[tagHash]; + acceptedMeasurements[tagHash] = measurement; + acceptedTags.push(tagHash); + + // Remove from deprecated + delete deprecatedMeasurements[tagHash]; + _removeFromArray(deprecatedTags, tagHash); + + emit MeasurementAdded(tag, tagHash); } - address constant public owner = 0x1000000000000000000000000000000000000002; // Set in seismic-reth genesis - mapping(bytes32 => bool) public attributes; + /** + * @dev Deprecate a currently allowed set of measurements + * @param tag The tag of the measurement to deprecate + */ + function deprecateMeasurements( + string calldata tag + ) external onlyNetworkMultisig { + bytes32 tagHash = keccak256(abi.encodePacked(tag)); + + // Check if measurement exists in accepted + require( + bytes(acceptedMeasurements[tagHash].tag).length > 0, + "No accepted measurement with that tag" + ); + + // Move from accepted to deprecated + Measurements memory measurement = acceptedMeasurements[tagHash]; + deprecatedMeasurements[tagHash] = measurement; + deprecatedTags.push(tagHash); + + // Remove from accepted + delete acceptedMeasurements[tagHash]; + _removeFromArray(acceptedTags, tagHash); + + emit MeasurementDeprecated(tag, tagHash); + } - event SetDefiningAttributesV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status); - event SetDefiningAttributesV2(bytes mrtd, bytes mrseam, bytes pcr4, bytes pcr7, bool status); + /** + * @dev Check if measurements are accepted + * @param tag The tag to check + */ + function isAccepted(string calldata tag) external view returns (bool) { + bytes32 tagHash = keccak256(abi.encodePacked(tag)); + return bytes(acceptedMeasurements[tagHash].tag).length > 0; + } /** - * @dev Sets the status for a set of defining attributes (version 1) + * @dev Check if measurements are accepted + * @param tag The tag to check */ - function set_id_status_v1(bytes memory mrtd, bytes memory mrseam, bytes memory pcr4, bool status) public { - require(msg.sender == owner, "Only owner can set status"); - require(mrtd.length == 48, "Invalid mrtd length"); - require(mrseam.length == 48, "Invalid mrseam length"); - require(pcr4.length == 32, "Invalid pcr4 length"); - - DefiningAttributesV1 memory attrs = DefiningAttributesV1(mrtd, mrseam, pcr4); - bytes32 id = computeIdV1(attrs); - attributes[id] = status; - emit SetDefiningAttributesV1(mrtd, mrseam, pcr4, status); + function isDeprecated(string calldata tag) external view returns (bool) { + bytes32 tagHash = keccak256(abi.encodePacked(tag)); + return bytes(deprecatedMeasurements[tagHash].tag).length > 0; } /** - * @dev Gets the status of a set of defining attributes (version 1) + * @dev Get accepted measurement by tag */ - function get_id_status_v1(bytes memory mrtd, bytes memory mrseam, bytes memory pcr4) public view returns (bool) { - require(mrtd.length == 48, "Invalid mrtd length"); - require(mrseam.length == 48, "Invalid mrseam length"); - require(pcr4.length == 32, "Invalid pcr4 length"); - - DefiningAttributesV1 memory attrs = DefiningAttributesV1(mrtd, mrseam, pcr4); - bytes32 id = computeIdV1(attrs); - return attributes[id]; + function getAcceptedMeasurement( + string calldata tag + ) external view returns (Measurements memory) { + bytes32 tagHash = keccak256(abi.encodePacked(tag)); + require( + bytes(acceptedMeasurements[tagHash].tag).length > 0, + "Measurement not found" + ); + return acceptedMeasurements[tagHash]; } /** - * @dev Computes the ID for a set of defining attributes (version 1) + * @dev Get count of accepted measurements */ - function computeIdV1(DefiningAttributesV1 memory attrs) public pure returns (bytes32) { - return keccak256(abi.encode(attrs)); + function getAcceptedCount() external view returns (uint256) { + return acceptedTags.length; } /** - * @dev Computes the ID for a set of defining attributes (version 2) + * @dev Helper to remove element from array */ - function computeIdV2(DefiningAttributesV2 memory attrs) public pure returns (bytes32) { - return keccak256(abi.encode(attrs)); + function _removeFromArray( + bytes32[] storage array, + bytes32 element + ) private { + for (uint256 i = 0; i < array.length; i++) { + if (array[i] == element) { + array[i] = array[array.length - 1]; + array.pop(); + break; + } + } } -} \ No newline at end of file +} diff --git a/crates/enclave-contract/contracts/foundry.toml b/crates/enclave-contract/contracts/foundry.toml deleted file mode 100644 index 82d497fc..00000000 --- a/crates/enclave-contract/contracts/foundry.toml +++ /dev/null @@ -1,4 +0,0 @@ -[profile.default] -src = "." -out = "out" -libs = [] \ No newline at end of file diff --git a/crates/enclave-contract/foundry.toml b/crates/enclave-contract/foundry.toml new file mode 100644 index 00000000..36661944 --- /dev/null +++ b/crates/enclave-contract/foundry.toml @@ -0,0 +1,4 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] \ No newline at end of file diff --git a/crates/enclave-contract/lib/forge-std b/crates/enclave-contract/lib/forge-std new file mode 160000 index 00000000..8e40513d --- /dev/null +++ b/crates/enclave-contract/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 8e40513d678f392f398620b3ef2b418648b33e89 diff --git a/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol b/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol new file mode 100644 index 00000000..da5e29f0 --- /dev/null +++ b/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../contracts/UpgradeOperator.sol"; +import "../contracts/MultisigUpgradeOperator.sol"; + +contract MultisigUpgradeOperatorTest is Test { + UpgradeOperatorMock public upgradeOperator; + MultisigUpgradeOperator public multisig; + + // ANVIL test accounts + address public signer1 = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + address public signer2 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + address public signer3 = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + address public nonSigner = address(0x4); + + // Test data + UpgradeOperator.Measurements public testMeasurement1; + UpgradeOperator.Measurements public testMeasurement2; + + event ProposalCreated( + bytes32 indexed proposalId, + MultisigUpgradeOperator.ProposalType indexed proposalType, + string tag, + uint256 nonce + ); + + event VoteCast(bytes32 indexed proposalId, address indexed voter); + + event ProposalExecuted( + bytes32 indexed proposalId, + MultisigUpgradeOperator.ProposalType indexed proposalType + ); + + function setUp() public { + // Deploy MultisigUpgradeOperator + multisig = new MultisigUpgradeOperator(); + + // Since we can't actually transfer ownership in this test setup, + // we'll use vm.etch to deploy the UpgradeOperator at the expected address + // and modify it to accept the multisig as owner + + // For testing purposes, we'll deploy a modified UpgradeOperator + // that accepts our multisig as the owner + UpgradeOperatorMock mockUpgradeOperator = new UpgradeOperatorMock( + address(multisig) + ); + vm.etch( + address(0x1000000000000000000000000000000000000001), + address(mockUpgradeOperator).code + ); + + upgradeOperator = UpgradeOperatorMock( + 0x1000000000000000000000000000000000000001 + ); + + // Setup test measurements + testMeasurement1.tag = "AzureV1"; + testMeasurement1 + .mrtd = hex"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; + testMeasurement1 + .mrseam = hex"222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"; + testMeasurement1.registrar_slots = new uint8[](2); + testMeasurement1.registrar_slots[0] = 4; + testMeasurement1.registrar_slots[1] = 5; + testMeasurement1.registrar_values = new bytes[](2); + testMeasurement1.registrar_values[ + 0 + ] = hex"33333333333333333333333333333333333333333333333333333333"; + testMeasurement1.registrar_values[ + 1 + ] = hex"44444444444444444444444444444444444444444444444444444444"; + + testMeasurement2.tag = "AWSV1"; + testMeasurement2 + .mrtd = hex"555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"; + testMeasurement2 + .mrseam = hex"666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666"; + testMeasurement2.registrar_slots = new uint8[](1); + testMeasurement2.registrar_slots[0] = 3; + testMeasurement2.registrar_values = new bytes[](1); + testMeasurement2.registrar_values[ + 0 + ] = hex"77777777777777777777777777777777777777777777777777777777"; + } + + // Test proposal creation for adding measurements + function testProposeAddMeasurements() public { + vm.prank(signer1); + + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + // Check proposal was created + ( + MultisigUpgradeOperator.ProposalType proposalType, + string memory tag, + bool executed, + uint256 voteCount + ) = multisig.getProposalInfo(proposalId); + + assertEq( + uint(proposalType), + uint(MultisigUpgradeOperator.ProposalType.ADD_MEASUREMENTS) + ); + assertEq(tag, "AzureV1"); + assertFalse(executed); + assertEq(voteCount, 1); // Auto-voted by proposer + } + + // Test non-signer cannot create proposals + function testNonSignerCannotPropose() public { + vm.prank(nonSigner); + vm.expectRevert("Not authorized"); + multisig.proposeAddMeasurements(testMeasurement1); + } + + // Test voting on proposal + function testVoting() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + // Check initial vote count + (uint256 voteCount, , , , ) = multisig.getVoteStatus(proposalId); + assertEq(voteCount, 1); + + // Second signer votes + vm.prank(signer2); + vm.expectEmit(true, true, false, true); + emit VoteCast(proposalId, signer2); + multisig.vote(proposalId); + + // Check vote count increased + (voteCount, , , , ) = multisig.getVoteStatus(proposalId); + assertEq(voteCount, 2); + } + + // Test double voting fails + function testDoubleVoteFails() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + vm.prank(signer1); + vm.expectRevert("Already voted"); + multisig.vote(proposalId); + } + + // Test manual execution + function testManualExecution() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + vm.prank(signer2); + multisig.vote(proposalId); + + // Anyone can execute once threshold is met + vm.prank(nonSigner); + multisig.executeProposal(proposalId); + + // Check proposal is executed + (, , bool executed, ) = multisig.getProposalInfo(proposalId); + assertTrue(executed); + } + + // Test cannot execute without enough votes + function testCannotExecuteWithoutVotes() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + vm.expectRevert("Insufficient votes"); + multisig.executeProposal(proposalId); + } + + // Test deprecate measurements proposal + function testProposeDeprecateMeasurements() public { + // First add a measurement via multisig + vm.prank(signer1); + bytes32 addProposalId = multisig.proposeAddMeasurements( + testMeasurement1 + ); + vm.prank(signer2); + multisig.vote(addProposalId); + multisig.executeProposal(addProposalId); + + assertTrue(upgradeOperator.isAccepted("AzureV1")); + + // Now propose to deprecate it + vm.prank(signer1); + bytes32 deprecateProposalId = multisig.proposeDeprecateMeasurements( + "AzureV1" + ); + + // Vote to execute + vm.prank(signer3); + multisig.vote(deprecateProposalId); + multisig.executeProposal(deprecateProposalId); + + // Check it's deprecated + assertFalse(upgradeOperator.isAccepted("AzureV1")); + assertTrue(upgradeOperator.isDeprecated("AzureV1")); + } + + // Test reinstate measurements proposal + function testProposeReinstateMeasurements() public { + // Add, then deprecate a measurement + vm.prank(signer1); + bytes32 addProposalId = multisig.proposeAddMeasurements( + testMeasurement1 + ); + vm.prank(signer2); + multisig.vote(addProposalId); + multisig.executeProposal(addProposalId); + + vm.prank(signer1); + bytes32 deprecateProposalId = multisig.proposeDeprecateMeasurements( + "AzureV1" + ); + vm.prank(signer2); + multisig.vote(deprecateProposalId); + multisig.executeProposal(deprecateProposalId); + assertFalse(upgradeOperator.isAccepted("AzureV1")); + assertTrue(upgradeOperator.isDeprecated("AzureV1")); + + // Now propose to reinstate + vm.prank(signer1); + bytes32 reinstateProposalId = multisig.proposeReinstateMeasurements( + "AzureV1" + ); + vm.prank(signer3); + multisig.vote(reinstateProposalId); + multisig.executeProposal(reinstateProposalId); + + // Check it's reinstated + assertTrue(upgradeOperator.isAccepted("AzureV1")); + assertFalse(upgradeOperator.isDeprecated("AzureV1")); + } + + // Test get vote status + function testGetVoteStatus() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + ( + uint256 voteCount, + bool hasVoted1, + bool hasVoted2, + bool hasVoted3, + bool canExecute + ) = multisig.getVoteStatus(proposalId); + + assertEq(voteCount, 1); + assertTrue(hasVoted1); + assertFalse(hasVoted2); + assertFalse(hasVoted3); + assertFalse(canExecute); + + vm.prank(signer2); + multisig.vote(proposalId); + + (voteCount, hasVoted1, hasVoted2, hasVoted3, canExecute) = multisig + .getVoteStatus(proposalId); + + assertEq(voteCount, 2); + assertTrue(hasVoted1); + assertTrue(hasVoted2); + assertFalse(hasVoted3); + assertTrue(canExecute); + } + + // Test complete multisig workflow + function testCompleteMultisigWorkflow() public { + // Signer1 proposes to add Azure measurements + vm.prank(signer1); + bytes32 proposal1 = multisig.proposeAddMeasurements(testMeasurement1); + // Signer2 votes, triggering execution + vm.prank(signer2); + multisig.vote(proposal1); + multisig.executeProposal(proposal1); + assertTrue(upgradeOperator.isAccepted("AzureV1")); + + // Signer2 proposes to add AWS measurements + vm.prank(signer2); + bytes32 proposal2 = multisig.proposeAddMeasurements(testMeasurement2); + + // Signer3 votes allowing enough votes for execution + vm.prank(signer3); + multisig.vote(proposal2); + + // execute + multisig.executeProposal(proposal2); + assertTrue(upgradeOperator.isAccepted("AWSV1")); + + assertEq(upgradeOperator.getAcceptedCount(), 2); + + // Signer1 proposes to deprecate Azure + vm.prank(signer1); + bytes32 proposal3 = multisig.proposeDeprecateMeasurements("AzureV1"); + + // Signer2 votes + vm.prank(signer2); + multisig.vote(proposal3); + multisig.executeProposal(proposal3); + + assertFalse(upgradeOperator.isAccepted("AzureV1")); + assertTrue(upgradeOperator.isDeprecated("AzureV1")); + assertEq(upgradeOperator.getAcceptedCount(), 1); + + // Signer3 proposes to reinstate Azure + vm.prank(signer3); + bytes32 proposal4 = multisig.proposeReinstateMeasurements("AzureV1"); + + // Signer1 votes + vm.prank(signer1); + multisig.vote(proposal4); + multisig.executeProposal(proposal4); + + assertTrue(upgradeOperator.isAccepted("AzureV1")); + assertFalse(upgradeOperator.isDeprecated("AzureV1")); + assertEq(upgradeOperator.getAcceptedCount(), 2); + } + + // Test proposal with invalid measurements fails + function testInvalidMeasurementsFails() public { + UpgradeOperator.Measurements memory invalidMeasurement; + + // Empty tag + invalidMeasurement = testMeasurement1; + invalidMeasurement.tag = ""; + vm.prank(signer1); + vm.expectRevert("Tag cannot be empty"); + multisig.proposeAddMeasurements(invalidMeasurement); + + // Empty MRTD + invalidMeasurement = testMeasurement1; + invalidMeasurement.mrtd = ""; + vm.prank(signer1); + vm.expectRevert("MRTD cannot be empty"); + multisig.proposeAddMeasurements(invalidMeasurement); + + // Mismatched arrays + invalidMeasurement = testMeasurement1; + uint8[] memory slots = new uint8[](3); + slots[0] = 1; + slots[1] = 2; + slots[2] = 3; + invalidMeasurement.registrar_slots = slots; + vm.prank(signer1); + vm.expectRevert("Registrar arrays length mismatch"); + multisig.proposeAddMeasurements(invalidMeasurement); + } + + // Test get proposal measurements + function testGetProposalMeasurements() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeAddMeasurements(testMeasurement1); + + UpgradeOperator.Measurements memory retrieved = multisig + .getProposalMeasurements(proposalId); + + assertEq(retrieved.tag, testMeasurement1.tag); + assertEq(retrieved.mrtd, testMeasurement1.mrtd); + assertEq(retrieved.mrseam, testMeasurement1.mrseam); + assertEq(retrieved.registrar_slots.length, 2); + assertEq(retrieved.registrar_values.length, 2); + } + + // Test cannot get measurements for non-add proposals + function testCannotGetMeasurementsForDeprecateProposal() public { + vm.prank(signer1); + bytes32 proposalId = multisig.proposeDeprecateMeasurements("AzureV1"); + + vm.expectRevert("Not an add measurements proposal"); + multisig.getProposalMeasurements(proposalId); + } +} + +// Mock UpgradeOperator for testing that accepts a different owner +contract UpgradeOperatorMock is UpgradeOperator { + address public immutable testOwner; + + constructor(address _testOwner) { + testOwner = _testOwner; + } + + modifier onlyNetworkMultisig() override { + require(msg.sender == testOwner, "Ownable-- caller is not the owner"); + _; + } +} diff --git a/crates/enclave-contract/tests/multisig_test.rs b/crates/enclave-contract/test/multisig_test.rs similarity index 100% rename from crates/enclave-contract/tests/multisig_test.rs rename to crates/enclave-contract/test/multisig_test.rs From 5b15e5d5b587ab2b8b8090ef3802d99720205497 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 11:05:17 -0400 Subject: [PATCH 02/21] make enclave-contract crate only solidity --- Cargo.lock | 11 - Cargo.toml | 2 - crates/enclave-contract/Cargo.toml | 12 - crates/enclave-contract/foundry.toml | 2 +- .../MultisigUpgradeOperator.sol | 0 .../{contracts => src}/UpgradeOperator.sol | 0 .../{contracts => src}/build.sh | 0 .../src/contract_interface.rs | 405 ------------------ crates/enclave-contract/src/lib.rs | 28 -- crates/enclave-server/Cargo.toml | 4 +- scripts/run_integration_tests.sh | 23 +- 11 files changed, 5 insertions(+), 482 deletions(-) delete mode 100644 crates/enclave-contract/Cargo.toml rename crates/enclave-contract/{contracts => src}/MultisigUpgradeOperator.sol (100%) rename crates/enclave-contract/{contracts => src}/UpgradeOperator.sol (100%) rename crates/enclave-contract/{contracts => src}/build.sh (100%) delete mode 100644 crates/enclave-contract/src/contract_interface.rs delete mode 100644 crates/enclave-contract/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 121ec8e6..1bc55ec9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2214,16 +2214,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "enclave-contract" -version = "0.1.0" -dependencies = [ - "alloy", - "anyhow", - "reqwest", - "tokio", -] - [[package]] name = "encoding_rs" version = "0.8.35" @@ -5502,7 +5492,6 @@ dependencies = [ "az-tdx-vtpm", "base64 0.22.1", "clap", - "enclave-contract", "hex", "hkdf", "jsonrpsee", diff --git a/Cargo.toml b/Cargo.toml index 758f9df9..53e446ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "crates/enclave", "crates/enclave/derive", "crates/enclave-server", - "crates/enclave-contract", ] [workspace.lints] @@ -113,7 +112,6 @@ readme = "README.md" [workspace.dependencies] seismic-enclave-derive = { path = "crates/enclave/derive" } seismic-enclave = { path = "crates/enclave" } -enclave-contract = { path = "crates/enclave-contract" } # coco deps # attestation-service depends on attestation-agent, ensure versions are compatible when updating diff --git a/crates/enclave-contract/Cargo.toml b/crates/enclave-contract/Cargo.toml deleted file mode 100644 index 03d7b6fa..00000000 --- a/crates/enclave-contract/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "enclave-contract" -version = "0.1.0" -edition = "2021" - -[dependencies] -alloy.workspace = true -anyhow.workspace = true -reqwest.workspace = true - -[dev-dependencies] -tokio = { workspace = true, features = ["full", "test-util"] } diff --git a/crates/enclave-contract/foundry.toml b/crates/enclave-contract/foundry.toml index 36661944..9b66d98a 100644 --- a/crates/enclave-contract/foundry.toml +++ b/crates/enclave-contract/foundry.toml @@ -1,4 +1,4 @@ [profile.default] -src = "contracts" +src = "src" out = "out" libs = ["lib"] \ No newline at end of file diff --git a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol b/crates/enclave-contract/src/MultisigUpgradeOperator.sol similarity index 100% rename from crates/enclave-contract/contracts/MultisigUpgradeOperator.sol rename to crates/enclave-contract/src/MultisigUpgradeOperator.sol diff --git a/crates/enclave-contract/contracts/UpgradeOperator.sol b/crates/enclave-contract/src/UpgradeOperator.sol similarity index 100% rename from crates/enclave-contract/contracts/UpgradeOperator.sol rename to crates/enclave-contract/src/UpgradeOperator.sol diff --git a/crates/enclave-contract/contracts/build.sh b/crates/enclave-contract/src/build.sh similarity index 100% rename from crates/enclave-contract/contracts/build.sh rename to crates/enclave-contract/src/build.sh diff --git a/crates/enclave-contract/src/contract_interface.rs b/crates/enclave-contract/src/contract_interface.rs deleted file mode 100644 index b57d3503..00000000 --- a/crates/enclave-contract/src/contract_interface.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! Contract interface definitions and types - -use alloy::{ - network::EthereumWallet, - primitives::{bytes, Bytes, U256}, - providers::ProviderBuilder, - signers::local::PrivateKeySigner, - sol, -}; - -// Generate contract bindings for the factory -sol! { - #[sol(rpc)] - interface UpgradeOperatorFactory { - function deployUpgradeOperator(bytes32 salt) external returns (address); - function deployUpgradeOperatorWithOwner(bytes32 salt, address owner) external returns (address); - function deployMultisigUpgradeOperator(bytes32 salt, address upgradeOperator) external returns (address); - function deployUpgradeOperatorWithMultisig(bytes32 upgradeOperatorSalt, bytes32 multisigSalt) external returns (address, address); - function computeUpgradeOperatorAddress(bytes32 salt) external view returns (address); - function computeUpgradeOperatorAddressWithOwner(bytes32 salt, address owner) external view returns (address); - function computeMultisigUpgradeOperatorAddress(bytes32 salt, address upgradeOperator) external view returns (address); - function isDeployed(address contractAddress) external view returns (bool); - } -} - -// Generate contract bindings for the upgrade operator -sol! { - #[sol(rpc)] - interface UpgradeOperator { - function set_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external; - function get_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4) external view returns (bool); - function computeIdV1(bytes mrtd, bytes mrseam, bytes pcr4) external pure returns (bytes32); - function owner() external view returns (address); - } -} - -// Generate contract bindings for the multisig contract -sol! { - #[sol(rpc)] - interface MultisigUpgradeOperator { - function createProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external returns (bytes32); - function vote(bytes32 proposalId, bool approved) external; - function executeProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external; - function getVoteCount(bytes32 proposalId) external view returns (uint256 approvalCount, uint256 totalVotes); - function canExecute(bytes32 proposalId) external view returns (bool); - function computeProposalIdV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external view returns (bytes32); - function proposalNonce() external view returns (uint256); - function signer1() external view returns (address); - function signer2() external view returns (address); - function signer3() external view returns (address); - function upgradeOperator() external view returns (address); - function setUpgradeOperator(address _upgradeOperator) external; - function factory() external view returns (address); - } -} - -/// Represents the proposal parameters for upgrade validation -/// This struct makes it easy to change the parameters in the future -/// To change parameters, just modify this struct and update the contract interfaces -#[derive(Debug, Clone)] -pub struct ProposalParamsV1 { - pub mrtd: Bytes, // 48 bytes - pub mrseam: Bytes, // 48 bytes - pub pcr4: Bytes, // 32 bytes -} - -impl ProposalParamsV1 { - /// Creates a new ProposalParams instance - pub fn new(mrtd: Bytes, mrseam: Bytes, pcr4: Bytes) -> Self { - Self { mrtd, mrseam, pcr4 } - } - - /// Creates test proposal parameters - /// Based off the devbox values - pub fn test_params() -> Self { - Self { - mrtd: bytes!("cbd40696f617d42254fc7037469cbcf1414fe173678798cfa1070b7d40e26fa8175b99d0cd245994278f980dec73146a"), - mrseam: bytes!("9790d89a10210ec6968a773cee2ca05b5aa97309f36727a968527be4606fc19e6f73acce350946c9d46a9bf7a63f8430"), - pcr4: bytes!("6f2f7d9a42b35a2f8f9d7bf366ca3e369a45d004f3ac49b0a93785fe817c82b5"), - } - } -} - -/// Creates a proposal in the MultisigUpgradeOperator contract. -/// -/// # Arguments -/// -/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. -/// * `sk` - A string slice representing the private key used to sign the transaction. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `params` - The proposal parameters for the proposal. -/// * `status` - The status to set. -/// -/// # Returns -/// -/// * `Result<([u8; 32], u64), anyhow::Error>` - Returns (proposal_id, nonce) if successful, or an `anyhow::Error` if an error occurs. -pub async fn create_multisig_proposal( - multisig_address: alloy::primitives::Address, - sk: &str, - rpc: &str, - params: &ProposalParamsV1, - status: bool, -) -> Result<([u8; 32], u64), anyhow::Error> { - // Set up signer with the provided sk - let signer: PrivateKeySigner = sk.parse().unwrap(); - let wallet = EthereumWallet::from(signer); - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); - - // Create multisig contract instance - let multisig_contract = - MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider.clone())); - - // Get current nonce before creating proposal (for debugging/logging if needed) - let _current_nonce = multisig_contract - .proposalNonce() - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to get current nonce: {:?}", e))?; - - // Create proposal - let create_tx = multisig_contract.createProposalV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - ); - let create_pending = create_tx.send().await.map_err(|e| { - anyhow::anyhow!( - "create_multisig_proposal create proposal tx failed: {:?}", - e - ) - })?; - - let _create_receipt = create_pending - .watch() - .await - .map_err(|e| anyhow::anyhow!("Failed to get proposal creation receipt: {:?}", e))?; - - // Get the new nonce after proposal creation - let new_nonce = multisig_contract - .proposalNonce() - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to get new nonce: {:?}", e))?; - - // Compute the proposal ID using the new nonce - let proposal_id = multisig_contract - .computeProposalIdV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - new_nonce, - ) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to compute proposal ID: {:?}", e))?; - - println!( - "Proposal created with ID: {:?}, nonce: {}", - proposal_id, new_nonce - ); - - Ok((proposal_id.into(), new_nonce.try_into().unwrap())) -} - -/// Votes on a proposal in the MultisigUpgradeOperator contract. -/// -/// # Arguments -/// -/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. -/// * `sk` - A string slice representing the private key used to sign the transaction. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `proposal_id` - The proposal ID to vote on. -/// * `approved` - Whether to approve the proposal. -/// -/// # Returns -/// -/// * `Result<(), anyhow::Error>` - Returns success or an `anyhow::Error` if an error occurs. -pub async fn vote_on_multisig_proposal( - multisig_address: alloy::primitives::Address, - sk: &str, - rpc: &str, - proposal_id: [u8; 32], - approved: bool, -) -> Result<(), anyhow::Error> { - // Set up signer with the provided sk - let signer: PrivateKeySigner = sk.parse().unwrap(); - let wallet = EthereumWallet::from(signer); - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); - - // Create multisig contract instance - let multisig_contract = - MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); - - // Vote on proposal - let vote_tx = multisig_contract.vote(proposal_id.into(), approved); - let vote_pending = vote_tx - .send() - .await - .map_err(|e| anyhow::anyhow!("Failed to vote on proposal: {:?}", e))?; - - let _vote_receipt = vote_pending - .watch() - .await - .map_err(|e| anyhow::anyhow!("Failed to get vote receipt: {:?}", e))?; - - println!("Voted {} on proposal: {:?}", approved, proposal_id); - - Ok(()) -} - -/// Executes a proposal in the MultisigUpgradeOperator contract. -/// -/// # Arguments -/// -/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. -/// * `sk` - A string slice representing the private key used to sign the transaction. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `params` - The proposal parameters for the proposal. -/// * `status` - The status to set. -/// * `nonce` - The nonce to use for the proposal execution. -/// -/// # Returns -/// -/// * `Result<(), anyhow::Error>` - Returns success or an `anyhow::Error` if an error occurs. -pub async fn execute_multisig_proposal( - multisig_address: alloy::primitives::Address, - sk: &str, - rpc: &str, - params: &ProposalParamsV1, - status: bool, - nonce: u64, -) -> Result<(), anyhow::Error> { - // Set up signer with the provided sk - let signer: PrivateKeySigner = sk.parse().unwrap(); - let wallet = EthereumWallet::from(signer); - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); - - // Create multisig contract instance - let multisig_contract = - MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); - - // Execute proposal - let execute_tx = multisig_contract.executeProposalV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - U256::from(nonce), - ); - let execute_pending = execute_tx - .send() - .await - .map_err(|e| anyhow::anyhow!("Failed to execute proposal: {:?}", e))?; - - let _execute_receipt = execute_pending - .watch() - .await - .map_err(|e| anyhow::anyhow!("Failed to get execution receipt: {:?}", e))?; - - println!("Proposal executed successfully"); - - Ok(()) -} - -/// Checks if a proposal can be executed in the MultisigUpgradeOperator contract. -/// -/// # Arguments -/// -/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `proposal_id` - The proposal ID to check. -/// -/// # Returns -/// -/// * `Result` - Returns true if the proposal can be executed, or an `anyhow::Error` if an error occurs. -pub async fn can_execute_multisig_proposal( - multisig_address: alloy::primitives::Address, - rpc: &str, - proposal_id: [u8; 32], -) -> Result { - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().connect_http(rpc_url); - - // Create multisig contract instance - let multisig_contract = - MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); - - // Check if proposal can be executed - let can_execute = multisig_contract - .canExecute(proposal_id.into()) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to check if proposal can be executed: {:?}", e))?; - - Ok(can_execute) -} - -/// Gets the vote count for a proposal in the MultisigUpgradeOperator contract. -/// -/// # Arguments -/// -/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `proposal_id` - The proposal ID to check. -/// -/// # Returns -/// -/// * `Result<(u64, u64), anyhow::Error>` - Returns (approval_count, total_votes) if successful, or an `anyhow::Error` if an error occurs. -pub async fn get_multisig_vote_count( - multisig_address: alloy::primitives::Address, - rpc: &str, - proposal_id: [u8; 32], -) -> Result<(u64, u64), anyhow::Error> { - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().connect_http(rpc_url); - - // Create multisig contract instance - let multisig_contract = - MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); - - // Get vote count - let result = multisig_contract - .getVoteCount(proposal_id.into()) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to get vote count: {:?}", e))?; - - Ok(( - result.approvalCount.try_into().unwrap(), - result.totalVotes.try_into().unwrap(), - )) -} - -/// Checks if a proposal configuration is approved in the UpgradeOperator contract. -/// -/// # Arguments -/// -/// * `upgrade_operator_address` - The address of the UpgradeOperator contract. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `params` - The proposal parameters to check. -/// -/// # Returns -/// -/// * `Result` - Returns true if the proposal is approved, or an `anyhow::Error` if an error occurs. -pub async fn check_proposal_status_v1( - upgrade_operator_address: alloy::primitives::Address, - rpc: &str, - params: &ProposalParamsV1, -) -> Result { - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().connect_http(rpc_url); - - // Create upgrade operator contract instance - let upgrade_operator_contract = - UpgradeOperator::new(upgrade_operator_address, std::sync::Arc::new(provider)); - - // Check proposal status - let status = upgrade_operator_contract - .get_id_status_v1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - ) - .call() - .await - .map_err(|e| anyhow::anyhow!("check_proposal_status_v1 failed: {:?}", e))?; - - Ok(status) -} - -/// Computes the CREATE2 address for a contract without deploying it. -/// -/// # Arguments -/// -/// * `factory_address` - The address of the factory contract. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `salt` - A 32-byte salt value for CREATE2 deployment. -/// -/// # Returns -/// -/// * `Result` - Returns the computed CREATE2 address if successful, or an `anyhow::Error` if an error occurs. -pub async fn compute_create2_address( - factory_address: alloy::primitives::Address, - rpc: &str, - salt: [u8; 32], -) -> Result { - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().connect_http(rpc_url); - - let factory_contract = - UpgradeOperatorFactory::new(factory_address, std::sync::Arc::new(provider)); - - let expected_address = factory_contract - .computeUpgradeOperatorAddress(salt.into()) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to compute CREATE2 address: {:?}", e))?; - - Ok(expected_address) -} diff --git a/crates/enclave-contract/src/lib.rs b/crates/enclave-contract/src/lib.rs deleted file mode 100644 index 49fcb1cb..00000000 --- a/crates/enclave-contract/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Enclave Contract - Utilities for deploying and interacting with smart contracts -//! -//! This crate provides utilities for deploying smart contracts using both regular -//! deployment and CREATE2 deployment through factory contracts. - -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -pub mod contract_interface; -pub use contract_interface::*; - -/// Anvil's first secret key that they publically expose and fund for testing -pub const ANVIL_ALICE_SK: &str = - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; - -pub const ANVIL_BOB_SK: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; - -pub const ANVIL_CHARLIE_SK: &str = - "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"; - -/// The address of the UpgradeOperator contract -/// seismic-reth deploys this code at genesis -pub const UPGRADE_OPERATOR_ADDRESS: &str = "0x1000000000000000000000000000000000000001"; - -/// The address of the MultisigUpgradeOperator contract -/// which can control state transitions of the UpgradeOperator -/// seismic-reth deploys this code at genesis -pub const UPGRADE_MULTISIG_ADDRESS: &str = "0x1000000000000000000000000000000000000002"; diff --git a/crates/enclave-server/Cargo.toml b/crates/enclave-server/Cargo.toml index d046cfb8..85b15d80 100644 --- a/crates/enclave-server/Cargo.toml +++ b/crates/enclave-server/Cargo.toml @@ -24,7 +24,7 @@ path = "src/bin/install_policies.rs" [dependencies] seismic-enclave.workspace = true -enclave-contract.workspace = true +#enclave-contract.workspace = true alloy.workspace = true attestation-agent.workspace = true @@ -59,7 +59,7 @@ serde = {workspace = true, optional = true } clap = { version = "4.5", features = ["derive"] } [dev-dependencies] -enclave-contract.workspace = true +#enclave-contract.workspace = true serial_test = "3.2.0" tempfile = "3.17.1" reqwest.workspace = true diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 1c21fa5f..5dffbcab 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -44,26 +44,7 @@ if ! sudo supervisorctl status reth | grep -q "RUNNING"; then fi echo "โœ… reth service is running" -# Test 1: Run multisig upgrade operator workflow test -echo "๐Ÿงช Running test_multisig_upgrade_operator_workflow..." -cd crates/enclave-contract -# Build tests and get binary paths -OUTPUT=$(cargo test --no-run 2>&1) -echo "$OUTPUT" -mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*multisig_test-[a-z0-9]*') -if [ ${#binaries[@]} -eq 0 ]; then - echo "โŒ Could not find multisig_test binaries" - exit 1 -fi -echo "Found binaries: ${binaries[*]}" -# Run the first binary with the specific test -if ! "${binaries[0]}" test_multisig_upgrade_operator_workflow; then - echo "โŒ test_multisig_upgrade_operator_workflow failed" - exit 1 -fi -echo "โœ… test_multisig_upgrade_operator_workflow passed" - -# Test 2: Run boot share root key test +# Test 1: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." cd ../enclave-server ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" @@ -88,7 +69,7 @@ if ! sudo "${binaries[0]}" test_boot_share_root_key; then fi echo "โœ… test_boot_share_root_key passed" -# Test 3: Run snapshot integration handlers test +# Test 2: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 From cda36d127822991e1af5fec8d8b2f514af520ae2 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 11:21:14 -0400 Subject: [PATCH 03/21] Revert "make enclave-contract crate only solidity" This reverts commit 5b15e5d5b587ab2b8b8090ef3802d99720205497. --- Cargo.lock | 11 + Cargo.toml | 2 + crates/enclave-contract/Cargo.toml | 12 + .../MultisigUpgradeOperator.sol | 0 .../{src => contracts}/UpgradeOperator.sol | 0 .../{src => contracts}/build.sh | 0 crates/enclave-contract/foundry.toml | 2 +- .../src/contract_interface.rs | 405 ++++++++++++++++++ crates/enclave-contract/src/lib.rs | 28 ++ crates/enclave-server/Cargo.toml | 4 +- scripts/run_integration_tests.sh | 23 +- 11 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 crates/enclave-contract/Cargo.toml rename crates/enclave-contract/{src => contracts}/MultisigUpgradeOperator.sol (100%) rename crates/enclave-contract/{src => contracts}/UpgradeOperator.sol (100%) rename crates/enclave-contract/{src => contracts}/build.sh (100%) create mode 100644 crates/enclave-contract/src/contract_interface.rs create mode 100644 crates/enclave-contract/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1bc55ec9..121ec8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2214,6 +2214,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enclave-contract" +version = "0.1.0" +dependencies = [ + "alloy", + "anyhow", + "reqwest", + "tokio", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -5492,6 +5502,7 @@ dependencies = [ "az-tdx-vtpm", "base64 0.22.1", "clap", + "enclave-contract", "hex", "hkdf", "jsonrpsee", diff --git a/Cargo.toml b/Cargo.toml index 53e446ca..758f9df9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/enclave", "crates/enclave/derive", "crates/enclave-server", + "crates/enclave-contract", ] [workspace.lints] @@ -112,6 +113,7 @@ readme = "README.md" [workspace.dependencies] seismic-enclave-derive = { path = "crates/enclave/derive" } seismic-enclave = { path = "crates/enclave" } +enclave-contract = { path = "crates/enclave-contract" } # coco deps # attestation-service depends on attestation-agent, ensure versions are compatible when updating diff --git a/crates/enclave-contract/Cargo.toml b/crates/enclave-contract/Cargo.toml new file mode 100644 index 00000000..03d7b6fa --- /dev/null +++ b/crates/enclave-contract/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "enclave-contract" +version = "0.1.0" +edition = "2021" + +[dependencies] +alloy.workspace = true +anyhow.workspace = true +reqwest.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["full", "test-util"] } diff --git a/crates/enclave-contract/src/MultisigUpgradeOperator.sol b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol similarity index 100% rename from crates/enclave-contract/src/MultisigUpgradeOperator.sol rename to crates/enclave-contract/contracts/MultisigUpgradeOperator.sol diff --git a/crates/enclave-contract/src/UpgradeOperator.sol b/crates/enclave-contract/contracts/UpgradeOperator.sol similarity index 100% rename from crates/enclave-contract/src/UpgradeOperator.sol rename to crates/enclave-contract/contracts/UpgradeOperator.sol diff --git a/crates/enclave-contract/src/build.sh b/crates/enclave-contract/contracts/build.sh similarity index 100% rename from crates/enclave-contract/src/build.sh rename to crates/enclave-contract/contracts/build.sh diff --git a/crates/enclave-contract/foundry.toml b/crates/enclave-contract/foundry.toml index 9b66d98a..36661944 100644 --- a/crates/enclave-contract/foundry.toml +++ b/crates/enclave-contract/foundry.toml @@ -1,4 +1,4 @@ [profile.default] -src = "src" +src = "contracts" out = "out" libs = ["lib"] \ No newline at end of file diff --git a/crates/enclave-contract/src/contract_interface.rs b/crates/enclave-contract/src/contract_interface.rs new file mode 100644 index 00000000..b57d3503 --- /dev/null +++ b/crates/enclave-contract/src/contract_interface.rs @@ -0,0 +1,405 @@ +//! Contract interface definitions and types + +use alloy::{ + network::EthereumWallet, + primitives::{bytes, Bytes, U256}, + providers::ProviderBuilder, + signers::local::PrivateKeySigner, + sol, +}; + +// Generate contract bindings for the factory +sol! { + #[sol(rpc)] + interface UpgradeOperatorFactory { + function deployUpgradeOperator(bytes32 salt) external returns (address); + function deployUpgradeOperatorWithOwner(bytes32 salt, address owner) external returns (address); + function deployMultisigUpgradeOperator(bytes32 salt, address upgradeOperator) external returns (address); + function deployUpgradeOperatorWithMultisig(bytes32 upgradeOperatorSalt, bytes32 multisigSalt) external returns (address, address); + function computeUpgradeOperatorAddress(bytes32 salt) external view returns (address); + function computeUpgradeOperatorAddressWithOwner(bytes32 salt, address owner) external view returns (address); + function computeMultisigUpgradeOperatorAddress(bytes32 salt, address upgradeOperator) external view returns (address); + function isDeployed(address contractAddress) external view returns (bool); + } +} + +// Generate contract bindings for the upgrade operator +sol! { + #[sol(rpc)] + interface UpgradeOperator { + function set_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external; + function get_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4) external view returns (bool); + function computeIdV1(bytes mrtd, bytes mrseam, bytes pcr4) external pure returns (bytes32); + function owner() external view returns (address); + } +} + +// Generate contract bindings for the multisig contract +sol! { + #[sol(rpc)] + interface MultisigUpgradeOperator { + function createProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external returns (bytes32); + function vote(bytes32 proposalId, bool approved) external; + function executeProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external; + function getVoteCount(bytes32 proposalId) external view returns (uint256 approvalCount, uint256 totalVotes); + function canExecute(bytes32 proposalId) external view returns (bool); + function computeProposalIdV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external view returns (bytes32); + function proposalNonce() external view returns (uint256); + function signer1() external view returns (address); + function signer2() external view returns (address); + function signer3() external view returns (address); + function upgradeOperator() external view returns (address); + function setUpgradeOperator(address _upgradeOperator) external; + function factory() external view returns (address); + } +} + +/// Represents the proposal parameters for upgrade validation +/// This struct makes it easy to change the parameters in the future +/// To change parameters, just modify this struct and update the contract interfaces +#[derive(Debug, Clone)] +pub struct ProposalParamsV1 { + pub mrtd: Bytes, // 48 bytes + pub mrseam: Bytes, // 48 bytes + pub pcr4: Bytes, // 32 bytes +} + +impl ProposalParamsV1 { + /// Creates a new ProposalParams instance + pub fn new(mrtd: Bytes, mrseam: Bytes, pcr4: Bytes) -> Self { + Self { mrtd, mrseam, pcr4 } + } + + /// Creates test proposal parameters + /// Based off the devbox values + pub fn test_params() -> Self { + Self { + mrtd: bytes!("cbd40696f617d42254fc7037469cbcf1414fe173678798cfa1070b7d40e26fa8175b99d0cd245994278f980dec73146a"), + mrseam: bytes!("9790d89a10210ec6968a773cee2ca05b5aa97309f36727a968527be4606fc19e6f73acce350946c9d46a9bf7a63f8430"), + pcr4: bytes!("6f2f7d9a42b35a2f8f9d7bf366ca3e369a45d004f3ac49b0a93785fe817c82b5"), + } + } +} + +/// Creates a proposal in the MultisigUpgradeOperator contract. +/// +/// # Arguments +/// +/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. +/// * `sk` - A string slice representing the private key used to sign the transaction. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `params` - The proposal parameters for the proposal. +/// * `status` - The status to set. +/// +/// # Returns +/// +/// * `Result<([u8; 32], u64), anyhow::Error>` - Returns (proposal_id, nonce) if successful, or an `anyhow::Error` if an error occurs. +pub async fn create_multisig_proposal( + multisig_address: alloy::primitives::Address, + sk: &str, + rpc: &str, + params: &ProposalParamsV1, + status: bool, +) -> Result<([u8; 32], u64), anyhow::Error> { + // Set up signer with the provided sk + let signer: PrivateKeySigner = sk.parse().unwrap(); + let wallet = EthereumWallet::from(signer); + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); + + // Create multisig contract instance + let multisig_contract = + MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider.clone())); + + // Get current nonce before creating proposal (for debugging/logging if needed) + let _current_nonce = multisig_contract + .proposalNonce() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to get current nonce: {:?}", e))?; + + // Create proposal + let create_tx = multisig_contract.createProposalV1( + params.mrtd.clone(), + params.mrseam.clone(), + params.pcr4.clone(), + status, + ); + let create_pending = create_tx.send().await.map_err(|e| { + anyhow::anyhow!( + "create_multisig_proposal create proposal tx failed: {:?}", + e + ) + })?; + + let _create_receipt = create_pending + .watch() + .await + .map_err(|e| anyhow::anyhow!("Failed to get proposal creation receipt: {:?}", e))?; + + // Get the new nonce after proposal creation + let new_nonce = multisig_contract + .proposalNonce() + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to get new nonce: {:?}", e))?; + + // Compute the proposal ID using the new nonce + let proposal_id = multisig_contract + .computeProposalIdV1( + params.mrtd.clone(), + params.mrseam.clone(), + params.pcr4.clone(), + status, + new_nonce, + ) + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to compute proposal ID: {:?}", e))?; + + println!( + "Proposal created with ID: {:?}, nonce: {}", + proposal_id, new_nonce + ); + + Ok((proposal_id.into(), new_nonce.try_into().unwrap())) +} + +/// Votes on a proposal in the MultisigUpgradeOperator contract. +/// +/// # Arguments +/// +/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. +/// * `sk` - A string slice representing the private key used to sign the transaction. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `proposal_id` - The proposal ID to vote on. +/// * `approved` - Whether to approve the proposal. +/// +/// # Returns +/// +/// * `Result<(), anyhow::Error>` - Returns success or an `anyhow::Error` if an error occurs. +pub async fn vote_on_multisig_proposal( + multisig_address: alloy::primitives::Address, + sk: &str, + rpc: &str, + proposal_id: [u8; 32], + approved: bool, +) -> Result<(), anyhow::Error> { + // Set up signer with the provided sk + let signer: PrivateKeySigner = sk.parse().unwrap(); + let wallet = EthereumWallet::from(signer); + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); + + // Create multisig contract instance + let multisig_contract = + MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); + + // Vote on proposal + let vote_tx = multisig_contract.vote(proposal_id.into(), approved); + let vote_pending = vote_tx + .send() + .await + .map_err(|e| anyhow::anyhow!("Failed to vote on proposal: {:?}", e))?; + + let _vote_receipt = vote_pending + .watch() + .await + .map_err(|e| anyhow::anyhow!("Failed to get vote receipt: {:?}", e))?; + + println!("Voted {} on proposal: {:?}", approved, proposal_id); + + Ok(()) +} + +/// Executes a proposal in the MultisigUpgradeOperator contract. +/// +/// # Arguments +/// +/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. +/// * `sk` - A string slice representing the private key used to sign the transaction. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `params` - The proposal parameters for the proposal. +/// * `status` - The status to set. +/// * `nonce` - The nonce to use for the proposal execution. +/// +/// # Returns +/// +/// * `Result<(), anyhow::Error>` - Returns success or an `anyhow::Error` if an error occurs. +pub async fn execute_multisig_proposal( + multisig_address: alloy::primitives::Address, + sk: &str, + rpc: &str, + params: &ProposalParamsV1, + status: bool, + nonce: u64, +) -> Result<(), anyhow::Error> { + // Set up signer with the provided sk + let signer: PrivateKeySigner = sk.parse().unwrap(); + let wallet = EthereumWallet::from(signer); + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); + + // Create multisig contract instance + let multisig_contract = + MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); + + // Execute proposal + let execute_tx = multisig_contract.executeProposalV1( + params.mrtd.clone(), + params.mrseam.clone(), + params.pcr4.clone(), + status, + U256::from(nonce), + ); + let execute_pending = execute_tx + .send() + .await + .map_err(|e| anyhow::anyhow!("Failed to execute proposal: {:?}", e))?; + + let _execute_receipt = execute_pending + .watch() + .await + .map_err(|e| anyhow::anyhow!("Failed to get execution receipt: {:?}", e))?; + + println!("Proposal executed successfully"); + + Ok(()) +} + +/// Checks if a proposal can be executed in the MultisigUpgradeOperator contract. +/// +/// # Arguments +/// +/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `proposal_id` - The proposal ID to check. +/// +/// # Returns +/// +/// * `Result` - Returns true if the proposal can be executed, or an `anyhow::Error` if an error occurs. +pub async fn can_execute_multisig_proposal( + multisig_address: alloy::primitives::Address, + rpc: &str, + proposal_id: [u8; 32], +) -> Result { + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().connect_http(rpc_url); + + // Create multisig contract instance + let multisig_contract = + MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); + + // Check if proposal can be executed + let can_execute = multisig_contract + .canExecute(proposal_id.into()) + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to check if proposal can be executed: {:?}", e))?; + + Ok(can_execute) +} + +/// Gets the vote count for a proposal in the MultisigUpgradeOperator contract. +/// +/// # Arguments +/// +/// * `multisig_address` - The address of the MultisigUpgradeOperator contract. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `proposal_id` - The proposal ID to check. +/// +/// # Returns +/// +/// * `Result<(u64, u64), anyhow::Error>` - Returns (approval_count, total_votes) if successful, or an `anyhow::Error` if an error occurs. +pub async fn get_multisig_vote_count( + multisig_address: alloy::primitives::Address, + rpc: &str, + proposal_id: [u8; 32], +) -> Result<(u64, u64), anyhow::Error> { + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().connect_http(rpc_url); + + // Create multisig contract instance + let multisig_contract = + MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); + + // Get vote count + let result = multisig_contract + .getVoteCount(proposal_id.into()) + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to get vote count: {:?}", e))?; + + Ok(( + result.approvalCount.try_into().unwrap(), + result.totalVotes.try_into().unwrap(), + )) +} + +/// Checks if a proposal configuration is approved in the UpgradeOperator contract. +/// +/// # Arguments +/// +/// * `upgrade_operator_address` - The address of the UpgradeOperator contract. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `params` - The proposal parameters to check. +/// +/// # Returns +/// +/// * `Result` - Returns true if the proposal is approved, or an `anyhow::Error` if an error occurs. +pub async fn check_proposal_status_v1( + upgrade_operator_address: alloy::primitives::Address, + rpc: &str, + params: &ProposalParamsV1, +) -> Result { + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().connect_http(rpc_url); + + // Create upgrade operator contract instance + let upgrade_operator_contract = + UpgradeOperator::new(upgrade_operator_address, std::sync::Arc::new(provider)); + + // Check proposal status + let status = upgrade_operator_contract + .get_id_status_v1( + params.mrtd.clone(), + params.mrseam.clone(), + params.pcr4.clone(), + ) + .call() + .await + .map_err(|e| anyhow::anyhow!("check_proposal_status_v1 failed: {:?}", e))?; + + Ok(status) +} + +/// Computes the CREATE2 address for a contract without deploying it. +/// +/// # Arguments +/// +/// * `factory_address` - The address of the factory contract. +/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. +/// * `salt` - A 32-byte salt value for CREATE2 deployment. +/// +/// # Returns +/// +/// * `Result` - Returns the computed CREATE2 address if successful, or an `anyhow::Error` if an error occurs. +pub async fn compute_create2_address( + factory_address: alloy::primitives::Address, + rpc: &str, + salt: [u8; 32], +) -> Result { + let rpc_url = reqwest::Url::parse(rpc).unwrap(); + let provider = ProviderBuilder::new().connect_http(rpc_url); + + let factory_contract = + UpgradeOperatorFactory::new(factory_address, std::sync::Arc::new(provider)); + + let expected_address = factory_contract + .computeUpgradeOperatorAddress(salt.into()) + .call() + .await + .map_err(|e| anyhow::anyhow!("Failed to compute CREATE2 address: {:?}", e))?; + + Ok(expected_address) +} diff --git a/crates/enclave-contract/src/lib.rs b/crates/enclave-contract/src/lib.rs new file mode 100644 index 00000000..49fcb1cb --- /dev/null +++ b/crates/enclave-contract/src/lib.rs @@ -0,0 +1,28 @@ +//! Enclave Contract - Utilities for deploying and interacting with smart contracts +//! +//! This crate provides utilities for deploying smart contracts using both regular +//! deployment and CREATE2 deployment through factory contracts. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +pub mod contract_interface; +pub use contract_interface::*; + +/// Anvil's first secret key that they publically expose and fund for testing +pub const ANVIL_ALICE_SK: &str = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + +pub const ANVIL_BOB_SK: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + +pub const ANVIL_CHARLIE_SK: &str = + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"; + +/// The address of the UpgradeOperator contract +/// seismic-reth deploys this code at genesis +pub const UPGRADE_OPERATOR_ADDRESS: &str = "0x1000000000000000000000000000000000000001"; + +/// The address of the MultisigUpgradeOperator contract +/// which can control state transitions of the UpgradeOperator +/// seismic-reth deploys this code at genesis +pub const UPGRADE_MULTISIG_ADDRESS: &str = "0x1000000000000000000000000000000000000002"; diff --git a/crates/enclave-server/Cargo.toml b/crates/enclave-server/Cargo.toml index 85b15d80..d046cfb8 100644 --- a/crates/enclave-server/Cargo.toml +++ b/crates/enclave-server/Cargo.toml @@ -24,7 +24,7 @@ path = "src/bin/install_policies.rs" [dependencies] seismic-enclave.workspace = true -#enclave-contract.workspace = true +enclave-contract.workspace = true alloy.workspace = true attestation-agent.workspace = true @@ -59,7 +59,7 @@ serde = {workspace = true, optional = true } clap = { version = "4.5", features = ["derive"] } [dev-dependencies] -#enclave-contract.workspace = true +enclave-contract.workspace = true serial_test = "3.2.0" tempfile = "3.17.1" reqwest.workspace = true diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 5dffbcab..1c21fa5f 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -44,7 +44,26 @@ if ! sudo supervisorctl status reth | grep -q "RUNNING"; then fi echo "โœ… reth service is running" -# Test 1: Run boot share root key test +# Test 1: Run multisig upgrade operator workflow test +echo "๐Ÿงช Running test_multisig_upgrade_operator_workflow..." +cd crates/enclave-contract +# Build tests and get binary paths +OUTPUT=$(cargo test --no-run 2>&1) +echo "$OUTPUT" +mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*multisig_test-[a-z0-9]*') +if [ ${#binaries[@]} -eq 0 ]; then + echo "โŒ Could not find multisig_test binaries" + exit 1 +fi +echo "Found binaries: ${binaries[*]}" +# Run the first binary with the specific test +if ! "${binaries[0]}" test_multisig_upgrade_operator_workflow; then + echo "โŒ test_multisig_upgrade_operator_workflow failed" + exit 1 +fi +echo "โœ… test_multisig_upgrade_operator_workflow passed" + +# Test 2: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." cd ../enclave-server ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" @@ -69,7 +88,7 @@ if ! sudo "${binaries[0]}" test_boot_share_root_key; then fi echo "โœ… test_boot_share_root_key passed" -# Test 2: Run snapshot integration handlers test +# Test 3: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 From 7b82ee279a1e727be5b89e7c93da7a5995a3ce23 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 13:21:41 -0400 Subject: [PATCH 04/21] change contract mapping to store by measurement hash instead of tag hash --- .../contracts/MultisigUpgradeOperator.sol | 32 +++--- .../contracts/UpgradeOperator.sol | 107 +++++++++++------- .../test/MultisigUpgradeOperator.t.sol | 51 +++++---- 3 files changed, 116 insertions(+), 74 deletions(-) diff --git a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol index 5d8730b5..11956e0d 100644 --- a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol +++ b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol @@ -120,19 +120,19 @@ contract MultisigUpgradeOperator { /** * @dev Creates a proposal to deprecate measurements - * @param tag The tag of measurements to deprecate + * @param measurements The measurements to deprecate * @return proposalId The unique identifier for this proposal */ function proposeDeprecateMeasurements( - string calldata tag + UpgradeOperator.Measurements calldata measurements ) external onlySigner returns (bytes32 proposalId) { - require(bytes(tag).length > 0, "Tag cannot be empty"); + require(bytes(measurements.tag).length > 0, "Tag cannot be empty"); proposalNonce++; proposalId = keccak256( abi.encodePacked( ProposalType.DEPRECATE_MEASUREMENTS, - tag, + measurements.tag, proposalNonce ) ); @@ -141,14 +141,14 @@ contract MultisigUpgradeOperator { require(!proposal.executed, "Proposal already exists"); proposal.proposalType = ProposalType.DEPRECATE_MEASUREMENTS; - proposal.tagHash = keccak256(abi.encodePacked(tag)); + proposal.tagHash = keccak256(abi.encodePacked(measurements.tag)); // Store tag in measurements.tag for execution - proposal.measurements.tag = tag; + proposal.measurements = measurements; emit ProposalCreated( proposalId, ProposalType.DEPRECATE_MEASUREMENTS, - tag, + measurements.tag, proposalNonce ); @@ -160,19 +160,19 @@ contract MultisigUpgradeOperator { /** * @dev Creates a proposal to reinstate measurements - * @param tag The tag of measurements to reinstate + * @param measurements The measurements to reinstate * @return proposalId The unique identifier for this proposal */ function proposeReinstateMeasurements( - string calldata tag + UpgradeOperator.Measurements calldata measurements ) external onlySigner returns (bytes32 proposalId) { - require(bytes(tag).length > 0, "Tag cannot be empty"); + require(bytes(measurements.tag).length > 0, "Tag cannot be empty"); proposalNonce++; proposalId = keccak256( abi.encodePacked( ProposalType.REINSTATE_MEASUREMENTS, - tag, + measurements.tag, proposalNonce ) ); @@ -181,14 +181,14 @@ contract MultisigUpgradeOperator { require(!proposal.executed, "Proposal already exists"); proposal.proposalType = ProposalType.REINSTATE_MEASUREMENTS; - proposal.tagHash = keccak256(abi.encodePacked(tag)); + proposal.tagHash = keccak256(abi.encodePacked(measurements.tag)); // Store tag in measurements.tag for execution - proposal.measurements.tag = tag; + proposal.measurements = measurements; emit ProposalCreated( proposalId, ProposalType.REINSTATE_MEASUREMENTS, - tag, + measurements.tag, proposalNonce ); @@ -252,11 +252,11 @@ contract MultisigUpgradeOperator { } else if ( proposal.proposalType == ProposalType.DEPRECATE_MEASUREMENTS ) { - upgradeOperator.deprecateMeasurements(proposal.measurements.tag); + upgradeOperator.deprecateMeasurements(proposal.measurements); } else if ( proposal.proposalType == ProposalType.REINSTATE_MEASUREMENTS ) { - upgradeOperator.reinstateMeasurement(proposal.measurements.tag); + upgradeOperator.reinstateMeasurement(proposal.measurements); } emit ProposalExecuted(proposalId, proposal.proposalType); diff --git a/crates/enclave-contract/contracts/UpgradeOperator.sol b/crates/enclave-contract/contracts/UpgradeOperator.sol index d50eaa1e..85593dd2 100644 --- a/crates/enclave-contract/contracts/UpgradeOperator.sol +++ b/crates/enclave-contract/contracts/UpgradeOperator.sol @@ -52,97 +52,106 @@ contract UpgradeOperator { "Registrar arrays length mismatch" ); + bytes32 measurementHash = _getMeasurementHash(measurements); + // Store the measurement - acceptedMeasurements[tagHash] = measurements; - acceptedTags.push(tagHash); + acceptedMeasurements[measurementHash] = measurements; + acceptedTags.push(measurementHash); tagExists[tagHash] = true; - emit MeasurementAdded(measurements.tag, tagHash); + emit MeasurementAdded(measurements.tag, measurementHash); } /** * @dev accept a currently deprecated set of measurements - * @param tag The tag of the measurement to reinstate + * @param m the measurement to reinstate */ function reinstateMeasurement( - string calldata tag + Measurements calldata m ) external onlyNetworkMultisig { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); + bytes32 tagHash = keccak256(abi.encodePacked(m.tag)); - // Check if measurement exists in accepted + bytes32 measurementHash = _getMeasurementHash(m); + + // Check if measurement exists in deprecated require( - bytes(deprecatedMeasurements[tagHash].tag).length > 0, - "No deprecated measurement with that tag" + keccak256( + abi.encodePacked(deprecatedMeasurements[measurementHash].tag) + ) == tagHash, + "No deprecated measurement with that tag or hash" ); // Move from deprecated to accepted - Measurements memory measurement = deprecatedMeasurements[tagHash]; - acceptedMeasurements[tagHash] = measurement; - acceptedTags.push(tagHash); + Measurements memory measurement = deprecatedMeasurements[ + measurementHash + ]; + acceptedMeasurements[measurementHash] = measurement; + acceptedTags.push(measurementHash); // Remove from deprecated - delete deprecatedMeasurements[tagHash]; - _removeFromArray(deprecatedTags, tagHash); + delete deprecatedMeasurements[measurementHash]; + _removeFromArray(deprecatedTags, measurementHash); - emit MeasurementAdded(tag, tagHash); + emit MeasurementAdded(m.tag, measurementHash); } /** * @dev Deprecate a currently allowed set of measurements - * @param tag The tag of the measurement to deprecate + * @param m the measurement to deprecate */ function deprecateMeasurements( - string calldata tag + Measurements calldata m ) external onlyNetworkMultisig { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); - + bytes32 tagHash = keccak256(abi.encodePacked(m.tag)); + bytes32 measurementHash = _getMeasurementHash(m); // Check if measurement exists in accepted require( - bytes(acceptedMeasurements[tagHash].tag).length > 0, - "No accepted measurement with that tag" + keccak256( + abi.encodePacked(acceptedMeasurements[measurementHash].tag) + ) == tagHash, + "No deprecated measurement with that tag or hash" ); // Move from accepted to deprecated - Measurements memory measurement = acceptedMeasurements[tagHash]; - deprecatedMeasurements[tagHash] = measurement; - deprecatedTags.push(tagHash); + Measurements memory measurement = acceptedMeasurements[measurementHash]; + deprecatedMeasurements[measurementHash] = measurement; + deprecatedTags.push(measurementHash); // Remove from accepted - delete acceptedMeasurements[tagHash]; - _removeFromArray(acceptedTags, tagHash); + delete acceptedMeasurements[measurementHash]; + _removeFromArray(acceptedTags, measurementHash); - emit MeasurementDeprecated(tag, tagHash); + emit MeasurementDeprecated(m.tag, measurementHash); } /** * @dev Check if measurements are accepted - * @param tag The tag to check + * @param measurementHash Hash of the measurements to check */ - function isAccepted(string calldata tag) external view returns (bool) { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); - return bytes(acceptedMeasurements[tagHash].tag).length > 0; + function isAccepted(bytes32 measurementHash) external view returns (bool) { + return bytes(acceptedMeasurements[measurementHash].tag).length > 0; } /** * @dev Check if measurements are accepted - * @param tag The tag to check + * @param measurementHash Hash of the measurements to check */ - function isDeprecated(string calldata tag) external view returns (bool) { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); - return bytes(deprecatedMeasurements[tagHash].tag).length > 0; + function isDeprecated( + bytes32 measurementHash + ) external view returns (bool) { + return bytes(deprecatedMeasurements[measurementHash].tag).length > 0; } /** * @dev Get accepted measurement by tag */ function getAcceptedMeasurement( - string calldata tag + bytes32 measurementHash ) external view returns (Measurements memory) { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); require( - bytes(acceptedMeasurements[tagHash].tag).length > 0, + bytes(acceptedMeasurements[measurementHash].tag).length > 0, "Measurement not found" ); - return acceptedMeasurements[tagHash]; + return acceptedMeasurements[measurementHash]; } /** @@ -152,6 +161,12 @@ contract UpgradeOperator { return acceptedTags.length; } + function getMeasurementHash( + Measurements calldata measurements + ) external pure returns (bytes32) { + return _getMeasurementHash(measurements); + } + /** * @dev Helper to remove element from array */ @@ -167,4 +182,18 @@ contract UpgradeOperator { } } } + + function _getMeasurementHash( + Measurements memory m + ) private pure returns (bytes32) { + return + keccak256( + abi.encode( + m.mrtd, + m.mrseam, + m.registrar_slots, + m.registrar_values + ) + ); + } } diff --git a/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol b/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol index da5e29f0..8f2c69e8 100644 --- a/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol +++ b/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol @@ -18,6 +18,8 @@ contract MultisigUpgradeOperatorTest is Test { // Test data UpgradeOperator.Measurements public testMeasurement1; UpgradeOperator.Measurements public testMeasurement2; + bytes32 public measurement1Hash; + bytes32 public measurement2Hash; event ProposalCreated( bytes32 indexed proposalId, @@ -83,6 +85,9 @@ contract MultisigUpgradeOperatorTest is Test { testMeasurement2.registrar_values[ 0 ] = hex"77777777777777777777777777777777777777777777777777777777"; + + measurement1Hash = upgradeOperator.getMeasurementHash(testMeasurement1); + measurement2Hash = upgradeOperator.getMeasurementHash(testMeasurement2); } // Test proposal creation for adding measurements @@ -182,12 +187,12 @@ contract MultisigUpgradeOperatorTest is Test { multisig.vote(addProposalId); multisig.executeProposal(addProposalId); - assertTrue(upgradeOperator.isAccepted("AzureV1")); + assertTrue(upgradeOperator.isAccepted(measurement1Hash)); // Now propose to deprecate it vm.prank(signer1); bytes32 deprecateProposalId = multisig.proposeDeprecateMeasurements( - "AzureV1" + testMeasurement1 ); // Vote to execute @@ -196,8 +201,8 @@ contract MultisigUpgradeOperatorTest is Test { multisig.executeProposal(deprecateProposalId); // Check it's deprecated - assertFalse(upgradeOperator.isAccepted("AzureV1")); - assertTrue(upgradeOperator.isDeprecated("AzureV1")); + assertFalse(upgradeOperator.isAccepted(measurement1Hash)); + assertTrue(upgradeOperator.isDeprecated(measurement1Hash)); } // Test reinstate measurements proposal @@ -213,26 +218,26 @@ contract MultisigUpgradeOperatorTest is Test { vm.prank(signer1); bytes32 deprecateProposalId = multisig.proposeDeprecateMeasurements( - "AzureV1" + testMeasurement1 ); vm.prank(signer2); multisig.vote(deprecateProposalId); multisig.executeProposal(deprecateProposalId); - assertFalse(upgradeOperator.isAccepted("AzureV1")); - assertTrue(upgradeOperator.isDeprecated("AzureV1")); + assertFalse(upgradeOperator.isAccepted(measurement1Hash)); + assertTrue(upgradeOperator.isDeprecated(measurement1Hash)); // Now propose to reinstate vm.prank(signer1); bytes32 reinstateProposalId = multisig.proposeReinstateMeasurements( - "AzureV1" + testMeasurement1 ); vm.prank(signer3); multisig.vote(reinstateProposalId); multisig.executeProposal(reinstateProposalId); // Check it's reinstated - assertTrue(upgradeOperator.isAccepted("AzureV1")); - assertFalse(upgradeOperator.isDeprecated("AzureV1")); + assertTrue(upgradeOperator.isAccepted(measurement1Hash)); + assertFalse(upgradeOperator.isDeprecated(measurement1Hash)); } // Test get vote status @@ -276,7 +281,8 @@ contract MultisigUpgradeOperatorTest is Test { vm.prank(signer2); multisig.vote(proposal1); multisig.executeProposal(proposal1); - assertTrue(upgradeOperator.isAccepted("AzureV1")); + + assertTrue(upgradeOperator.isAccepted(measurement1Hash)); // Signer2 proposes to add AWS measurements vm.prank(signer2); @@ -288,34 +294,39 @@ contract MultisigUpgradeOperatorTest is Test { // execute multisig.executeProposal(proposal2); - assertTrue(upgradeOperator.isAccepted("AWSV1")); + + assertTrue(upgradeOperator.isAccepted(measurement2Hash)); assertEq(upgradeOperator.getAcceptedCount(), 2); // Signer1 proposes to deprecate Azure vm.prank(signer1); - bytes32 proposal3 = multisig.proposeDeprecateMeasurements("AzureV1"); + bytes32 proposal3 = multisig.proposeDeprecateMeasurements( + testMeasurement1 + ); // Signer2 votes vm.prank(signer2); multisig.vote(proposal3); multisig.executeProposal(proposal3); - assertFalse(upgradeOperator.isAccepted("AzureV1")); - assertTrue(upgradeOperator.isDeprecated("AzureV1")); + assertFalse(upgradeOperator.isAccepted(measurement1Hash)); + assertTrue(upgradeOperator.isDeprecated(measurement1Hash)); assertEq(upgradeOperator.getAcceptedCount(), 1); // Signer3 proposes to reinstate Azure vm.prank(signer3); - bytes32 proposal4 = multisig.proposeReinstateMeasurements("AzureV1"); + bytes32 proposal4 = multisig.proposeReinstateMeasurements( + testMeasurement1 + ); // Signer1 votes vm.prank(signer1); multisig.vote(proposal4); multisig.executeProposal(proposal4); - assertTrue(upgradeOperator.isAccepted("AzureV1")); - assertFalse(upgradeOperator.isDeprecated("AzureV1")); + assertTrue(upgradeOperator.isAccepted(measurement1Hash)); + assertFalse(upgradeOperator.isDeprecated(measurement1Hash)); assertEq(upgradeOperator.getAcceptedCount(), 2); } @@ -367,7 +378,9 @@ contract MultisigUpgradeOperatorTest is Test { // Test cannot get measurements for non-add proposals function testCannotGetMeasurementsForDeprecateProposal() public { vm.prank(signer1); - bytes32 proposalId = multisig.proposeDeprecateMeasurements("AzureV1"); + bytes32 proposalId = multisig.proposeDeprecateMeasurements( + testMeasurement1 + ); vm.expectRevert("Not an add measurements proposal"); multisig.getProposalMeasurements(proposalId); From 46e918348d5234f351f1a3cb137d548a616c16a3 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 13:35:42 -0400 Subject: [PATCH 05/21] Update rust contract interface --- .../src/contract_interface.rs | 195 ++++----------- crates/enclave-contract/src/lib.rs | 1 + crates/enclave-contract/test/multisig_test.rs | 227 ------------------ crates/enclave-server/src/server/boot.rs | 14 +- scripts/run_integration_tests.sh | 23 +- 5 files changed, 63 insertions(+), 397 deletions(-) delete mode 100644 crates/enclave-contract/test/multisig_test.rs diff --git a/crates/enclave-contract/src/contract_interface.rs b/crates/enclave-contract/src/contract_interface.rs index b57d3503..1baf0427 100644 --- a/crates/enclave-contract/src/contract_interface.rs +++ b/crates/enclave-contract/src/contract_interface.rs @@ -1,83 +1,52 @@ //! Contract interface definitions and types use alloy::{ - network::EthereumWallet, - primitives::{bytes, Bytes, U256}, - providers::ProviderBuilder, - signers::local::PrivateKeySigner, - sol, + network::EthereumWallet, providers::ProviderBuilder, signers::local::PrivateKeySigner, sol, }; -// Generate contract bindings for the factory -sol! { - #[sol(rpc)] - interface UpgradeOperatorFactory { - function deployUpgradeOperator(bytes32 salt) external returns (address); - function deployUpgradeOperatorWithOwner(bytes32 salt, address owner) external returns (address); - function deployMultisigUpgradeOperator(bytes32 salt, address upgradeOperator) external returns (address); - function deployUpgradeOperatorWithMultisig(bytes32 upgradeOperatorSalt, bytes32 multisigSalt) external returns (address, address); - function computeUpgradeOperatorAddress(bytes32 salt) external view returns (address); - function computeUpgradeOperatorAddressWithOwner(bytes32 salt, address owner) external view returns (address); - function computeMultisigUpgradeOperatorAddress(bytes32 salt, address upgradeOperator) external view returns (address); - function isDeployed(address contractAddress) external view returns (bool); - } -} +use crate::Measurements; // Generate contract bindings for the upgrade operator sol! { #[sol(rpc)] interface UpgradeOperator { - function set_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external; - function get_id_status_v1(bytes mrtd, bytes mrseam, bytes pcr4) external view returns (bool); - function computeIdV1(bytes mrtd, bytes mrseam, bytes pcr4) external pure returns (bytes32); + struct Measurements { + string tag; + bytes mrtd; + bytes mrseam; + uint8[] registrar_slots; + bytes[] registrar_values; + } + function acceptedMeasurments(bytes32) public returns(Measurements); + function deprecatedMeasurments(bytes32) public returns(Measurements); + function acceptedTags() public returns(bytes32[]); + function deprecatedTags() public returns(bytes32[]); + + function addAcceptedMeasurements(Measurements measurements) external; + function reinstateMeasurement(Measurements measurements) external; + function deprecateMeasurements(Measurements measurements) external; + function isAccepted(bytes32 measurementHash) external view returns(bool); + function isDeprecated(bytes32 measurementHash) external view returns(bool); + function getAcceptedMeasurement(bytes32 measurementHash) external view returns(Measurements); + function getAcceptedCount() external view returns (uint256); function owner() external view returns (address); + function getMeasurementHash(Measurements measurements) external pure returns(bytes32); } -} - // Generate contract bindings for the multisig contract -sol! { #[sol(rpc)] interface MultisigUpgradeOperator { - function createProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status) external returns (bytes32); - function vote(bytes32 proposalId, bool approved) external; - function executeProposalV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external; - function getVoteCount(bytes32 proposalId) external view returns (uint256 approvalCount, uint256 totalVotes); - function canExecute(bytes32 proposalId) external view returns (bool); - function computeProposalIdV1(bytes mrtd, bytes mrseam, bytes pcr4, bool status, uint256 nonce) external view returns (bytes32); + + function proposeAddMeasurements(UpgradeOperator.Measurements measurements) external returns(bytes32 proposalId); + function proposeDeprecateMeasurements(UpgradeOperator.Measurements measurements) external returns(bytes32 proposalId); + function proposeReinstateMeasurements(UpgradeOperator.Measurements measurements) external returns(bytes32 proposalId); + function vote(bytes32 proposalId) external; + function executeProposal(bytes32 proposalId) external; + function getVoteStatus(bytes32 proposalId) external view returns (uint256 voteCount, bool hasVoted1, bool hasVoted2, bool hasVoted3, bool canExecute); function proposalNonce() external view returns (uint256); function signer1() external view returns (address); function signer2() external view returns (address); function signer3() external view returns (address); function upgradeOperator() external view returns (address); - function setUpgradeOperator(address _upgradeOperator) external; - function factory() external view returns (address); - } -} - -/// Represents the proposal parameters for upgrade validation -/// This struct makes it easy to change the parameters in the future -/// To change parameters, just modify this struct and update the contract interfaces -#[derive(Debug, Clone)] -pub struct ProposalParamsV1 { - pub mrtd: Bytes, // 48 bytes - pub mrseam: Bytes, // 48 bytes - pub pcr4: Bytes, // 32 bytes -} - -impl ProposalParamsV1 { - /// Creates a new ProposalParams instance - pub fn new(mrtd: Bytes, mrseam: Bytes, pcr4: Bytes) -> Self { - Self { mrtd, mrseam, pcr4 } - } - - /// Creates test proposal parameters - /// Based off the devbox values - pub fn test_params() -> Self { - Self { - mrtd: bytes!("cbd40696f617d42254fc7037469cbcf1414fe173678798cfa1070b7d40e26fa8175b99d0cd245994278f980dec73146a"), - mrseam: bytes!("9790d89a10210ec6968a773cee2ca05b5aa97309f36727a968527be4606fc19e6f73acce350946c9d46a9bf7a63f8430"), - pcr4: bytes!("6f2f7d9a42b35a2f8f9d7bf366ca3e369a45d004f3ac49b0a93785fe817c82b5"), - } } } @@ -98,8 +67,7 @@ pub async fn create_multisig_proposal( multisig_address: alloy::primitives::Address, sk: &str, rpc: &str, - params: &ProposalParamsV1, - status: bool, + params: Measurements, ) -> Result<([u8; 32], u64), anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); @@ -119,12 +87,7 @@ pub async fn create_multisig_proposal( .map_err(|e| anyhow::anyhow!("Failed to get current nonce: {:?}", e))?; // Create proposal - let create_tx = multisig_contract.createProposalV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - ); + let create_tx = multisig_contract.proposeAddMeasurements(params); let create_pending = create_tx.send().await.map_err(|e| { anyhow::anyhow!( "create_multisig_proposal create proposal tx failed: {:?}", @@ -132,7 +95,7 @@ pub async fn create_multisig_proposal( ) })?; - let _create_receipt = create_pending + let proposal_id = create_pending .watch() .await .map_err(|e| anyhow::anyhow!("Failed to get proposal creation receipt: {:?}", e))?; @@ -144,19 +107,6 @@ pub async fn create_multisig_proposal( .await .map_err(|e| anyhow::anyhow!("Failed to get new nonce: {:?}", e))?; - // Compute the proposal ID using the new nonce - let proposal_id = multisig_contract - .computeProposalIdV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - new_nonce, - ) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to compute proposal ID: {:?}", e))?; - println!( "Proposal created with ID: {:?}, nonce: {}", proposal_id, new_nonce @@ -183,7 +133,6 @@ pub async fn vote_on_multisig_proposal( sk: &str, rpc: &str, proposal_id: [u8; 32], - approved: bool, ) -> Result<(), anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); @@ -196,7 +145,7 @@ pub async fn vote_on_multisig_proposal( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Vote on proposal - let vote_tx = multisig_contract.vote(proposal_id.into(), approved); + let vote_tx = multisig_contract.vote(proposal_id.into()); let vote_pending = vote_tx .send() .await @@ -207,7 +156,7 @@ pub async fn vote_on_multisig_proposal( .await .map_err(|e| anyhow::anyhow!("Failed to get vote receipt: {:?}", e))?; - println!("Voted {} on proposal: {:?}", approved, proposal_id); + println!("Voted on proposal:{:?}", proposal_id); Ok(()) } @@ -230,9 +179,7 @@ pub async fn execute_multisig_proposal( multisig_address: alloy::primitives::Address, sk: &str, rpc: &str, - params: &ProposalParamsV1, - status: bool, - nonce: u64, + params: [u8; 32], ) -> Result<(), anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); @@ -245,13 +192,7 @@ pub async fn execute_multisig_proposal( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Execute proposal - let execute_tx = multisig_contract.executeProposalV1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - status, - U256::from(nonce), - ); + let execute_tx = multisig_contract.executeProposal(params.into()); let execute_pending = execute_tx .send() .await @@ -291,13 +232,13 @@ pub async fn can_execute_multisig_proposal( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Check if proposal can be executed - let can_execute = multisig_contract - .canExecute(proposal_id.into()) + let res = multisig_contract + .getVoteStatus(proposal_id.into()) .call() .await .map_err(|e| anyhow::anyhow!("Failed to check if proposal can be executed: {:?}", e))?; - Ok(can_execute) + Ok(res.canExecute) } /// Gets the vote count for a proposal in the MultisigUpgradeOperator contract. @@ -315,7 +256,7 @@ pub async fn get_multisig_vote_count( multisig_address: alloy::primitives::Address, rpc: &str, proposal_id: [u8; 32], -) -> Result<(u64, u64), anyhow::Error> { +) -> Result { let rpc_url = reqwest::Url::parse(rpc).unwrap(); let provider = ProviderBuilder::new().connect_http(rpc_url); @@ -324,16 +265,13 @@ pub async fn get_multisig_vote_count( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Get vote count - let result = multisig_contract - .getVoteCount(proposal_id.into()) + let res = multisig_contract + .getVoteStatus(proposal_id.into()) .call() .await .map_err(|e| anyhow::anyhow!("Failed to get vote count: {:?}", e))?; - Ok(( - result.approvalCount.try_into().unwrap(), - result.totalVotes.try_into().unwrap(), - )) + Ok(res.voteCount.try_into().unwrap()) } /// Checks if a proposal configuration is approved in the UpgradeOperator contract. @@ -347,10 +285,10 @@ pub async fn get_multisig_vote_count( /// # Returns /// /// * `Result` - Returns true if the proposal is approved, or an `anyhow::Error` if an error occurs. -pub async fn check_proposal_status_v1( +pub async fn check_proposal_status( upgrade_operator_address: alloy::primitives::Address, rpc: &str, - params: &ProposalParamsV1, + params: Measurements, ) -> Result { let rpc_url = reqwest::Url::parse(rpc).unwrap(); let provider = ProviderBuilder::new().connect_http(rpc_url); @@ -359,47 +297,18 @@ pub async fn check_proposal_status_v1( let upgrade_operator_contract = UpgradeOperator::new(upgrade_operator_address, std::sync::Arc::new(provider)); + let measurement_hash = upgrade_operator_contract + .getMeasurementHash(params) + .call() + .await + .map_err(|e| anyhow::anyhow!("get_measurement_hash failed: {:?}", e))?; + // Check proposal status let status = upgrade_operator_contract - .get_id_status_v1( - params.mrtd.clone(), - params.mrseam.clone(), - params.pcr4.clone(), - ) + .isAccepted(measurement_hash) .call() .await .map_err(|e| anyhow::anyhow!("check_proposal_status_v1 failed: {:?}", e))?; Ok(status) } - -/// Computes the CREATE2 address for a contract without deploying it. -/// -/// # Arguments -/// -/// * `factory_address` - The address of the factory contract. -/// * `rpc` - A string slice representing the RPC URL of the Ethereum node. -/// * `salt` - A 32-byte salt value for CREATE2 deployment. -/// -/// # Returns -/// -/// * `Result` - Returns the computed CREATE2 address if successful, or an `anyhow::Error` if an error occurs. -pub async fn compute_create2_address( - factory_address: alloy::primitives::Address, - rpc: &str, - salt: [u8; 32], -) -> Result { - let rpc_url = reqwest::Url::parse(rpc).unwrap(); - let provider = ProviderBuilder::new().connect_http(rpc_url); - - let factory_contract = - UpgradeOperatorFactory::new(factory_address, std::sync::Arc::new(provider)); - - let expected_address = factory_contract - .computeUpgradeOperatorAddress(salt.into()) - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to compute CREATE2 address: {:?}", e))?; - - Ok(expected_address) -} diff --git a/crates/enclave-contract/src/lib.rs b/crates/enclave-contract/src/lib.rs index 49fcb1cb..456295a1 100644 --- a/crates/enclave-contract/src/lib.rs +++ b/crates/enclave-contract/src/lib.rs @@ -7,6 +7,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] pub mod contract_interface; +pub use contract_interface::UpgradeOperator::Measurements; pub use contract_interface::*; /// Anvil's first secret key that they publically expose and fund for testing diff --git a/crates/enclave-contract/test/multisig_test.rs b/crates/enclave-contract/test/multisig_test.rs deleted file mode 100644 index b0b4d1fb..00000000 --- a/crates/enclave-contract/test/multisig_test.rs +++ /dev/null @@ -1,227 +0,0 @@ -use enclave_contract::UPGRADE_MULTISIG_ADDRESS; -use enclave_contract::UPGRADE_OPERATOR_ADDRESS; -use enclave_contract::{ - can_execute_multisig_proposal, check_proposal_status_v1, create_multisig_proposal, - execute_multisig_proposal, get_multisig_vote_count, vote_on_multisig_proposal, - ProposalParamsV1, ANVIL_ALICE_SK, ANVIL_BOB_SK, -}; -use std::thread::sleep; -use std::time::Duration; - -/// Prints a string to standard output and immediately flushes the output buffer. -/// Useful to see prints immediately during long-running Cargo tests. -pub fn print_flush>(s: S) { - use std::io::Write; - let stdout = std::io::stdout(); - let mut handle = stdout.lock(); // lock ensures safe writing - write!(handle, "{}", s.as_ref()).unwrap(); - handle.flush().unwrap(); -} - -/// Test the complete multisig workflow for controlling UpgradeOperator -/// This test verifies that the multisig contract can properly control the UpgradeOperator -/// through a 2-of-3 voting mechanism -#[tokio::test(flavor = "multi_thread")] -pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Error> { - // Set path to the factory contract's json file - let reth_rpc = "http://localhost:8545"; - let multisig_address = UPGRADE_MULTISIG_ADDRESS - .parse::() - .unwrap(); - let upgrade_operator_address = UPGRADE_OPERATOR_ADDRESS - .parse::() - .unwrap(); - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - // Test data for proposal - let params = ProposalParamsV1::test_params(); - let status = true; - - print_flush("Creating multisig proposal...\n"); - - // Create a proposal to set MRTD - let (proposal_id, nonce) = - create_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, ¶ms, status) - .await - .map_err(|e| anyhow::anyhow!("multisig workflow failed to create proposal: {:?}", e))?; - - print_flush(format!( - "Proposal created with ID: {:?}, nonce: {}\n", - proposal_id, nonce - )); - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - // Check initial vote count - let (approval_count, total_votes) = - get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; - - print_flush(format!( - "Initial vote count - Approvals: {}, Total votes: {}\n", - approval_count, total_votes - )); - assert_eq!(approval_count, 0, "Initial approval count should be 0"); - assert_eq!(total_votes, 0, "Initial total votes should be 0"); - - // Check if proposal can be executed (should be false initially) - let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; - - print_flush(format!("Can execute proposal: {}\n", can_execute)); - assert!(!can_execute, "Proposal should not be executable initially"); - - print_flush("Alice voting yes on proposal...\n"); - - // Alice votes yes - vote_on_multisig_proposal( - multisig_address, - ANVIL_ALICE_SK, - reth_rpc, - proposal_id, - true, - ) - .await - .map_err(|e| anyhow::anyhow!("failed to vote with Alice: {:?}", e))?; - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - // Check vote count after Alice's vote - let (approval_count, total_votes) = - get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; - - print_flush(format!( - "Vote count after Alice - Approvals: {}, Total votes: {}\n", - approval_count, total_votes - )); - assert_eq!( - approval_count, 1, - "Approval count should be 1 after Alice's vote" - ); - assert_eq!(total_votes, 1, "Total votes should be 1 after Alice's vote"); - - // Check if proposal can be executed (should still be false with only 1 vote) - let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; - - print_flush(format!( - "Can execute proposal after Alice: {}\n", - can_execute - )); - assert!( - !can_execute, - "Proposal should not be executable with only 1 vote" - ); - - print_flush("Bob voting yes on proposal...\n"); - - // Bob votes yes - vote_on_multisig_proposal(multisig_address, ANVIL_BOB_SK, reth_rpc, proposal_id, true) - .await - .map_err(|e| anyhow::anyhow!("failed to vote with Bob: {:?}", e))?; - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - // Check vote count after Bob's vote - let (approval_count, total_votes) = - get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; - - print_flush(format!( - "Vote count after Bob - Approvals: {}, Total votes: {}\n", - approval_count, total_votes - )); - assert_eq!( - approval_count, 2, - "Approval count should be 2 after Bob's vote" - ); - assert_eq!(total_votes, 2, "Total votes should be 2 after Bob's vote"); - - // Check if proposal can be executed (should be true with 2 votes) - let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; - - print_flush(format!("Can execute proposal after Bob: {}\n", can_execute)); - assert!(can_execute, "Proposal should be executable with 2 votes"); - - // Check initial proposal status (should be false) - let initial_proposal_status = - check_proposal_status_v1(upgrade_operator_address, reth_rpc, ¶ms) - .await - .map_err(|e| anyhow::anyhow!("failed to check initial proposal status: {:?}", e))?; - - print_flush(format!( - "Initial proposal status: {}\n", - initial_proposal_status - )); - assert!( - !initial_proposal_status, - "Initial proposal status should be false" - ); - - print_flush("Executing proposal...\n"); - - // Execute the proposal - execute_multisig_proposal( - multisig_address, - ANVIL_ALICE_SK, - reth_rpc, - ¶ms, - status, - nonce, - ) - .await - .map_err(|e| anyhow::anyhow!("failed to execute proposal: {:?}", e))?; - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - print_flush("Checking final proposal status...\n"); - - // Check final proposal status (should be true) - let final_proposal_status = - check_proposal_status_v1(upgrade_operator_address, reth_rpc, ¶ms) - .await - .map_err(|e| anyhow::anyhow!("failed to check final proposal status: {:?}", e))?; - - print_flush(format!( - "Final proposal status: {}\n", - final_proposal_status - )); - assert!( - final_proposal_status, - "Final proposal status should be true" - ); - - // Test that the proposal cannot be executed again - let can_execute_again = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| { - anyhow::anyhow!("failed to check if proposal can be executed again: {:?}", e) - })?; - - print_flush(format!( - "Can execute proposal again: {}\n", - can_execute_again - )); - assert!( - !can_execute_again, - "Proposal should not be executable again after execution" - ); - - print_flush("Test completed successfully!\n"); - - Ok(()) -} diff --git a/crates/enclave-server/src/server/boot.rs b/crates/enclave-server/src/server/boot.rs index 32245d29..26f43235 100644 --- a/crates/enclave-server/src/server/boot.rs +++ b/crates/enclave-server/src/server/boot.rs @@ -234,11 +234,13 @@ impl Booter { } // Create ProposalParamsV1 struct - let params = enclave_contract::ProposalParamsV1::new( - alloy::primitives::Bytes::from(mr_td_bytes), - alloy::primitives::Bytes::from(mr_seam_bytes), - alloy::primitives::Bytes::from(pcr4_bytes), - ); + let params = enclave_contract::Measurements { + tag: "AzureV1", + mrtd: alloy::primitives::Bytes::from(mr_td_bytes), + mrseam: alloy::primitives::Bytes::from(mr_seam_bytes), + registrar_slots: vec![4], + registrar_values: vec![alloy::primitives::Bytes::from(pcr4_bytes)], + }; // Get contract address and RPC URL from environment variables let upgrade_operator_address = enclave_contract::UPGRADE_OPERATOR_ADDRESS @@ -248,7 +250,7 @@ impl Booter { // Check the proposal status against the onchain contract let status = - enclave_contract::check_proposal_status_v1(upgrade_operator_address, &rpc_url, ¶ms) + enclave_contract::check_proposal_status(upgrade_operator_address, &rpc_url, ¶ms) .await .map_err(|e| anyhow::anyhow!("Booter failed to check proposal status: {e}"))?; diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 1c21fa5f..5dffbcab 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -44,26 +44,7 @@ if ! sudo supervisorctl status reth | grep -q "RUNNING"; then fi echo "โœ… reth service is running" -# Test 1: Run multisig upgrade operator workflow test -echo "๐Ÿงช Running test_multisig_upgrade_operator_workflow..." -cd crates/enclave-contract -# Build tests and get binary paths -OUTPUT=$(cargo test --no-run 2>&1) -echo "$OUTPUT" -mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*multisig_test-[a-z0-9]*') -if [ ${#binaries[@]} -eq 0 ]; then - echo "โŒ Could not find multisig_test binaries" - exit 1 -fi -echo "Found binaries: ${binaries[*]}" -# Run the first binary with the specific test -if ! "${binaries[0]}" test_multisig_upgrade_operator_workflow; then - echo "โŒ test_multisig_upgrade_operator_workflow failed" - exit 1 -fi -echo "โœ… test_multisig_upgrade_operator_workflow passed" - -# Test 2: Run boot share root key test +# Test 1: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." cd ../enclave-server ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" @@ -88,7 +69,7 @@ if ! sudo "${binaries[0]}" test_boot_share_root_key; then fi echo "โœ… test_boot_share_root_key passed" -# Test 3: Run snapshot integration handlers test +# Test 2: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 From 83eea8d466368b7082175177c33eeac2a5cd3212 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 13:40:18 -0400 Subject: [PATCH 06/21] clippy --- crates/enclave-server/src/server/boot.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/enclave-server/src/server/boot.rs b/crates/enclave-server/src/server/boot.rs index 26f43235..1c736a6f 100644 --- a/crates/enclave-server/src/server/boot.rs +++ b/crates/enclave-server/src/server/boot.rs @@ -235,7 +235,7 @@ impl Booter { // Create ProposalParamsV1 struct let params = enclave_contract::Measurements { - tag: "AzureV1", + tag: "AzureV1".to_string(), mrtd: alloy::primitives::Bytes::from(mr_td_bytes), mrseam: alloy::primitives::Bytes::from(mr_seam_bytes), registrar_slots: vec![4], @@ -250,7 +250,7 @@ impl Booter { // Check the proposal status against the onchain contract let status = - enclave_contract::check_proposal_status(upgrade_operator_address, &rpc_url, ¶ms) + enclave_contract::check_proposal_status(upgrade_operator_address, &rpc_url, params) .await .map_err(|e| anyhow::anyhow!("Booter failed to check proposal status: {e}"))?; From b2ad7f0b5b379cd1c1f0dbd3490d33d5443c0763 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 14:54:58 -0400 Subject: [PATCH 07/21] spawn enclave before reth, then tear it down --- scripts/run_integration_tests.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 5dffbcab..1c1e89ea 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -26,9 +26,8 @@ cleanup() { # # Set up trap to cleanup on exit trap cleanup EXIT - -# Make sure enclave is NOT running so we can access TPM in test -sudo supervisorctl stop seismic-enclave-server || true +# Make sure enclave is running so we can start reth +sudo supervisorctl start enclave-server || true sleep 2 # Start services via supervisor @@ -36,6 +35,10 @@ echo "๐Ÿ”ง Starting supervisor services..." sudo supervisorctl start reth || true sleep 10 +# Make sure enclave is NOT running so we can access TPM in test +sudo supervisorctl stop enclave-server || true +sleep 2 + # Check if reth is running echo "๐Ÿ” Checking if reth is running..." if ! sudo supervisorctl status reth | grep -q "RUNNING"; then From 83cbb6725b383bcf5ae02925a131c418dd5ec88b Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 15:17:03 -0400 Subject: [PATCH 08/21] explicitly call target dir --- scripts/run_integration_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 1c1e89ea..b36e9e00 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -64,7 +64,7 @@ echo "Found binaries: ${binaries[*]}" # Run the first binary with the specific test sleep 2 echo "๐Ÿš€ Executing: sudo ${binaries[0]} test_boot_share_root_key" -if ! sudo "${binaries[0]}" test_boot_share_root_key; then +if ! sudo "target/${binaries[0]}" test_boot_share_root_key; then echo "โŒ test_boot_share_root_key failed with exit code $?" echo "๐Ÿ” Last 20 lines of system log:" sudo journalctl -n 20 --no-pager 2>/dev/null || echo "Could not access journalctl" From 18211754a7f8889ca5532bee74f04a9e396c2031 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 15:17:30 -0400 Subject: [PATCH 09/21] explicitly call target dir --- scripts/run_integration_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index b36e9e00..0381fd81 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -76,7 +76,7 @@ echo "โœ… test_boot_share_root_key passed" echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 -if ! sudo "${binaries[0]}" test_snapshot_integration_handlers; then +if ! sudo "target/${binaries[0]}" test_snapshot_integration_handlers; then echo "โŒ test_snapshot_integration_handlers failed" exit 1 fi From 62e9d070e347cc95f1df3f20dfb32b915c327106 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 16:09:39 -0400 Subject: [PATCH 10/21] change path --- crates/enclave-server/src/utils/test_utils.rs | 2 +- scripts/run_integration_tests.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/enclave-server/src/utils/test_utils.rs b/crates/enclave-server/src/utils/test_utils.rs index b7605a50..9bd2289b 100644 --- a/crates/enclave-server/src/utils/test_utils.rs +++ b/crates/enclave-server/src/utils/test_utils.rs @@ -71,7 +71,7 @@ pub fn get_random_port() -> u16 { /// attests to a public secp256k1 key from an AzTdxVtpm machine pub fn pub_key_eval_request() -> AttestationEvalEvidenceRequest { use seismic_enclave::request_types::Data; - let evidence_bytes = read_vector_txt("../../examples/az_tdx_key_att.txt".to_string()).unwrap(); + let evidence_bytes = read_vector_txt("../examples/az_tdx_key_att.txt".to_string()).unwrap(); let evidence_str = String::from_utf8(evidence_bytes).unwrap(); let evidence = serde_json::from_str(&evidence_str).unwrap(); let req = AttestationEvalEvidenceRequest { diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 0381fd81..28342af6 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -63,7 +63,7 @@ fi echo "Found binaries: ${binaries[*]}" # Run the first binary with the specific test sleep 2 -echo "๐Ÿš€ Executing: sudo ${binaries[0]} test_boot_share_root_key" +echo "๐Ÿš€ Executing: sudo target/${binaries[0]} test_boot_share_root_key" if ! sudo "target/${binaries[0]}" test_boot_share_root_key; then echo "โŒ test_boot_share_root_key failed with exit code $?" echo "๐Ÿ” Last 20 lines of system log:" @@ -74,7 +74,7 @@ echo "โœ… test_boot_share_root_key passed" # Test 2: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." -sudo supervisorctl start enclave-server +sudo supervisorctl start enclave-server sleep 2 if ! sudo "target/${binaries[0]}" test_snapshot_integration_handlers; then echo "โŒ test_snapshot_integration_handlers failed" From 6def8a09efd499273881ab137f7741207d65ff46 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 16:33:43 -0400 Subject: [PATCH 11/21] shouldnt work either --- crates/enclave-server/src/utils/test_utils.rs | 2 +- scripts/run_integration_tests.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/enclave-server/src/utils/test_utils.rs b/crates/enclave-server/src/utils/test_utils.rs index 9bd2289b..b7605a50 100644 --- a/crates/enclave-server/src/utils/test_utils.rs +++ b/crates/enclave-server/src/utils/test_utils.rs @@ -71,7 +71,7 @@ pub fn get_random_port() -> u16 { /// attests to a public secp256k1 key from an AzTdxVtpm machine pub fn pub_key_eval_request() -> AttestationEvalEvidenceRequest { use seismic_enclave::request_types::Data; - let evidence_bytes = read_vector_txt("../examples/az_tdx_key_att.txt".to_string()).unwrap(); + let evidence_bytes = read_vector_txt("../../examples/az_tdx_key_att.txt".to_string()).unwrap(); let evidence_str = String::from_utf8(evidence_bytes).unwrap(); let evidence = serde_json::from_str(&evidence_str).unwrap(); let req = AttestationEvalEvidenceRequest { diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 28342af6..d5edfb89 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -55,7 +55,7 @@ ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" # Build tests and get binary paths OUTPUT=$(cargo test --no-run 2>&1) echo "$OUTPUT" -mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*integration-[a-z0-9]*') +mapfile -t binaries < <(echo "$OUTPUT" | grep -o 'target/[^ ]*integration-[a-z0-9]*') if [ ${#binaries[@]} -eq 0 ]; then echo "โŒ Could not find enclave-server integration test binaries" exit 1 @@ -63,8 +63,8 @@ fi echo "Found binaries: ${binaries[*]}" # Run the first binary with the specific test sleep 2 -echo "๐Ÿš€ Executing: sudo target/${binaries[0]} test_boot_share_root_key" -if ! sudo "target/${binaries[0]}" test_boot_share_root_key; then +echo "๐Ÿš€ Executing: sudo ${binaries[0]} test_boot_share_root_key" +if ! sudo "${binaries[0]}" test_boot_share_root_key; then echo "โŒ test_boot_share_root_key failed with exit code $?" echo "๐Ÿ” Last 20 lines of system log:" sudo journalctl -n 20 --no-pager 2>/dev/null || echo "Could not access journalctl" @@ -76,7 +76,7 @@ echo "โœ… test_boot_share_root_key passed" echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 -if ! sudo "target/${binaries[0]}" test_snapshot_integration_handlers; then +if ! sudo "${binaries[0]}" test_snapshot_integration_handlers; then echo "โŒ test_snapshot_integration_handlers failed" exit 1 fi From bd951a2917eed4313ba78f1ece9100e06996e6fb Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 17:05:45 -0400 Subject: [PATCH 12/21] try mking t his pass --- scripts/run_integration_tests.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index d5edfb89..4b401e39 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -49,13 +49,15 @@ echo "โœ… reth service is running" # Test 1: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." +echo "PWD before cd ../enclave-server = $PWD" cd ../enclave-server +echo "PWD after cd ../enclave-server = $PWD" ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" # Build tests and get binary paths OUTPUT=$(cargo test --no-run 2>&1) echo "$OUTPUT" -mapfile -t binaries < <(echo "$OUTPUT" | grep -o 'target/[^ ]*integration-[a-z0-9]*') +mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*integration-[a-z0-9]*') if [ ${#binaries[@]} -eq 0 ]; then echo "โŒ Could not find enclave-server integration test binaries" exit 1 @@ -63,6 +65,8 @@ fi echo "Found binaries: ${binaries[*]}" # Run the first binary with the specific test sleep 2 + +echo "working directory = $PWD" echo "๐Ÿš€ Executing: sudo ${binaries[0]} test_boot_share_root_key" if ! sudo "${binaries[0]}" test_boot_share_root_key; then echo "โŒ test_boot_share_root_key failed with exit code $?" From 75d5dca45ea2ffaccfee5e7be116121b137c21c7 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 17:14:28 -0400 Subject: [PATCH 13/21] a ha -- should do it --- scripts/run_integration_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 4b401e39..7bf4838f 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -50,7 +50,7 @@ echo "โœ… reth service is running" # Test 1: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." echo "PWD before cd ../enclave-server = $PWD" -cd ../enclave-server +cd crates/enclave-server echo "PWD after cd ../enclave-server = $PWD" ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" From 695c9d82f2a3a44407c708a1905383605189e750 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Tue, 21 Oct 2025 17:29:37 -0400 Subject: [PATCH 14/21] clean up --- scripts/run_integration_tests.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 7bf4838f..3a44dfd6 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -49,9 +49,7 @@ echo "โœ… reth service is running" # Test 1: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." -echo "PWD before cd ../enclave-server = $PWD" cd crates/enclave-server -echo "PWD after cd ../enclave-server = $PWD" ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" # Build tests and get binary paths @@ -66,7 +64,6 @@ echo "Found binaries: ${binaries[*]}" # Run the first binary with the specific test sleep 2 -echo "working directory = $PWD" echo "๐Ÿš€ Executing: sudo ${binaries[0]} test_boot_share_root_key" if ! sudo "${binaries[0]}" test_boot_share_root_key; then echo "โŒ test_boot_share_root_key failed with exit code $?" From 185f50d5304480d1865e8b67b1f82c0556179b1c Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 19:18:22 -0400 Subject: [PATCH 15/21] bring back old test --- crates/enclave-contract/foundry.toml | 3 +- .../MultisigUpgradeOperator.t.sol | 0 .../enclave-contract/tests/multisig_test.rs | 212 ++++++++++++++++++ .../tests/integration/booter.rs | 1 + scripts/run_integration_tests.sh | 26 ++- 5 files changed, 238 insertions(+), 4 deletions(-) rename crates/enclave-contract/{test => tests}/MultisigUpgradeOperator.t.sol (100%) create mode 100644 crates/enclave-contract/tests/multisig_test.rs diff --git a/crates/enclave-contract/foundry.toml b/crates/enclave-contract/foundry.toml index 36661944..ecb7d231 100644 --- a/crates/enclave-contract/foundry.toml +++ b/crates/enclave-contract/foundry.toml @@ -1,4 +1,5 @@ [profile.default] src = "contracts" out = "out" -libs = ["lib"] \ No newline at end of file +libs = ["lib"] +test = "tests" \ No newline at end of file diff --git a/crates/enclave-contract/test/MultisigUpgradeOperator.t.sol b/crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol similarity index 100% rename from crates/enclave-contract/test/MultisigUpgradeOperator.t.sol rename to crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol diff --git a/crates/enclave-contract/tests/multisig_test.rs b/crates/enclave-contract/tests/multisig_test.rs new file mode 100644 index 00000000..eda8cfd2 --- /dev/null +++ b/crates/enclave-contract/tests/multisig_test.rs @@ -0,0 +1,212 @@ +use enclave_contract::can_execute_multisig_proposal; +use enclave_contract::check_proposal_status; +use enclave_contract::create_multisig_proposal; +use enclave_contract::execute_multisig_proposal; +use enclave_contract::get_multisig_vote_count; +use enclave_contract::vote_on_multisig_proposal; +use enclave_contract::Measurements; +use enclave_contract::UPGRADE_MULTISIG_ADDRESS; +use enclave_contract::UPGRADE_OPERATOR_ADDRESS; + +use alloy::primitives::bytes; +use enclave_contract::{ANVIL_ALICE_SK, ANVIL_BOB_SK}; +use std::thread::sleep; +use std::time::Duration; +/// Prints a string to standard output and immediately flushes the output buffer. +/// Useful to see prints immediately during long-running Cargo tests. +pub fn print_flush>(s: S) { + use std::io::Write; + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); // lock ensures safe writing + write!(handle, "{}", s.as_ref()).unwrap(); + handle.flush().unwrap(); +} + +/// Test the complete multisig workflow for controlling UpgradeOperator +/// This test verifies that the multisig contract can properly control the UpgradeOperator +/// through a 2-of-3 voting mechanism +#[tokio::test(flavor = "multi_thread")] +pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Error> { + // Set path to the factory contract's json file + let reth_rpc = "http://localhost:8545"; + let multisig_address = UPGRADE_MULTISIG_ADDRESS + .parse::() + .unwrap(); + let upgrade_operator_address = UPGRADE_OPERATOR_ADDRESS + .parse::() + .unwrap(); + + // Wait a bit for the transaction to be processed + sleep(Duration::from_secs(2)); + + // Test data for proposal + let params = Measurements { + tag: "AlloyV1".to_string(), + mrtd: bytes!("cbd40696f617d42254fc7037469cbcf1414fe173678798cfa1070b7d40e26fa8175b99d0cd245994278f980dec73146a"), + mrseam: bytes!("9790d89a10210ec6968a773cee2ca05b5aa97309f36727a968527be4606fc19e6f73acce350946c9d46a9bf7a63f8430"), + registrar_slots: vec![4], + registrar_values: vec![bytes!("6f2f7d9a42b35a2f8f9d7bf366ca3e369a45d004f3ac49b0a93785fe817c82b5")], + }; + + print_flush("Creating multisig proposal...\n"); + + // Create a proposal to set MRTD + let (proposal_id, nonce) = + create_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, params.clone()) + .await + .map_err(|e| anyhow::anyhow!("multisig workflow failed to create proposal: {:?}", e))?; + + print_flush(format!( + "Proposal created with ID: {:?}, nonce: {}\n", + proposal_id, nonce + )); + + // Wait a bit for the transaction to be processed + sleep(Duration::from_secs(2)); + + // Check initial vote count + let total_votes = get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; + + print_flush(format!( + "Initial vote count - Total votes: {}\n", + total_votes + )); + assert_eq!(total_votes, 0, "Initial total votes should be 0"); + + // Check if proposal can be executed (should be false initially) + let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; + + print_flush(format!("Can execute proposal: {}\n", can_execute)); + assert!(!can_execute, "Proposal should not be executable initially"); + + print_flush("Alice voting yes on proposal...\n"); + + // Alice votes yes + vote_on_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to vote with Alice: {:?}", e))?; + + // Wait a bit for the transaction to be processed + sleep(Duration::from_secs(2)); + + // Check vote count after Alice's vote + let total_votes = get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; + + print_flush(format!( + "Vote count after Alice - Total votes: {}\n", + total_votes + )); + + assert_eq!(total_votes, 1, "Total votes should be 1 after Alice's vote"); + + // Check if proposal can be executed (should still be false with only 1 vote) + let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; + + print_flush(format!( + "Can execute proposal after Alice: {}\n", + can_execute + )); + assert!( + !can_execute, + "Proposal should not be executable with only 1 vote" + ); + + print_flush("Bob voting yes on proposal...\n"); + + // Bob votes yes + vote_on_multisig_proposal(multisig_address, ANVIL_BOB_SK, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to vote with Bob: {:?}", e))?; + + // Wait a bit for the transaction to be processed + sleep(Duration::from_secs(2)); + + // Check vote count after Bob's vote + let total_votes = get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; + + print_flush(format!( + "Vote count after Bob - Total votes: {}\n", + total_votes + )); + + assert_eq!(total_votes, 2, "Total votes should be 2 after Bob's vote"); + + // Check if proposal can be executed (should be true with 2 votes) + let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; + + print_flush(format!("Can execute proposal after Bob: {}\n", can_execute)); + assert!(can_execute, "Proposal should be executable with 2 votes"); + + // Check initial proposal status (should be false) + let initial_proposal_status = + check_proposal_status(upgrade_operator_address, reth_rpc, params.clone()) + .await + .map_err(|e| anyhow::anyhow!("failed to check initial proposal status: {:?}", e))?; + + print_flush(format!( + "Initial proposal status: {}\n", + initial_proposal_status + )); + assert!( + !initial_proposal_status, + "Initial proposal status should be false" + ); + + print_flush("Executing proposal...\n"); + + // Execute the proposal + execute_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, proposal_id) + .await + .map_err(|e| anyhow::anyhow!("failed to execute proposal: {:?}", e))?; + + // Wait a bit for the transaction to be processed + sleep(Duration::from_secs(2)); + + print_flush("Checking final proposal status...\n"); + + // Check final proposal status (should be true) + let final_proposal_status = check_proposal_status(upgrade_operator_address, reth_rpc, params) + .await + .map_err(|e| anyhow::anyhow!("failed to check final proposal status: {:?}", e))?; + + print_flush(format!( + "Final proposal status: {}\n", + final_proposal_status + )); + assert!( + final_proposal_status, + "Final proposal status should be true" + ); + + // Test that the proposal cannot be executed again + let can_execute_again = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) + .await + .map_err(|e| { + anyhow::anyhow!("failed to check if proposal can be executed again: {:?}", e) + })?; + + print_flush(format!( + "Can execute proposal again: {}\n", + can_execute_again + )); + assert!( + !can_execute_again, + "Proposal should not be executable again after execution" + ); + + print_flush("Test completed successfully!\n"); + + Ok(()) +} diff --git a/crates/enclave-server/tests/integration/booter.rs b/crates/enclave-server/tests/integration/booter.rs index d2e19a0e..7660bc4e 100644 --- a/crates/enclave-server/tests/integration/booter.rs +++ b/crates/enclave-server/tests/integration/booter.rs @@ -25,6 +25,7 @@ async fn test_boot_share_root_key() { } assert!(reth_is_running(), "Test startup error: Reth is not running"); + let addy = UPGRADE_MULTISIG_ADDRESS; // Test the booter with the canonical deployment let enclave_engine: AttestationEngine = engine_mock_booted().await; let new_node_booter = Booter::mock(); diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 3a44dfd6..52d013b1 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -47,9 +47,29 @@ if ! sudo supervisorctl status reth | grep -q "RUNNING"; then fi echo "โœ… reth service is running" -# Test 1: Run boot share root key test + +# Test 1: Run multisig upgrade operator workflow test +echo "๐Ÿงช Running test_multisig_upgrade_operator_workflow..." +cd crates/enclave-contract +# Build tests and get binary paths +OUTPUT=$(cargo test --no-run 2>&1) +echo "$OUTPUT" +mapfile -t binaries < <(echo "$OUTPUT" | grep -o '/[^ ]*multisig_test-[a-z0-9]*') +if [ ${#binaries[@]} -eq 0 ]; then + echo "โŒ Could not find multisig_test binaries" + exit 1 +fi +echo "Found binaries: ${binaries[*]}" +# Run the first binary with the specific test +if ! "${binaries[0]}" test_multisig_upgrade_operator_workflow; then + echo "โŒ test_multisig_upgrade_operator_workflow failed" + exit 1 +fi +echo "โœ… test_multisig_upgrade_operator_workflow passed" + +# Test 2: Run boot share root key test echo "๐Ÿงช Running test_boot_share_root_key..." -cd crates/enclave-server +cd ../enclave-server ls -la Cargo.toml 2>/dev/null || echo "โŒ Cargo.toml not found in $(pwd)" # Build tests and get binary paths @@ -73,7 +93,7 @@ if ! sudo "${binaries[0]}" test_boot_share_root_key; then fi echo "โœ… test_boot_share_root_key passed" -# Test 2: Run snapshot integration handlers test +# Test 3: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server sleep 2 From a095511e6f531157ea55eb7018f07961e812cb8d Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Tue, 21 Oct 2025 19:25:09 -0400 Subject: [PATCH 16/21] cleanup --- crates/enclave-server/tests/integration/booter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/enclave-server/tests/integration/booter.rs b/crates/enclave-server/tests/integration/booter.rs index 7660bc4e..d2e19a0e 100644 --- a/crates/enclave-server/tests/integration/booter.rs +++ b/crates/enclave-server/tests/integration/booter.rs @@ -25,7 +25,6 @@ async fn test_boot_share_root_key() { } assert!(reth_is_running(), "Test startup error: Reth is not running"); - let addy = UPGRADE_MULTISIG_ADDRESS; // Test the booter with the canonical deployment let enclave_engine: AttestationEngine = engine_mock_booted().await; let new_node_booter = Booter::mock(); From b1789c3b540c931e648fc24bc85bec3d99895365 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Wed, 22 Oct 2025 13:00:23 -0400 Subject: [PATCH 17/21] finish fixing up old tests --- .../contracts/MultisigUpgradeOperator.sol | 12 ++-- .../src/contract_interface.rs | 57 ++++++++----------- .../tests/MultisigUpgradeOperator.t.sol | 18 +++--- .../enclave-contract/tests/multisig_test.rs | 45 +-------------- 4 files changed, 39 insertions(+), 93 deletions(-) diff --git a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol index 11956e0d..cd0f36bf 100644 --- a/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol +++ b/crates/enclave-contract/contracts/MultisigUpgradeOperator.sol @@ -18,7 +18,7 @@ contract MultisigUpgradeOperator { 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; // Charlie (0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a) // The UpgradeOperator contract being controlled - UpgradeOperator public immutable upgradeOperator = + UpgradeOperator public constant upgradeOperator = UpgradeOperator(0x1000000000000000000000000000000000000001); // Set in seismic-reth genesis // Nonce counter for proposal uniqueness @@ -212,6 +212,10 @@ contract MultisigUpgradeOperator { function _vote(bytes32 proposalId) internal { Proposal storage proposal = proposals[proposalId]; + require( + bytes(proposal.measurements.tag).length > 0, + "proposal does not exist" + ); require(!proposal.executed, "Proposal already executed"); require(!proposal.hasVoted[msg.sender], "Already voted"); @@ -219,12 +223,6 @@ contract MultisigUpgradeOperator { proposal.voteCount++; emit VoteCast(proposalId, msg.sender); - - // todo we probably dont want auto execute - // Auto-execute if threshold reached - // if (proposal.voteCount >= 2) { - // _executeProposal(proposalId); - // } } /** diff --git a/crates/enclave-contract/src/contract_interface.rs b/crates/enclave-contract/src/contract_interface.rs index 1baf0427..5eccd1bf 100644 --- a/crates/enclave-contract/src/contract_interface.rs +++ b/crates/enclave-contract/src/contract_interface.rs @@ -1,10 +1,14 @@ //! Contract interface definitions and types use alloy::{ - network::EthereumWallet, providers::ProviderBuilder, signers::local::PrivateKeySigner, sol, + network::EthereumWallet, + primitives::{FixedBytes, Log}, + providers::ProviderBuilder, + signers::local::PrivateKeySigner, + sol, }; -use crate::Measurements; +use crate::{Measurements, MultisigUpgradeOperator::ProposalCreated}; // Generate contract bindings for the upgrade operator sol! { @@ -29,12 +33,13 @@ sol! { function isDeprecated(bytes32 measurementHash) external view returns(bool); function getAcceptedMeasurement(bytes32 measurementHash) external view returns(Measurements); function getAcceptedCount() external view returns (uint256); - function owner() external view returns (address); + function OWNER() external view returns (address); function getMeasurementHash(Measurements measurements) external pure returns(bytes32); } // Generate contract bindings for the multisig contract #[sol(rpc)] interface MultisigUpgradeOperator { + event ProposalCreated(bytes32 indexed proposalId,uint8 indexed proposalType,string tag,uint256 nonce); function proposeAddMeasurements(UpgradeOperator.Measurements measurements) external returns(bytes32 proposalId); function proposeDeprecateMeasurements(UpgradeOperator.Measurements measurements) external returns(bytes32 proposalId); @@ -68,10 +73,10 @@ pub async fn create_multisig_proposal( sk: &str, rpc: &str, params: Measurements, -) -> Result<([u8; 32], u64), anyhow::Error> { +) -> Result, anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); - let wallet = EthereumWallet::from(signer); + let wallet = EthereumWallet::from(signer.clone()); let rpc_url = reqwest::Url::parse(rpc).unwrap(); let provider = ProviderBuilder::new().wallet(wallet).connect_http(rpc_url); @@ -79,13 +84,6 @@ pub async fn create_multisig_proposal( let multisig_contract = MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider.clone())); - // Get current nonce before creating proposal (for debugging/logging if needed) - let _current_nonce = multisig_contract - .proposalNonce() - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to get current nonce: {:?}", e))?; - // Create proposal let create_tx = multisig_contract.proposeAddMeasurements(params); let create_pending = create_tx.send().await.map_err(|e| { @@ -95,24 +93,17 @@ pub async fn create_multisig_proposal( ) })?; - let proposal_id = create_pending - .watch() + // wait for it to be included + let receipt = create_pending + .get_receipt() .await .map_err(|e| anyhow::anyhow!("Failed to get proposal creation receipt: {:?}", e))?; - // Get the new nonce after proposal creation - let new_nonce = multisig_contract - .proposalNonce() - .call() - .await - .map_err(|e| anyhow::anyhow!("Failed to get new nonce: {:?}", e))?; + let event: Log = receipt.decoded_log().unwrap(); - println!( - "Proposal created with ID: {:?}, nonce: {}", - proposal_id, new_nonce - ); + let proposal_id = event.proposalId; - Ok((proposal_id.into(), new_nonce.try_into().unwrap())) + Ok(proposal_id) } /// Votes on a proposal in the MultisigUpgradeOperator contract. @@ -132,7 +123,7 @@ pub async fn vote_on_multisig_proposal( multisig_address: alloy::primitives::Address, sk: &str, rpc: &str, - proposal_id: [u8; 32], + proposal_id: FixedBytes<32>, ) -> Result<(), anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); @@ -145,7 +136,7 @@ pub async fn vote_on_multisig_proposal( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Vote on proposal - let vote_tx = multisig_contract.vote(proposal_id.into()); + let vote_tx = multisig_contract.vote(proposal_id); let vote_pending = vote_tx .send() .await @@ -179,7 +170,7 @@ pub async fn execute_multisig_proposal( multisig_address: alloy::primitives::Address, sk: &str, rpc: &str, - params: [u8; 32], + params: FixedBytes<32>, ) -> Result<(), anyhow::Error> { // Set up signer with the provided sk let signer: PrivateKeySigner = sk.parse().unwrap(); @@ -192,7 +183,7 @@ pub async fn execute_multisig_proposal( MultisigUpgradeOperator::new(multisig_address, std::sync::Arc::new(provider)); // Execute proposal - let execute_tx = multisig_contract.executeProposal(params.into()); + let execute_tx = multisig_contract.executeProposal(params); let execute_pending = execute_tx .send() .await @@ -222,7 +213,7 @@ pub async fn execute_multisig_proposal( pub async fn can_execute_multisig_proposal( multisig_address: alloy::primitives::Address, rpc: &str, - proposal_id: [u8; 32], + proposal_id: FixedBytes<32>, ) -> Result { let rpc_url = reqwest::Url::parse(rpc).unwrap(); let provider = ProviderBuilder::new().connect_http(rpc_url); @@ -233,7 +224,7 @@ pub async fn can_execute_multisig_proposal( // Check if proposal can be executed let res = multisig_contract - .getVoteStatus(proposal_id.into()) + .getVoteStatus(proposal_id) .call() .await .map_err(|e| anyhow::anyhow!("Failed to check if proposal can be executed: {:?}", e))?; @@ -255,7 +246,7 @@ pub async fn can_execute_multisig_proposal( pub async fn get_multisig_vote_count( multisig_address: alloy::primitives::Address, rpc: &str, - proposal_id: [u8; 32], + proposal_id: FixedBytes<32>, ) -> Result { let rpc_url = reqwest::Url::parse(rpc).unwrap(); let provider = ProviderBuilder::new().connect_http(rpc_url); @@ -266,7 +257,7 @@ pub async fn get_multisig_vote_count( // Get vote count let res = multisig_contract - .getVoteStatus(proposal_id.into()) + .getVoteStatus(proposal_id) .call() .await .map_err(|e| anyhow::anyhow!("Failed to get vote count: {:?}", e))?; diff --git a/crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol b/crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol index 8f2c69e8..d46ab672 100644 --- a/crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol +++ b/crates/enclave-contract/tests/MultisigUpgradeOperator.t.sol @@ -60,19 +60,15 @@ contract MultisigUpgradeOperatorTest is Test { // Setup test measurements testMeasurement1.tag = "AzureV1"; testMeasurement1 - .mrtd = hex"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; + .mrtd = hex"cbd40696f617d42254fc7037469cbcf1414fe173678798cfa1070b7d40e26fa8175b99d0cd245994278f980dec73146a"; testMeasurement1 - .mrseam = hex"222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"; - testMeasurement1.registrar_slots = new uint8[](2); + .mrseam = hex"9790d89a10210ec6968a773cee2ca05b5aa97309f36727a968527be4606fc19e6f73acce350946c9d46a9bf7a63f8430"; + testMeasurement1.registrar_slots = new uint8[](1); testMeasurement1.registrar_slots[0] = 4; - testMeasurement1.registrar_slots[1] = 5; - testMeasurement1.registrar_values = new bytes[](2); + testMeasurement1.registrar_values = new bytes[](1); testMeasurement1.registrar_values[ 0 - ] = hex"33333333333333333333333333333333333333333333333333333333"; - testMeasurement1.registrar_values[ - 1 - ] = hex"44444444444444444444444444444444444444444444444444444444"; + ] = hex"6f2f7d9a42b35a2f8f9d7bf366ca3e369a45d004f3ac49b0a93785fe817c82b5"; testMeasurement2.tag = "AWSV1"; testMeasurement2 @@ -371,8 +367,8 @@ contract MultisigUpgradeOperatorTest is Test { assertEq(retrieved.tag, testMeasurement1.tag); assertEq(retrieved.mrtd, testMeasurement1.mrtd); assertEq(retrieved.mrseam, testMeasurement1.mrseam); - assertEq(retrieved.registrar_slots.length, 2); - assertEq(retrieved.registrar_values.length, 2); + assertEq(retrieved.registrar_slots.length, 1); + assertEq(retrieved.registrar_values.length, 1); } // Test cannot get measurements for non-add proposals diff --git a/crates/enclave-contract/tests/multisig_test.rs b/crates/enclave-contract/tests/multisig_test.rs index eda8cfd2..3e9ea1e0 100644 --- a/crates/enclave-contract/tests/multisig_test.rs +++ b/crates/enclave-contract/tests/multisig_test.rs @@ -51,15 +51,12 @@ pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Err print_flush("Creating multisig proposal...\n"); // Create a proposal to set MRTD - let (proposal_id, nonce) = + let proposal_id = create_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, params.clone()) .await .map_err(|e| anyhow::anyhow!("multisig workflow failed to create proposal: {:?}", e))?; - print_flush(format!( - "Proposal created with ID: {:?}, nonce: {}\n", - proposal_id, nonce - )); + print_flush(format!("Proposal created with ID: {:?}\n", proposal_id)); // Wait a bit for the transaction to be processed sleep(Duration::from_secs(2)); @@ -73,7 +70,7 @@ pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Err "Initial vote count - Total votes: {}\n", total_votes )); - assert_eq!(total_votes, 0, "Initial total votes should be 0"); + assert_eq!(total_votes, 1, "Initial total votes should be 1"); // Check if proposal can be executed (should be false initially) let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) @@ -83,42 +80,6 @@ pub async fn test_multisig_upgrade_operator_workflow() -> Result<(), anyhow::Err print_flush(format!("Can execute proposal: {}\n", can_execute)); assert!(!can_execute, "Proposal should not be executable initially"); - print_flush("Alice voting yes on proposal...\n"); - - // Alice votes yes - vote_on_multisig_proposal(multisig_address, ANVIL_ALICE_SK, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to vote with Alice: {:?}", e))?; - - // Wait a bit for the transaction to be processed - sleep(Duration::from_secs(2)); - - // Check vote count after Alice's vote - let total_votes = get_multisig_vote_count(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to get vote count: {:?}", e))?; - - print_flush(format!( - "Vote count after Alice - Total votes: {}\n", - total_votes - )); - - assert_eq!(total_votes, 1, "Total votes should be 1 after Alice's vote"); - - // Check if proposal can be executed (should still be false with only 1 vote) - let can_execute = can_execute_multisig_proposal(multisig_address, reth_rpc, proposal_id) - .await - .map_err(|e| anyhow::anyhow!("failed to check if proposal can be executed: {:?}", e))?; - - print_flush(format!( - "Can execute proposal after Alice: {}\n", - can_execute - )); - assert!( - !can_execute, - "Proposal should not be executable with only 1 vote" - ); - print_flush("Bob voting yes on proposal...\n"); // Bob votes yes From 3756cf18059be5c4afc64a359d495fbaff891da3 Mon Sep 17 00:00:00 2001 From: cdrappi Date: Wed, 22 Oct 2025 13:28:03 -0400 Subject: [PATCH 18/21] annoying --- scripts/run_integration_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 52d013b1..3caba35f 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -17,6 +17,7 @@ echo "๐Ÿš€ Starting integration tests..." # Function to cleanup processes cleanup() { echo "๐Ÿงน Cleaning up processes..." + echo $(sudo supervisorctl status) sudo supervisorctl stop all || true # Logs are stored elsewhere: # ~/.reth-logs and /var/log/reth.{out,err}.log From f50d507641dfa7ec2613813ddb1cd86df803d4fb Mon Sep 17 00:00:00 2001 From: cdrappi Date: Wed, 22 Oct 2025 14:10:08 -0400 Subject: [PATCH 19/21] sleep longer --- scripts/run_integration_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 3caba35f..ae197fd9 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -97,7 +97,8 @@ echo "โœ… test_boot_share_root_key passed" # Test 3: Run snapshot integration handlers test echo "๐Ÿงช Running test_snapshot_integration_handlers..." sudo supervisorctl start enclave-server -sleep 2 +sleep 10 + if ! sudo "${binaries[0]}" test_snapshot_integration_handlers; then echo "โŒ test_snapshot_integration_handlers failed" exit 1 From 16d180f492f3cc3aeb2b543d14381f677148234a Mon Sep 17 00:00:00 2001 From: cdrappi Date: Wed, 22 Oct 2025 14:11:32 -0400 Subject: [PATCH 20/21] sleep even longer --- scripts/run_integration_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index ae197fd9..c31d0b49 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -29,7 +29,7 @@ trap cleanup EXIT # Make sure enclave is running so we can start reth sudo supervisorctl start enclave-server || true -sleep 2 +sleep 10 # Start services via supervisor echo "๐Ÿ”ง Starting supervisor services..." From 504d66745f219395264f4e85cbd49f05c394ff35 Mon Sep 17 00:00:00 2001 From: daltoncoder Date: Wed, 22 Oct 2025 14:21:27 -0400 Subject: [PATCH 21/21] fix snapshot test --- crates/enclave-server/tests/integration/snapshot.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/enclave-server/tests/integration/snapshot.rs b/crates/enclave-server/tests/integration/snapshot.rs index 43ac9ed1..168f2c38 100644 --- a/crates/enclave-server/tests/integration/snapshot.rs +++ b/crates/enclave-server/tests/integration/snapshot.rs @@ -144,6 +144,8 @@ pub async fn test_snapshot_integration_handlers() -> Result<(), anyhow::Error> { "restore_from_encrypted_snapshot failed: {}", restore_resp.error ); + // give reth some time to start back up + sleep(Duration::from_secs(15)); assert!(Path::new(format!("{}/db/mdbx.dat", RETH_DATA_DIR).as_str()).exists()); assert!(reth_is_running());