From f6927c7348df333e8f9f84c3cb71da14239583d3 Mon Sep 17 00:00:00 2001 From: Noven Rizkia Date: Sun, 23 Aug 2026 18:34:47 +0700 Subject: [PATCH] fix: validate keyper configs before broadcast --- script/AddKeyperSet.gnosh.s.sol | 37 ++++++++-- script/AddKeyperSet.s.sol | 37 ++++++++-- test/AddKeyperSet.t.sol | 122 ++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 test/AddKeyperSet.t.sol diff --git a/script/AddKeyperSet.gnosh.s.sol b/script/AddKeyperSet.gnosh.s.sol index 1f3716c..db09dba 100644 --- a/script/AddKeyperSet.gnosh.s.sol +++ b/script/AddKeyperSet.gnosh.s.sol @@ -8,7 +8,10 @@ import {KeyBroadcastContract} from "../src/common/KeyBroadcastContract.sol"; import {EonKeyPublish} from "../src/common/EonKeyPublish.sol"; error ActivationDeltaTooLow(); +error EmptyKeyperSet(); +error ThresholdTooLow(); error ThresholdExceedsKeyperSetSize(uint256 threshold, uint256 keyperSetSize); +error DuplicateKeyper(address keyper); error UnexpectedKeyperSet( uint256 index, address expectedKeyperSet, @@ -20,13 +23,16 @@ contract AddKeyperSet is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); console.log("deployer:", deployerAddress); - vm.startBroadcast(deployerPrivateKey); uint256 activationDelta = vm.envOr("ACTIVATION_DELTA", uint256(1)); if (activationDelta < 1) { revert ActivationDeltaTooLow(); } + address[] memory keypers = vm.envAddress("KEYPER_ADDRESSES", ","); + uint256 threshold = vm.envUint("THRESHOLD"); + _validateKeyperConfig(keypers, threshold); + address keyperSetManagerAddress = vm.envAddress( "KEYPERSETMANAGER_ADDRESS" ); @@ -41,11 +47,7 @@ contract AddKeyperSet is Script { keyBroadcastContractAddress ); - address[] memory keypers = vm.envAddress("KEYPER_ADDRESSES", ","); - uint256 threshold = vm.envUint("THRESHOLD"); - if (threshold > keypers.length) { - revert ThresholdExceedsKeyperSetSize(threshold, keypers.length); - } + vm.startBroadcast(deployerPrivateKey); uint64 keyperSetIndex = keyperSetManager.getNumKeyperSets(); KeyperSet keyperSet = new KeyperSet(); @@ -79,4 +81,27 @@ contract AddKeyperSet is Script { vm.stopBroadcast(); } + + function _validateKeyperConfig( + address[] memory keypers, + uint256 threshold + ) internal pure { + if (keypers.length == 0) { + revert EmptyKeyperSet(); + } + if (threshold == 0) { + revert ThresholdTooLow(); + } + if (threshold > keypers.length) { + revert ThresholdExceedsKeyperSetSize(threshold, keypers.length); + } + + for (uint256 i = 0; i < keypers.length; i++) { + for (uint256 j = i + 1; j < keypers.length; j++) { + if (keypers[i] == keypers[j]) { + revert DuplicateKeyper(keypers[i]); + } + } + } + } } diff --git a/script/AddKeyperSet.s.sol b/script/AddKeyperSet.s.sol index 1f3716c..db09dba 100644 --- a/script/AddKeyperSet.s.sol +++ b/script/AddKeyperSet.s.sol @@ -8,7 +8,10 @@ import {KeyBroadcastContract} from "../src/common/KeyBroadcastContract.sol"; import {EonKeyPublish} from "../src/common/EonKeyPublish.sol"; error ActivationDeltaTooLow(); +error EmptyKeyperSet(); +error ThresholdTooLow(); error ThresholdExceedsKeyperSetSize(uint256 threshold, uint256 keyperSetSize); +error DuplicateKeyper(address keyper); error UnexpectedKeyperSet( uint256 index, address expectedKeyperSet, @@ -20,13 +23,16 @@ contract AddKeyperSet is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); address deployerAddress = vm.addr(deployerPrivateKey); console.log("deployer:", deployerAddress); - vm.startBroadcast(deployerPrivateKey); uint256 activationDelta = vm.envOr("ACTIVATION_DELTA", uint256(1)); if (activationDelta < 1) { revert ActivationDeltaTooLow(); } + address[] memory keypers = vm.envAddress("KEYPER_ADDRESSES", ","); + uint256 threshold = vm.envUint("THRESHOLD"); + _validateKeyperConfig(keypers, threshold); + address keyperSetManagerAddress = vm.envAddress( "KEYPERSETMANAGER_ADDRESS" ); @@ -41,11 +47,7 @@ contract AddKeyperSet is Script { keyBroadcastContractAddress ); - address[] memory keypers = vm.envAddress("KEYPER_ADDRESSES", ","); - uint256 threshold = vm.envUint("THRESHOLD"); - if (threshold > keypers.length) { - revert ThresholdExceedsKeyperSetSize(threshold, keypers.length); - } + vm.startBroadcast(deployerPrivateKey); uint64 keyperSetIndex = keyperSetManager.getNumKeyperSets(); KeyperSet keyperSet = new KeyperSet(); @@ -79,4 +81,27 @@ contract AddKeyperSet is Script { vm.stopBroadcast(); } + + function _validateKeyperConfig( + address[] memory keypers, + uint256 threshold + ) internal pure { + if (keypers.length == 0) { + revert EmptyKeyperSet(); + } + if (threshold == 0) { + revert ThresholdTooLow(); + } + if (threshold > keypers.length) { + revert ThresholdExceedsKeyperSetSize(threshold, keypers.length); + } + + for (uint256 i = 0; i < keypers.length; i++) { + for (uint256 j = i + 1; j < keypers.length; j++) { + if (keypers[i] == keypers[j]) { + revert DuplicateKeyper(keypers[i]); + } + } + } + } } diff --git a/test/AddKeyperSet.t.sol b/test/AddKeyperSet.t.sol new file mode 100644 index 0000000..6531c71 --- /dev/null +++ b/test/AddKeyperSet.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import "forge-std/Test.sol"; +import { + ActivationDeltaTooLow, + AddKeyperSet, + DuplicateKeyper, + EmptyKeyperSet, + ThresholdExceedsKeyperSetSize, + ThresholdTooLow +} from "../script/AddKeyperSet.s.sol"; + +contract AddKeyperSetHarness is AddKeyperSet { + function validateKeyperConfig( + address[] memory keypers, + uint256 threshold + ) external pure { + _validateKeyperConfig(keypers, threshold); + } +} + +contract AddKeyperSetTest is Test { + AddKeyperSetHarness internal addKeyperSet; + + function setUp() public { + addKeyperSet = new AddKeyperSetHarness(); + } + + function testRejectsEmptyKeyperSet() public { + address[] memory keypers = new address[](0); + + vm.expectRevert(EmptyKeyperSet.selector); + addKeyperSet.validateKeyperConfig(keypers, 1); + } + + function testRejectsZeroThreshold() public { + address[] memory keypers = _keypers(1); + + vm.expectRevert(ThresholdTooLow.selector); + addKeyperSet.validateKeyperConfig(keypers, 0); + } + + function testRejectsThresholdAboveKeyperSetSize() public { + address[] memory keypers = _keypers(2); + + vm.expectRevert( + abi.encodeWithSelector( + ThresholdExceedsKeyperSetSize.selector, + 3, + keypers.length + ) + ); + addKeyperSet.validateKeyperConfig(keypers, 3); + } + + function testRejectsDuplicateKeyper() public { + address[] memory keypers = _keypers(3); + keypers[2] = keypers[0]; + + vm.expectRevert( + abi.encodeWithSelector(DuplicateKeyper.selector, keypers[0]) + ); + addKeyperSet.validateKeyperConfig(keypers, 2); + } + + function testAcceptsSingleKeyperWithThresholdOne() public view { + address[] memory keypers = _keypers(1); + + addKeyperSet.validateKeyperConfig(keypers, 1); + } + + function testAcceptsThresholdEqualToKeyperSetSize() public view { + address[] memory keypers = _keypers(3); + + addKeyperSet.validateKeyperConfig(keypers, keypers.length); + } + + function testAcceptsThresholdBelowSimpleMajority() public view { + address[] memory keypers = _keypers(3); + + addKeyperSet.validateKeyperConfig(keypers, 1); + } + + function testRunRejectsInvalidActivationDeltaBeforeBroadcastSetup() public { + vm.setEnv("PRIVATE_KEY", "1"); + vm.setEnv("ACTIVATION_DELTA", "0"); + vm.setEnv("KEYPER_ADDRESSES", "not-an-address"); + + vm.expectRevert(ActivationDeltaTooLow.selector); + addKeyperSet.run(); + } + + function testRunRejectsInvalidKeyperConfigBeforeBroadcastSetup() public { + vm.setEnv("PRIVATE_KEY", "1"); + vm.setEnv("ACTIVATION_DELTA", "1"); + vm.setEnv("KEYPERSETMANAGER_ADDRESS", "not-an-address"); + vm.setEnv( + "KEYPER_ADDRESSES", + "0x0000000000000000000000000000000000000001" + ); + vm.setEnv("THRESHOLD", "2"); + + vm.expectRevert( + abi.encodeWithSelector( + ThresholdExceedsKeyperSetSize.selector, + 2, + 1 + ) + ); + addKeyperSet.run(); + } + + function _keypers( + uint256 count + ) internal pure returns (address[] memory keypers) { + keypers = new address[](count); + for (uint256 i = 0; i < count; i++) { + keypers[i] = address(uint160(i + 1)); + } + } +}