diff --git a/.gitmodules b/.gitmodules index 631e4729..925b6f41 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,9 @@ [submodule "contracts/lib/openzeppelin-contracts-upgradeable"] path = contracts/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "contracts"] + path = contracts + url = https://github.com/EspressoSystems/timeboost-contracts.git [submodule "test-node"] path = test-node url = https://github.com/EspressoSystems/decentralized-timeboost-nitro-testnode diff --git a/Cargo.lock b/Cargo.lock index 6850b4b4..4bbcc384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1594,6 +1594,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "anvil" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e29d3ca6c44fd6bc23219f6441303e329ee44580d3cee160df9c5d205e8fc83" +dependencies = [ + "thiserror 2.0.16", +] + [[package]] name = "anyhow" version = "1.0.99" diff --git a/contracts b/contracts new file mode 160000 index 00000000..d367ab38 --- /dev/null +++ b/contracts @@ -0,0 +1 @@ +Subproject commit d367ab389a735182f245abfc217121e3bb2a9a85 diff --git a/contracts/.gitignore b/contracts/.gitignore deleted file mode 100644 index 85198aaa..00000000 --- a/contracts/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -# Docs -docs/ - -# Dotenv file -.env diff --git a/contracts/lib/openzeppelin-contracts-upgradeable b/contracts/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index e725abdd..00000000 --- a/contracts/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e725abddf1e01cf05ace496e950fc8e243cc7cab diff --git a/contracts/src/KeyManager.sol b/contracts/src/KeyManager.sol deleted file mode 100644 index ca2f5060..00000000 --- a/contracts/src/KeyManager.sol +++ /dev/null @@ -1,270 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -contract KeyManager is Initializable, OwnableUpgradeable, UUPSUpgradeable { - struct CommitteeMember { - /// @notice public key for consensus votes, also used as the primary label for a node - bytes sigKey; - /// @notice DH public key used for authenticated network messages - bytes dhKey; - /// @notice public key for encrypting DKG-specific payloads - bytes dkgKey; - /// @notice a network address: `ip:port` or `hostname:port` - string networkAddress; - } - - /// @notice The consensus committee rotates with each epoch, registered by contract `manager`. - /// @notice Timeboost makes the simplifying decision that this committee is exactly the keyset - struct Committee { - /// @notice unique identifier for the committee, assigned by this contract - uint64 id; - /// @notice wall clock time since unix epoch for this committee to be active - uint64 effectiveTimestamp; - /// @notice constituting members and their key materials - CommitteeMember[] members; - } - - /// @notice Emitted when a committee is created. - /// @param id The id of the committee. - event CommitteeCreated(uint64 indexed id); - - /// @notice Emitted when the threshold encryption key is set. - /// @param thresholdEncryptionKey The threshold encryption key. - event ThresholdEncryptionKeyUpdated(bytes thresholdEncryptionKey); - - /// @notice Emitted when the manager is changed. - /// @param oldManager The old manager. - /// @param newManager The new manager. - event ManagerChanged(address indexed oldManager, address indexed newManager); - - /// @notice Emitted when a committee is removed. - /// @param fromId The id of the first committee to prune. - /// @param toId The id of the last committee to prune. - event CommitteesPruned(uint64 indexed fromId, uint64 indexed toId); - - /// @notice Thrown when the caller is not the manager. - /// @param caller The address that called the function. - error NotManager(address caller); - - /// @notice Thrown when the address is invalid. - error InvalidAddress(); - - /// @notice Thrown when the threshold encryption key is already set. - error ThresholdEncryptionKeyAlreadySet(); - - /// @notice Thrown when the committee id does not exist. - /// @param committeeId The id of the committee. - error CommitteeIdDoesNotExist(uint64 committeeId); - /// @notice Thrown when the committee is empty. - error EmptyCommitteeMembers(); - /// @notice Thrown when the effective timestamp is invalid. - error InvalidEffectiveTimestamp(uint64 effectiveTimestamp, uint64 lastEffectiveTimestamp); - /// @notice Thrown when there is no committee scheduled. - error NoCommitteeScheduled(); - /// @notice Thrown when the committee id overflows. - error CommitteeIdOverflow(); - /// @notice Thrown when the committee is too recent to remove. - error CannotRemoveRecentCommittees(); - /// @notice Thrown when pruning with invalid range. - error InvalidPruneRange(uint64 upToCommitteeId, uint64 oldestStored, uint64 nextCommitteeId); - - /// @notice The threshold encryption key for the committee. - bytes public thresholdEncryptionKey; - /// @notice The mapping of committee ids to committees. - mapping(uint64 => Committee) public committees; - /// @notice The manager of the contract. - address public manager; - /// @notice The next committee id. - uint64 public nextCommitteeId; - /// @notice The oldest committee id still stored in the mapping - uint64 private _oldestStoredCommitteeId; - /// @notice The gap for future upgrades. - uint256[48] private __gap; - - /// @notice Modifier to check if the caller is the manager. - modifier onlyManager() { - _onlyManager(); - _; - } - - /// @notice Internal function to check if the caller is the manager. - function _onlyManager() internal view { - if (msg.sender != manager) { - revert NotManager(msg.sender); - } - } - - constructor() { - _disableInitializers(); - } - - /** - * @notice This function is used to initialize the contract. - * @dev Reverts if the manager is the zero address. - * @dev Assumes that the manager is valid. - * @dev This must be called once when the contract is first deployed. - * @param initialManager The initial manager. - */ - function initialize(address initialManager) external initializer { - if (initialManager == address(0)) { - revert InvalidAddress(); - } - __Ownable_init(msg.sender); - __UUPSUpgradeable_init(); - manager = initialManager; - } - - /** - * @notice This function is used to authorize the upgrade of the contract. - * @dev Reverts if the caller is not the owner. - * @dev Assumes that the new implementation is valid. - * @param newImplementation The new implementation. - */ - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - - /** - * @notice This function is used to set the manager. - * @dev Reverts if the manager is the zero address or the same as the current manager. - * @dev Reverts if the caller is not the owner. - * @dev Assumes that the manager is valid. - * @param newManager The new manager. - */ - function setManager(address newManager) external virtual onlyOwner { - if (newManager == address(0) || newManager == manager) { - revert InvalidAddress(); - } - address oldManager = manager; - manager = newManager; - emit ManagerChanged(oldManager, newManager); - } - - /** - * @notice This function is used to set the threshold encryption key. - * @dev Reverts if the threshold encryption key is already set. - * @dev Reverts if the caller is not the manager. - * @dev Assumes that the threshold encryption key is valid. - * @param newThresholdEncryptionKey The threshold encryption key. - */ - function setThresholdEncryptionKey(bytes calldata newThresholdEncryptionKey) external virtual onlyManager { - if (thresholdEncryptionKey.length > 0) { - revert ThresholdEncryptionKeyAlreadySet(); - } - thresholdEncryptionKey = newThresholdEncryptionKey; - emit ThresholdEncryptionKeyUpdated(newThresholdEncryptionKey); - } - - /** - * @notice This function is used to set the next committee. - * @dev Reverts if the members array is empty. - * @dev Reverts if the effective timestamp is less than the last effective timestamp. - * @dev Reverts if the committees mapping is at uint64.max. - * @dev Assumes that the committee members are valid. - * @param effectiveTimestamp The effective timestamp of the committee. - * @param members The committee members. - * @return committeeId The id of the new committee. - */ - function setNextCommittee(uint64 effectiveTimestamp, CommitteeMember[] calldata members) - external - virtual - onlyManager - returns (uint64 committeeId) - { - if (members.length == 0) { - revert EmptyCommitteeMembers(); - } - - // ensure the effective timestamp is greater than the last effective timestamp - if (nextCommitteeId > 0) { - uint64 lastTimestamp = committees[nextCommitteeId - 1].effectiveTimestamp; - if (effectiveTimestamp <= lastTimestamp) { - revert InvalidEffectiveTimestamp(effectiveTimestamp, lastTimestamp); - } - } - - if (nextCommitteeId == type(uint64).max) revert CommitteeIdOverflow(); - - committees[nextCommitteeId] = - Committee({id: nextCommitteeId, effectiveTimestamp: effectiveTimestamp, members: members}); - - nextCommitteeId++; - - emit CommitteeCreated(nextCommitteeId - 1); - return nextCommitteeId - 1; - } - - /** - * @notice This function is used to get the committee by id. - * @dev Reverts if the id is greater than the length of the committees mapping. - * @dev Reverts if the id is less than the head committee id. - * @param id The id of the committee. - * @return committee The committee. - */ - function getCommitteeById(uint64 id) external view virtual returns (Committee memory committee) { - if (id < _oldestStoredCommitteeId || committees[id].id != id) { - revert CommitteeIdDoesNotExist(id); - } - - return committees[id]; - } - - /** - * @notice This function is used to get the current committee id. - * @dev Reverts if there is no committee scheduled at the current timestamp. - * @dev Searches backwards through existing committees to find the active one. - * @return committeeId The current committee id. - */ - function currentCommitteeId() public view virtual returns (uint64 committeeId) { - uint64 currentTimestamp = uint64(block.timestamp); - - if (nextCommitteeId == 0 || _oldestStoredCommitteeId >= nextCommitteeId) { - revert NoCommitteeScheduled(); - } - - // Search backwards from most recent committee to oldest stored - uint64 currCommitteeId = nextCommitteeId - 1; - while (currCommitteeId >= _oldestStoredCommitteeId) { - if (currentTimestamp >= committees[currCommitteeId].effectiveTimestamp) { - return currCommitteeId; - } - - if (currCommitteeId == 0) { - break; - } - - currCommitteeId--; - } - - revert NoCommitteeScheduled(); - } - - /** - * @notice Prunes all committees from _oldestStoredCommitteeId up to and including upToCommitteeId. - * @dev This matches timeboost's garbage collection behavior of removing old committees in bulk. - * @dev Reverts if upToCommitteeId is not in a valid range for pruning. - * @dev Reverts if any committee in the range became effective within the last 10 minutes. - * @param upToCommitteeId The highest committee ID to prune (inclusive). - */ - function pruneUntil(uint64 upToCommitteeId) external virtual onlyManager { - if (upToCommitteeId < _oldestStoredCommitteeId || upToCommitteeId >= nextCommitteeId) { - revert InvalidPruneRange(upToCommitteeId, _oldestStoredCommitteeId, nextCommitteeId); - } - - // Delete all committees in range - uint64 cutOffTime = uint64(block.timestamp - 10 minutes); - uint64 oldOldestStored = _oldestStoredCommitteeId; - for (uint64 id = _oldestStoredCommitteeId; id <= upToCommitteeId; id++) { - if (committees[id].effectiveTimestamp >= cutOffTime) { - revert CannotRemoveRecentCommittees(); - } - delete committees[id]; - } - - _oldestStoredCommitteeId = upToCommitteeId + 1; - - emit CommitteesPruned(oldOldestStored, upToCommitteeId); - } -} diff --git a/contracts/test/KeyManager.t.sol b/contracts/test/KeyManager.t.sol deleted file mode 100644 index 3b1eb7ac..00000000 --- a/contracts/test/KeyManager.t.sol +++ /dev/null @@ -1,290 +0,0 @@ - // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {KeyManager} from "../src/KeyManager.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -contract KeyManagerTest is Test { - KeyManager public keyManagerProxy; - address public manager; - address public owner; - - function setUp() public { - owner = makeAddr("owner"); - manager = makeAddr("manager"); - KeyManager keyManagerImpl = new KeyManager(); - bytes memory data = abi.encodeWithSelector(KeyManager.initialize.selector, manager); - vm.prank(owner); - ERC1967Proxy proxy = new ERC1967Proxy(address(keyManagerImpl), data); - keyManagerProxy = KeyManager(address(proxy)); - } - - function createTestMembers() internal pure returns (KeyManager.CommitteeMember[] memory) { - KeyManager.CommitteeMember[] memory members = new KeyManager.CommitteeMember[](1); - bytes memory randomBytes = abi.encodePacked("1"); - members[0] = KeyManager.CommitteeMember({ - sigKey: randomBytes, - dhKey: randomBytes, - dkgKey: randomBytes, - networkAddress: "127.0.0.1:8080" - }); - return members; - } - - function test_setThresholdEncryptionKey() public { - bytes memory thresholdEncKey = abi.encodePacked("1"); - vm.prank(manager); - vm.expectEmit(true, true, true, true); - emit KeyManager.ThresholdEncryptionKeyUpdated(thresholdEncKey); - keyManagerProxy.setThresholdEncryptionKey(thresholdEncKey); - assertEq(keyManagerProxy.thresholdEncryptionKey(), thresholdEncKey); - } - - function test_setNextCommittee() public { - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - vm.prank(manager); - vm.expectEmit(true, true, true, true); - emit KeyManager.CommitteeCreated(0); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), committeeMembers); - - // Test accessing the committee data - KeyManager.Committee memory retrievedCommittee = keyManagerProxy.getCommitteeById(0); - assertEq(retrievedCommittee.effectiveTimestamp, uint64(block.timestamp)); - assertEq(retrievedCommittee.members.length, 1); - assertEq(retrievedCommittee.members[0].sigKey, committeeMembers[0].sigKey); - assertEq(retrievedCommittee.members[0].dhKey, committeeMembers[0].dhKey); - assertEq(retrievedCommittee.members[0].dkgKey, committeeMembers[0].dkgKey); - assertEq(retrievedCommittee.members[0].networkAddress, committeeMembers[0].networkAddress); - - // Test accessing the current committee - uint64 currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 0); - retrievedCommittee = keyManagerProxy.getCommitteeById(currentCommitteeId); - assertEq(retrievedCommittee.effectiveTimestamp, uint64(block.timestamp)); - assertEq(retrievedCommittee.members.length, 1); - assertEq(retrievedCommittee.members[0].sigKey, committeeMembers[0].sigKey); - assertEq(retrievedCommittee.members[0].dhKey, committeeMembers[0].dhKey); - assertEq(retrievedCommittee.members[0].dkgKey, committeeMembers[0].dkgKey); - } - - function test_revertWhenEmptyCommittee_setNextCommittee() public { - vm.startPrank(manager); - vm.expectRevert(abi.encodeWithSelector(KeyManager.EmptyCommitteeMembers.selector)); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), new KeyManager.CommitteeMember[](0)); - vm.stopPrank(); - } - - function test_revertWhenInvalidEffectiveTimestamp_setNextCommittee() public { - KeyManager.CommitteeMember[] memory members = createTestMembers(); - - vm.startPrank(manager); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), members); - - // Try to create committee with earlier timestamp - vm.expectRevert( - abi.encodeWithSelector( - KeyManager.InvalidEffectiveTimestamp.selector, uint64(block.timestamp - 1), uint64(block.timestamp) - ) - ); - keyManagerProxy.setNextCommittee(uint64(block.timestamp - 1), members); - vm.stopPrank(); - } - - function test_setManager() public { - address newManager = makeAddr("newManager"); - vm.prank(owner); - vm.expectEmit(true, true, true, true); - emit KeyManager.ManagerChanged(manager, newManager); - keyManagerProxy.setManager(newManager); - assertEq(keyManagerProxy.manager(), newManager); - } - - function test_revertWhenInvalidAddress_setManager() public { - vm.startPrank(owner); - // revert for the zero address - vm.expectRevert(abi.encodeWithSelector(KeyManager.InvalidAddress.selector)); - keyManagerProxy.setManager(address(0)); - - // revert for the same manager - vm.expectRevert(abi.encodeWithSelector(KeyManager.InvalidAddress.selector)); - keyManagerProxy.setManager(manager); - vm.stopPrank(); - } - - function test_revertWhenNotOwner_setManager() public { - vm.startPrank(manager); - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, manager)); - keyManagerProxy.setManager(manager); - vm.stopPrank(); - } - - function test_revertWhenNotManager_setThresholdEncryptionKey() public { - bytes memory thresholdEncKey = abi.encodePacked("1"); - vm.expectRevert(abi.encodeWithSelector(KeyManager.NotManager.selector, address(this))); - keyManagerProxy.setThresholdEncryptionKey(thresholdEncKey); - } - - function test_revertWhenThresholdEncryptionKeyAlreadySet_setThresholdEncryptionKey() public { - bytes memory thresholdEncKey = abi.encodePacked("1"); - vm.startPrank(manager); - keyManagerProxy.setThresholdEncryptionKey(thresholdEncKey); - vm.expectRevert(abi.encodeWithSelector(KeyManager.ThresholdEncryptionKeyAlreadySet.selector)); - keyManagerProxy.setThresholdEncryptionKey(thresholdEncKey); - vm.stopPrank(); - } - - function test_revertWhenNotManager_setNextCommittee() public { - vm.expectRevert(abi.encodeWithSelector(KeyManager.NotManager.selector, address(this))); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), new KeyManager.CommitteeMember[](0)); - - // the owner should not be able to schedule the committee as it's not the manager - // the owner can become the manager by calling setManager - vm.prank(owner); - vm.expectRevert(abi.encodeWithSelector(KeyManager.NotManager.selector, owner)); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), new KeyManager.CommitteeMember[](0)); - } - - // Tests for currentCommitteeId function - function test_revertWhenEmptyCommittees_currentCommitteeId() public { - vm.expectRevert(abi.encodeWithSelector(KeyManager.NoCommitteeScheduled.selector)); - keyManagerProxy.currentCommitteeId(); - } - - function test_currentCommitteeId_oneCommitteeScheduled_effectiveNow() public { - // Create a committee that's effective now - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - uint64 effectiveTimestamp = uint64(block.timestamp); - vm.prank(manager); - keyManagerProxy.setNextCommittee(effectiveTimestamp, committeeMembers); - - uint64 currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 0); - } - - function test_revertWhenNoCommitteeScheduledAtCurrentTimestamp_currentCommitteeId() public { - // Create a committee that's effective in the future - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - uint64 effectiveTimestamp = uint64(block.timestamp + 100); - vm.prank(manager); - keyManagerProxy.setNextCommittee(effectiveTimestamp, committeeMembers); - - vm.expectRevert(abi.encodeWithSelector(KeyManager.NoCommitteeScheduled.selector)); - keyManagerProxy.currentCommitteeId(); - } - - function test_currentCommitteeId_singleCommittee_effectiveInThePast() public { - // Create a committee that was effective in the past - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - uint64 effectiveTimestamp = 100; - vm.prank(manager); - keyManagerProxy.setNextCommittee(effectiveTimestamp, committeeMembers); - - vm.warp(101); - uint64 currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 0); - } - - function test_currentCommitteeId_multipleCommittees() public { - // Create multiple committees with different timestamps - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - vm.startPrank(manager); - - // Committee 0: effective now - uint64 timestamp0 = uint64(block.timestamp); - keyManagerProxy.setNextCommittee(timestamp0, committeeMembers); - - // Committee 1: effective in 100 seconds - uint64 timestamp1 = uint64(block.timestamp + 100); - keyManagerProxy.setNextCommittee(timestamp1, committeeMembers); - - // Committee 2: effective in 200 seconds - uint64 timestamp2 = uint64(block.timestamp + 200); - keyManagerProxy.setNextCommittee(timestamp2, committeeMembers); - - vm.stopPrank(); - - // Test current committee (should be committee 0) - uint64 currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 0); - - // Test at timestamp1 - only warp once to minimize gas - vm.warp(timestamp1); - currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 1); - - // Test at timestamp2 - only warp once more - vm.warp(timestamp2); - currentCommitteeId = keyManagerProxy.currentCommitteeId(); - assertEq(currentCommitteeId, 2); - } - - function test_nextCommitteeId() public { - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - vm.startPrank(manager); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), committeeMembers); - keyManagerProxy.setNextCommittee(uint64(block.timestamp + 100), committeeMembers); - vm.stopPrank(); - - uint64 nextCommitteeId = keyManagerProxy.nextCommitteeId(); - assertEq(nextCommitteeId, 2); - } - - function test_pruneUntil() public { - KeyManager.CommitteeMember[] memory members = createTestMembers(); - - vm.startPrank(manager); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), members); - keyManagerProxy.setNextCommittee(uint64(block.timestamp + 10 minutes), members); - - vm.warp(uint64(block.timestamp + 20 minutes)); - - // Remove first committee - vm.expectEmit(true, true, true, true); - emit KeyManager.CommitteesPruned(0, 0); - keyManagerProxy.pruneUntil(0); - - // Verify first committee is deleted - vm.expectRevert(); - keyManagerProxy.getCommitteeById(0); - - // Verify second committee still exists - KeyManager.Committee memory committee1 = keyManagerProxy.getCommitteeById(1); - assertEq(committee1.id, 1); - vm.stopPrank(); - } - - function test_revertWhenCannotRemoveRecentCommittees_pruneUntil() public { - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - vm.startPrank(manager); - keyManagerProxy.setNextCommittee(uint64(block.timestamp), committeeMembers); - keyManagerProxy.setNextCommittee(uint64(block.timestamp + 10 minutes), committeeMembers); - keyManagerProxy.setNextCommittee(uint64(block.timestamp + 20 minutes), committeeMembers); - vm.warp(uint64(block.timestamp + 10 minutes)); - vm.expectRevert(abi.encodeWithSelector(KeyManager.CannotRemoveRecentCommittees.selector)); - keyManagerProxy.pruneUntil(0); - vm.expectRevert(abi.encodeWithSelector(KeyManager.CannotRemoveRecentCommittees.selector)); - keyManagerProxy.pruneUntil(1); - } - - function test_revertWhenInvalidPruneRange_pruneUntil() public { - vm.startPrank(manager); - vm.expectRevert(abi.encodeWithSelector(KeyManager.InvalidPruneRange.selector, 0, 0, 0)); - keyManagerProxy.pruneUntil(0); - - KeyManager.CommitteeMember[] memory committeeMembers = createTestMembers(); - - keyManagerProxy.setNextCommittee(uint64(block.timestamp), committeeMembers); - vm.expectRevert(abi.encodeWithSelector(KeyManager.InvalidPruneRange.selector, 1, 0, 1)); - keyManagerProxy.pruneUntil(1); - vm.stopPrank(); - } -} diff --git a/justfile b/justfile index c3601ce6..5de77dce 100644 --- a/justfile +++ b/justfile @@ -11,6 +11,11 @@ LOG_LEVELS := "RUST_LOG=timeboost=debug,sailfish=debug,cliquenet=debug,tests=deb build *ARGS: cargo build {{ARGS}} +update-submodules: + git submodule update --remote --recursive + cd timeboost-proto && cargo build + cd ../contracts && forge build + build_release *ARGS: cargo build --release --workspace --all-targets {{ARGS}} diff --git a/timeboost-contract/Cargo.toml b/timeboost-contract/Cargo.toml index ed694313..0981b914 100644 --- a/timeboost-contract/Cargo.toml +++ b/timeboost-contract/Cargo.toml @@ -29,4 +29,4 @@ tracing = { workspace = true } url = { workspace = true } [build-dependencies] -alloy = { workspace = true } +alloy = { workspace = true } \ No newline at end of file diff --git a/timeboost-contract/README.md b/timeboost-contract/README.md new file mode 100644 index 00000000..2505884a --- /dev/null +++ b/timeboost-contract/README.md @@ -0,0 +1,30 @@ +# Timeboost-Contract + +A Rust crate that provides interfaces for interacting with Timeboost smart contracts from Rust code. + +## Overview + +This crate bridges the Timeboost system with deployed smart contracts, providing: +- **Deployment Utilities**: Helper functions for contract deployment (testing only) +- **Integration Patterns**: Common patterns for Timeboost<>contract communication + +## Testing + +### Running Tests + +```bash +# Run all tests +cargo test -p timeboost-contract + +# Run specific test +cargo test -p timeboost-contract test_key_manager_deployment +``` + +## Related Documentation + +- **[Contracts README](https://github.com/EspressoSystems/timeboost-contracts/blob/main/README.md)** - Smart contract details and deployment scripts +- **[Timeboost README](../README.md)** - Main system documentation + +## License + +This project is licensed under the same terms as the parent Timeboost project. \ No newline at end of file diff --git a/timeboost-contract/src/deployer.rs b/timeboost-contract/src/deployer.rs index 1b2647f6..760536d6 100644 --- a/timeboost-contract/src/deployer.rs +++ b/timeboost-contract/src/deployer.rs @@ -1,5 +1,4 @@ -//! Helper logic for contract deployment - +//! Contract deployment helpers for testing use alloy::{contract::RawCallBuilder, primitives::Address, providers::Provider}; use crate::{ERC1967Proxy, KeyManager}; @@ -27,29 +26,32 @@ pub(crate) async fn deploy( } /// Given a chain provider/connector, deploy a new KeyManager contract -pub async fn deploy_key_manager_contract( - provider: impl Provider, +pub async fn deploy_key_manager_contract

