From 147467c0def8adb53b50bc361a59a1a73cd40e9a Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Mon, 14 Oct 2024 15:54:17 +0200 Subject: [PATCH] feat(contracts): integrate challenger --- bolt-contracts/script/Deploy.s.sol | 17 ++++++- .../src/contracts/BoltChallenger.sol | 51 ++++++++++++------- .../src/contracts/BoltParameters.sol | 24 +++++++-- .../src/contracts/BoltValidators.sol | 5 +- .../src/interfaces/IBoltParameters.sol | 3 ++ bolt-contracts/test/BoltChallenger.t.sol | 21 ++++++++ .../test/BoltManager.EigenLayer.t.sol | 13 ++++- .../test/BoltManager.Symbiotic.t.sol | 13 ++++- bolt-contracts/test/BoltValidators.t.sol | 13 ++++- 9 files changed, 131 insertions(+), 29 deletions(-) diff --git a/bolt-contracts/script/Deploy.s.sol b/bolt-contracts/script/Deploy.s.sol index 101f337da..2267d96a6 100644 --- a/bolt-contracts/script/Deploy.s.sol +++ b/bolt-contracts/script/Deploy.s.sol @@ -31,10 +31,23 @@ contract DeployBolt is Script { uint48 epochDuration = 1 days; uint48 slashingWindow = 7 days; + uint48 maxChallengeDuration = 7 days; bool allowUnsafeRegistration = true; + uint256 challengeBond = 1 ether; + uint256 blockhashEvmLookback = 256; - bytes memory initParameters = - abi.encodeCall(BoltParameters.initialize, (admin, epochDuration, slashingWindow, allowUnsafeRegistration)); + bytes memory initParameters = abi.encodeCall( + BoltParameters.initialize, + ( + admin, + epochDuration, + slashingWindow, + maxChallengeDuration, + allowUnsafeRegistration, + challengeBond, + blockhashEvmLookback + ) + ); address parametersProxy = Upgrades.deployUUPSProxy("BoltParameters.sol", initParameters); console.log("BoltParameters proxy deployed at", parametersProxy); diff --git a/bolt-contracts/src/contracts/BoltChallenger.sol b/bolt-contracts/src/contracts/BoltChallenger.sol index f436b6656..6a88fdc62 100644 --- a/bolt-contracts/src/contracts/BoltChallenger.sol +++ b/bolt-contracts/src/contracts/BoltChallenger.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; @@ -12,8 +15,9 @@ import {RLPWriter} from "../lib/rlp/RLPWriter.sol"; import {BeaconChainUtils} from "../lib/BeaconChainUtils.sol"; import {TransactionDecoder} from "../lib/TransactionDecoder.sol"; import {IBoltChallenger} from "../interfaces/IBoltChallenger.sol"; +import {IBoltParameters} from "../interfaces/IBoltParameters.sol"; -contract BoltChallenger is IBoltChallenger { +contract BoltChallenger is IBoltChallenger, OwnableUpgradeable, UUPSUpgradeable { using RLPReader for bytes; using RLPReader for RLPReader.RLPItem; using TransactionDecoder for bytes; @@ -22,26 +26,29 @@ contract BoltChallenger is IBoltChallenger { // ========= STORAGE ========= + /// @notice Bolt Parameters contract. + IBoltParameters public parameters; + /// @notice The set of existing unique challenge IDs. EnumerableSet.Bytes32Set internal challengeIDs; /// @notice The mapping of challenge IDs to their respective challenges. mapping(bytes32 => Challenge) internal challenges; - // ========= CONSTANTS ========= - - /// @notice The challenge bond required to open a challenge. - uint256 public constant CHALLENGE_BOND = 1 ether; + // ========= INITIALIZER ========= - /// @notice The maximum duration of a challenge to be considered valid. - uint256 public constant MAX_CHALLENGE_DURATION = 7 days; + /// @notice Initializer + /// @param _owner Address of the owner of the contract + /// @param _parameters Address of the Bolt Parameters contract + function initialize(address _owner, address _parameters) public initializer { + __Ownable_init(_owner); - /// @notice The maximum number of blocks to look back for block hashes in the EVM. - uint256 public constant BLOCKHASH_EVM_LOOKBACK = 256; - - // ========= CONSTRUCTOR ========= + parameters = IBoltParameters(_parameters); + } - constructor() {} + function _authorizeUpgrade( + address newImplementation + ) internal override onlyOwner {} // ========= VIEW FUNCTIONS ========= @@ -109,7 +116,7 @@ contract BoltChallenger is IBoltChallenger { } // Check that the attached bond amount is correct - if (msg.value != CHALLENGE_BOND) { + if (msg.value != parameters.CHALLENGE_BOND()) { revert IncorrectChallengeBond(); } @@ -194,14 +201,20 @@ contract BoltChallenger is IBoltChallenger { // The visibility of the BLOCKHASH opcode is limited to the 256 most recent blocks. // For simplicity we restrict this to 256 slots even though 256 blocks would be more accurate. - if (challenges[challengeID].targetSlot < BeaconChainUtils._getCurrentSlot() - BLOCKHASH_EVM_LOOKBACK) { + if ( + challenges[challengeID].targetSlot + < BeaconChainUtils._getCurrentSlot() - parameters.BLOCKHASH_EVM_LOOKBACK() + ) { revert BlockIsTooOld(); } // Check that the previous block is within the EVM lookback window for block hashes. // Clearly, if the previous block is available, the inclusion one will be too. uint256 previousBlockNumber = proof.inclusionBlockNumber - 1; - if (previousBlockNumber > block.number || previousBlockNumber < block.number - BLOCKHASH_EVM_LOOKBACK) { + if ( + previousBlockNumber > block.number + || previousBlockNumber < block.number - parameters.BLOCKHASH_EVM_LOOKBACK() + ) { revert InvalidBlockNumber(); } @@ -229,7 +242,7 @@ contract BoltChallenger is IBoltChallenger { revert ChallengeAlreadyResolved(); } - if (challenge.openedAt + MAX_CHALLENGE_DURATION >= Time.timestamp()) { + if (challenge.openedAt + parameters.MAX_CHALLENGE_DURATION() >= Time.timestamp()) { revert ChallengeNotExpired(); } @@ -254,7 +267,7 @@ contract BoltChallenger is IBoltChallenger { revert ChallengeAlreadyResolved(); } - if (challenge.openedAt + MAX_CHALLENGE_DURATION < Time.timestamp()) { + if (challenge.openedAt + parameters.MAX_CHALLENGE_DURATION() < Time.timestamp()) { // If the challenge has expired without being resolved, it is considered breached. // This should be handled by calling the `resolveExpiredChallenge()` function instead. revert ChallengeExpired(); @@ -468,7 +481,7 @@ contract BoltChallenger is IBoltChallenger { function _transferFullBond( address recipient ) internal { - (bool success,) = payable(recipient).call{value: CHALLENGE_BOND}(""); + (bool success,) = payable(recipient).call{value: parameters.CHALLENGE_BOND()}(""); if (!success) { revert BondTransferFailed(); } @@ -479,7 +492,7 @@ contract BoltChallenger is IBoltChallenger { function _transferHalfBond( address recipient ) internal { - (bool success,) = payable(recipient).call{value: CHALLENGE_BOND / 2}(""); + (bool success,) = payable(recipient).call{value: parameters.CHALLENGE_BOND() / 2}(""); if (!success) { revert BondTransferFailed(); } diff --git a/bolt-contracts/src/contracts/BoltParameters.sol b/bolt-contracts/src/contracts/BoltParameters.sol index 5b1853914..c9a47c37e 100644 --- a/bolt-contracts/src/contracts/BoltParameters.sol +++ b/bolt-contracts/src/contracts/BoltParameters.sol @@ -5,6 +5,8 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; contract BoltParameters is OwnableUpgradeable, UUPSUpgradeable { + // =========== STORAGE =========== // + // --> Storage layout marker: 0 bits /// @notice Duration of an epoch in seconds. @@ -19,6 +21,16 @@ contract BoltParameters is OwnableUpgradeable, UUPSUpgradeable { bool public ALLOW_UNSAFE_REGISTRATION; // --> Storage layout marker: 48 + 48 + 8 = 104 bits + /// @notice The maximum duration of a challenge before it is automatically considered valid. + uint48 public MAX_CHALLENGE_DURATION; + + /// @notice The challenge bond required to open a challenge. + uint256 public CHALLENGE_BOND; + + /// @notice The maximum number of blocks to look back for block hashes in the EVM. + uint256 public BLOCKHASH_EVM_LOOKBACK; + // --> Storage layout marker: 3 words + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. @@ -27,9 +39,9 @@ contract BoltParameters is OwnableUpgradeable, UUPSUpgradeable { * * Total storage slots: 50 */ - uint256[49] private __gap; + uint256[47] private __gap; - // ============== PROXY METHODS ============== // + // ============== INITIALIZER ============== // /// @notice The initializer for the BoltManager contract. /// @param _epochDuration The epoch duration. @@ -37,13 +49,19 @@ contract BoltParameters is OwnableUpgradeable, UUPSUpgradeable { address _owner, uint48 _epochDuration, uint48 _slashingWindow, - bool _allowUnsafeRegistration + uint48 _maxChallengeDuration, + bool _allowUnsafeRegistration, + uint256 _challengeBond, + uint256 _blockhashEvmLookback ) public initializer { __Ownable_init(_owner); EPOCH_DURATION = _epochDuration; SLASHING_WINDOW = _slashingWindow; ALLOW_UNSAFE_REGISTRATION = _allowUnsafeRegistration; + MAX_CHALLENGE_DURATION = _maxChallengeDuration; + CHALLENGE_BOND = _challengeBond; + BLOCKHASH_EVM_LOOKBACK = _blockhashEvmLookback; } function _authorizeUpgrade( diff --git a/bolt-contracts/src/contracts/BoltValidators.sol b/bolt-contracts/src/contracts/BoltValidators.sol index 87c320ff7..98a77c217 100644 --- a/bolt-contracts/src/contracts/BoltValidators.sol +++ b/bolt-contracts/src/contracts/BoltValidators.sol @@ -44,10 +44,11 @@ contract BoltValidators is IBoltValidators, BLSSignatureVerifier, OwnableUpgrade /// @param validator Validator struct event ValidatorRegistered(bytes32 indexed pubkeyHash, Validator validator); - // ========= CONSTRUCTOR ========= + // ========= INITIALIZER ========= - /// @notice Constructor + /// @notice Initializer /// @param _owner Address of the owner of the contract + /// @param _parameters Address of the Bolt Parameters contract function initialize(address _owner, address _parameters) public initializer { __Ownable_init(_owner); diff --git a/bolt-contracts/src/interfaces/IBoltParameters.sol b/bolt-contracts/src/interfaces/IBoltParameters.sol index c24b502d3..4f36d5a80 100644 --- a/bolt-contracts/src/interfaces/IBoltParameters.sol +++ b/bolt-contracts/src/interfaces/IBoltParameters.sol @@ -5,4 +5,7 @@ interface IBoltParameters { function EPOCH_DURATION() external view returns (uint48); function SLASHING_WINDOW() external view returns (uint48); function ALLOW_UNSAFE_REGISTRATION() external view returns (bool); + function MAX_CHALLENGE_DURATION() external view returns (uint48); + function CHALLENGE_BOND() external view returns (uint256); + function BLOCKHASH_EVM_LOOKBACK() external view returns (uint256); } diff --git a/bolt-contracts/test/BoltChallenger.t.sol b/bolt-contracts/test/BoltChallenger.t.sol index 3a8605885..0010f5342 100644 --- a/bolt-contracts/test/BoltChallenger.t.sol +++ b/bolt-contracts/test/BoltChallenger.t.sol @@ -5,6 +5,7 @@ import {Test, console} from "forge-std/Test.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {BoltParameters} from "../src/contracts/BoltParameters.sol"; import {BoltChallenger} from "../src/contracts/BoltChallenger.sol"; import {IBoltChallenger} from "../src/interfaces/IBoltChallenger.sol"; import {RLPReader} from "../src/lib/rlp/RLPReader.sol"; @@ -41,6 +42,7 @@ contract BoltChallengerTest is Test { BoltChallengerExt boltChallenger; + address admin = makeAddr("admin"); address challenger = makeAddr("challenger"); address resolver = makeAddr("resolver"); @@ -51,7 +53,26 @@ contract BoltChallengerTest is Test { vm.pauseGasMetering(); (target, targetPK) = makeAddrAndKey("target"); + uint48 epochDuration = 1 days; + uint48 slashingWindow = 7 days; + uint48 maxChallengeDuration = 7 days; + bool allowUnsafeRegistration = true; + uint256 challengeBond = 1 ether; + uint256 blockhashEvmLookback = 256; + + BoltParameters parameters = new BoltParameters(); + parameters.initialize( + admin, + epochDuration, + slashingWindow, + maxChallengeDuration, + allowUnsafeRegistration, + challengeBond, + blockhashEvmLookback + ); + boltChallenger = new BoltChallengerExt(); + boltChallenger.initialize(admin, address(parameters)); vm.deal(challenger, 100 ether); vm.deal(resolver, 100 ether); diff --git a/bolt-contracts/test/BoltManager.EigenLayer.t.sol b/bolt-contracts/test/BoltManager.EigenLayer.t.sol index 91c93169b..de707420c 100644 --- a/bolt-contracts/test/BoltManager.EigenLayer.t.sol +++ b/bolt-contracts/test/BoltManager.EigenLayer.t.sol @@ -53,10 +53,21 @@ contract BoltManagerEigenLayerTest is Test { uint48 epochDuration = 1 days; uint48 slashingWindow = 7 days; + uint48 maxChallengeDuration = 7 days; bool allowUnsafeRegistration = true; + uint256 challengeBond = 1 ether; + uint256 blockhashEvmLookback = 256; BoltParameters parameters = new BoltParameters(); - parameters.initialize(admin, epochDuration, slashingWindow, allowUnsafeRegistration); + parameters.initialize( + admin, + epochDuration, + slashingWindow, + maxChallengeDuration, + allowUnsafeRegistration, + challengeBond, + blockhashEvmLookback + ); // Deploy Bolt contracts validators = new BoltValidators(); diff --git a/bolt-contracts/test/BoltManager.Symbiotic.t.sol b/bolt-contracts/test/BoltManager.Symbiotic.t.sol index e4343c94a..3a98d431c 100644 --- a/bolt-contracts/test/BoltManager.Symbiotic.t.sol +++ b/bolt-contracts/test/BoltManager.Symbiotic.t.sol @@ -153,10 +153,21 @@ contract BoltManagerSymbioticTest is Test { uint48 epochDuration = 1 days; uint48 slashingWindow = 7 days; + uint48 maxChallengeDuration = 7 days; bool allowUnsafeRegistration = true; + uint256 challengeBond = 1 ether; + uint256 blockhashEvmLookback = 256; BoltParameters parameters = new BoltParameters(); - parameters.initialize(admin, epochDuration, slashingWindow, allowUnsafeRegistration); + parameters.initialize( + admin, + epochDuration, + slashingWindow, + maxChallengeDuration, + allowUnsafeRegistration, + challengeBond, + blockhashEvmLookback + ); validators = new BoltValidators(); validators.initialize(admin, address(parameters)); diff --git a/bolt-contracts/test/BoltValidators.t.sol b/bolt-contracts/test/BoltValidators.t.sol index 64ea353c3..db6ac1d35 100644 --- a/bolt-contracts/test/BoltValidators.t.sol +++ b/bolt-contracts/test/BoltValidators.t.sol @@ -24,11 +24,22 @@ contract BoltValidatorsTest is Test { function setUp() public { uint48 epochDuration = 1 days; uint48 slashingWindow = 7 days; + uint48 maxChallengeDuration = 7 days; bool allowUnsafeRegistration = true; + uint256 challengeBond = 1 ether; + uint256 blockhashEvmLookback = 256; parameters = new BoltParameters(); - parameters.initialize(admin, epochDuration, slashingWindow, allowUnsafeRegistration); + parameters.initialize( + admin, + epochDuration, + slashingWindow, + maxChallengeDuration, + allowUnsafeRegistration, + challengeBond, + blockhashEvmLookback + ); validators = new BoltValidators(); validators.initialize(admin, address(parameters)); }