( + provider: &P, manager: Address, -) -> ContractResult

{ +) -> ContractResult
+where + P: Provider, +{ // first deploy the implementation contract let tx = KeyManager::deploy_builder(&provider); let impl_addr = deploy("KeyManager", tx).await?; - let km = KeyManager::new(impl_addr, &provider); + let km = KeyManager::new(impl_addr, provider); // then deploy the proxy, point to the implementation contract and initialize it let init_data = km.initialize(manager).calldata().to_owned(); let tx = ERC1967Proxy::deploy_builder(&provider, impl_addr, init_data); let proxy_addr = deploy("KeyManagerProxy", tx).await?; + tracing::info!("deployed KeyManagerProxy at {proxy_addr:#x}"); Ok(proxy_addr) } #[cfg(test)] mod tests { + use crate::{CommitteeMemberSol, CommitteeSol, KeyManager}; use alloy::{providers::WalletProvider, sol_types::SolValue}; use rand::prelude::*; - use crate::{CommitteeMemberSol, CommitteeSol, KeyManager}; - #[tokio::test] async fn test_key_manager_deployment() { let (provider, addr) = crate::init_test_chain().await.unwrap(); diff --git a/timeboost-proto/protos b/timeboost-proto/protos index 902b8bdc..11769670 160000 --- a/timeboost-proto/protos +++ b/timeboost-proto/protos @@ -1 +1 @@ -Subproject commit 902b8bdc94b0d84f14c6c5aa701ce7bc36d40691 +Subproject commit 117696709273b7fc95dd873b1d0538126524973a