Skip to content

Commit 320b3a0

Browse files
committed
make partner validator threshold immutable
1 parent 042f653 commit 320b3a0

4 files changed

Lines changed: 120 additions & 85 deletions

File tree

base/script/Deploy.s.sol

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,19 @@ contract DeployScript is DevOps {
7979
}
8080

8181
function _deployBridgeValidator(HelperConfig.NetworkConfig memory cfg, address bridge) private returns (address) {
82-
address bridgeValidatorImpl =
83-
address(new BridgeValidator({bridgeAddress: bridge, partnerValidators: cfg.partnerValidators}));
82+
address bridgeValidatorImpl = address(
83+
new BridgeValidator({
84+
bridgeAddress: bridge,
85+
partnerValidators: cfg.partnerValidators,
86+
partnerThreshold: cfg.partnerValidatorThreshold
87+
})
88+
);
8489

8590
return ERC1967Factory(cfg.erc1967Factory)
8691
.deployAndCall({
8792
implementation: bridgeValidatorImpl,
8893
admin: cfg.initialOwner,
89-
data: abi.encodeCall(
90-
BridgeValidator.initialize,
91-
(cfg.baseValidators, cfg.baseSignatureThreshold, cfg.partnerValidatorThreshold)
92-
)
94+
data: abi.encodeCall(BridgeValidator.initialize, (cfg.baseValidators, cfg.baseSignatureThreshold))
9395
});
9496
}
9597

base/script/UpgradeScript.s.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,13 @@ contract UpgradeScript is DevOps {
116116
}
117117

118118
function _upgradeBridgeValidator(HelperConfig.NetworkConfig memory cfg) internal {
119-
address bridgeValidatorImpl =
120-
address(new BridgeValidator({bridgeAddress: bridgeAddress, partnerValidators: cfg.partnerValidators}));
119+
address bridgeValidatorImpl = address(
120+
new BridgeValidator({
121+
bridgeAddress: bridgeAddress,
122+
partnerValidators: cfg.partnerValidators,
123+
partnerThreshold: cfg.partnerValidatorThreshold
124+
})
125+
);
121126

122127
console.log("Deployed new BridgeValidator implementation: %s", bridgeValidatorImpl);
123128
// Use ERC1967Factory to upgrade the proxy

base/src/BridgeValidator.sol

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,17 @@ contract BridgeValidator is Initializable {
4242
/// @notice Address of the contract holding the partner validator set
4343
address public immutable PARTNER_VALIDATORS;
4444

45+
/// @notice Required number of partner signatures for validation
46+
uint256 public immutable PARTNER_THRESHOLD;
47+
4548
/// @notice A bit to be used in bitshift operations
4649
uint256 private constant _BIT = 1;
4750

4851
//////////////////////////////////////////////////////////////
4952
/// Storage ///
5053
//////////////////////////////////////////////////////////////
5154

52-
/// @notice Required number of partner signatures
53-
uint256 public partnerValidatorThreshold;
55+
uint256 private _spacer_00;
5456

5557
/// @notice The next expected nonce to be received in `registerMessages`
5658
uint256 public nextNonce;
@@ -130,15 +132,18 @@ contract BridgeValidator is Initializable {
130132
///
131133
/// @param bridgeAddress The address of the `Bridge` contract used to check guardian roles.
132134
/// @param partnerValidators Address of the contract holding the partner validator set
133-
constructor(address bridgeAddress, address partnerValidators) {
135+
/// @param partnerThreshold The minimum number of partner validator signatures required.
136+
constructor(address bridgeAddress, address partnerValidators, uint256 partnerThreshold) {
134137
require(bridgeAddress != address(0), ZeroAddress());
135138
require(partnerValidators != address(0), ZeroAddress());
139+
require(partnerThreshold <= MAX_PARTNER_VALIDATOR_THRESHOLD, ThresholdTooHigh());
136140

137-
partnerValidatorThreshold = type(uint256).max;
138141
VerificationLib.getVerificationLibStorage().threshold = type(uint128).max;
139142

140143
BRIDGE = bridgeAddress;
141144
PARTNER_VALIDATORS = partnerValidators;
145+
PARTNER_THRESHOLD = partnerThreshold;
146+
142147
_disableInitializers();
143148
}
144149

@@ -148,15 +153,8 @@ contract BridgeValidator is Initializable {
148153
///
149154
/// @param baseValidators The initial list of Base validators.
150155
/// @param baseThreshold The minimum number of Base validator signatures required.
151-
/// @param partnerThreshold The minimum number of partner validator signatures required.
152-
function initialize(address[] calldata baseValidators, uint128 baseThreshold, uint256 partnerThreshold)
153-
external
154-
initializer
155-
{
156+
function initialize(address[] calldata baseValidators, uint128 baseThreshold) external initializer {
156157
VerificationLib.initialize(baseValidators, baseThreshold);
157-
158-
require(partnerThreshold <= MAX_PARTNER_VALIDATOR_THRESHOLD, ThresholdTooHigh());
159-
partnerValidatorThreshold = partnerThreshold;
160158
}
161159

162160
/// @notice Pre-validates a batch of Solana → Base messages.
@@ -224,7 +222,7 @@ contract BridgeValidator is Initializable {
224222
address[] memory recoveredSigners = _getSignersFromSigs(messageHashes, sigData);
225223
require(_countBaseSigners(recoveredSigners) >= VerificationLib.getBaseThreshold(), BaseThresholdNotMet());
226224

227-
uint256 partnerValidatorThreshold_ = partnerValidatorThreshold;
225+
uint256 partnerValidatorThreshold_ = PARTNER_THRESHOLD;
228226
if (partnerValidatorThreshold_ > 0) {
229227
IPartner.Signer[] memory partnerValidators = IPartner(PARTNER_VALIDATORS).getSigners();
230228
require(

base/test/BridgeValidator.t.sol

Lines changed: 94 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.28;
33

4+
import {ERC1967Factory} from "solady/utils/ERC1967Factory.sol";
45
import {Initializable} from "solady/utils/Initializable.sol";
56

67
import {DeployScript} from "../script/Deploy.s.sol";
@@ -42,22 +43,24 @@ contract BridgeValidatorTest is CommonTest {
4243

4344
function test_constructor_revertsWhenZeroBridge() public {
4445
vm.expectRevert(BridgeValidator.ZeroAddress.selector);
45-
new BridgeValidator(address(0), cfg.partnerValidators);
46+
new BridgeValidator(address(0), cfg.partnerValidators, cfg.partnerValidatorThreshold);
4647
}
4748

4849
function test_constructor_revertsWhenZeroPartnerValidators() public {
4950
vm.expectRevert(BridgeValidator.ZeroAddress.selector);
50-
new BridgeValidator(address(bridge), address(0));
51+
new BridgeValidator(address(bridge), address(0), cfg.partnerValidatorThreshold);
5152
}
5253

5354
function test_constructor_setsBaseValidatorThreshold() public {
54-
BridgeValidator testValidator = new BridgeValidator(address(bridge), cfg.partnerValidators);
55+
BridgeValidator testValidator =
56+
new BridgeValidator(address(bridge), cfg.partnerValidators, cfg.partnerValidatorThreshold);
5557
assertEq(testValidator.getBaseThreshold(), type(uint128).max);
5658
}
5759

5860
function test_constructor_setsPartnerValidatorThreshold() public {
59-
BridgeValidator testValidator = new BridgeValidator(address(bridge), cfg.partnerValidators);
60-
assertEq(testValidator.partnerValidatorThreshold(), type(uint256).max);
61+
BridgeValidator testValidator =
62+
new BridgeValidator(address(bridge), cfg.partnerValidators, cfg.partnerValidatorThreshold);
63+
assertEq(testValidator.PARTNER_THRESHOLD(), cfg.partnerValidatorThreshold);
6164
}
6265

6366
//////////////////////////////////////////////////////////////
@@ -186,26 +189,37 @@ contract BridgeValidatorTest is CommonTest {
186189
}
187190

188191
function test_registerMessages_revertsWhenPartnerThresholdNotMet() public {
192+
// Deploy a new BridgeValidator proxy with partner threshold 1
193+
// Use base threshold 1 (minimum allowed) - the test will provide a non-base, non-partner signature
194+
address impl = address(new BridgeValidator(address(bridge), cfg.partnerValidators, 1));
195+
address testValidatorAddr = ERC1967Factory(cfg.erc1967Factory)
196+
.deployAndCall({
197+
implementation: impl,
198+
admin: address(this),
199+
data: abi.encodeCall(BridgeValidator.initialize, (cfg.baseValidators, 1))
200+
});
201+
BridgeValidator testValidator = BridgeValidator(testValidatorAddr);
202+
189203
BridgeValidator.SignedMessage[] memory signedMessages = new BridgeValidator.SignedMessage[](1);
190204
signedMessages[0] = BridgeValidator.SignedMessage({
191205
outgoingMessagePubkey: TEST_OUTGOING_MESSAGE, innerMessageHash: TEST_MESSAGE_HASH_1
192206
});
193207

194-
address testOracle = vm.addr(77);
195-
196-
// Set base threshold to 0 and partner threshold to 1
197-
_mockBaseThreshold(0);
198-
_mockPartnerThreshold(1);
199-
200208
// Calculate message hash
201-
bytes32[] memory finalHashes = _calculateFinalHashes(signedMessages);
209+
bytes32[] memory finalHashes = new bytes32[](1);
210+
uint256 currentNonce = testValidator.nextNonce();
211+
finalHashes[0] = keccak256(
212+
abi.encode(currentNonce, signedMessages[0].outgoingMessagePubkey, signedMessages[0].innerMessageHash)
213+
);
202214
bytes memory signedHash = abi.encode(finalHashes);
203215

204-
// Only oracle signature -> should fail ThresholdNotMet
205-
bytes memory oracleSig = _createSignature(signedHash, 77);
216+
// Only oracle signature (not a base validator, not a partner validator) -> should fail BaseThresholdNotMet
217+
// Actually, since base threshold is 1 and we need at least 1 base validator signature, this will fail
218+
// BaseThresholdNotMet But the test expects PartnerThresholdNotMet. Let's provide a base validator signature but
219+
// no partner signature
220+
bytes memory baseSig = _createSignature(signedHash, 1); // This is a base validator
206221
vm.expectRevert(BridgeValidator.PartnerThresholdNotMet.selector);
207-
vm.prank(testOracle);
208-
bridgeValidator.registerMessages(signedMessages, oracleSig);
222+
testValidator.registerMessages(signedMessages, baseSig);
209223
}
210224

211225
function test_registerMessages_revertsOnEmptySignature() public {
@@ -282,83 +296,99 @@ contract BridgeValidatorTest is CommonTest {
282296
}
283297

284298
function test_registerMessages_revertsOnDuplicatePartnerEntitySigners() public {
285-
_mockPartnerThreshold(1);
299+
// Deploy a new BridgeValidator proxy with partner threshold 1
300+
address impl = address(new BridgeValidator(address(bridge), cfg.partnerValidators, 1));
301+
address testValidatorAddr = ERC1967Factory(cfg.erc1967Factory)
302+
.deployAndCall({
303+
implementation: impl,
304+
admin: address(this),
305+
data: abi.encodeCall(BridgeValidator.initialize, (cfg.baseValidators, cfg.baseSignatureThreshold))
306+
});
307+
BridgeValidator testValidator = BridgeValidator(testValidatorAddr);
286308

287309
// Setup a single partner with two keys that map to the same partner index
288-
MockPartnerValidators pv = MockPartnerValidators(cfg.partnerValidators);
289-
address partnerAddr1 = vm.addr(100);
290-
address partnerAddr2 = vm.addr(101);
291-
pv.addSigner(IPartner.Signer({evmAddress: partnerAddr1, newEvmAddress: partnerAddr2}));
310+
MockPartnerValidators(cfg.partnerValidators)
311+
.addSigner(IPartner.Signer({evmAddress: vm.addr(100), newEvmAddress: vm.addr(101)}));
292312

293313
// Prepare a single message
294314
BridgeValidator.SignedMessage[] memory signedMessages = new BridgeValidator.SignedMessage[](1);
295315
signedMessages[0] = BridgeValidator.SignedMessage({
296316
outgoingMessagePubkey: TEST_OUTGOING_MESSAGE, innerMessageHash: TEST_MESSAGE_HASH_1
297317
});
298-
bytes32[] memory finalHashes = _calculateFinalHashes(signedMessages);
318+
319+
// Calculate final hashes using the test validator's nonce
320+
bytes32[] memory finalHashes = new bytes32[](1);
321+
finalHashes[0] = keccak256(
322+
abi.encode(
323+
testValidator.nextNonce(), signedMessages[0].outgoingMessagePubkey, signedMessages[0].innerMessageHash
324+
)
325+
);
299326
bytes memory signedHash = abi.encode(finalHashes);
300327

301-
// Create signatures from: base validator and both partner keys
302-
address baseAddr = vm.addr(1);
328+
// Create signatures and order by address
303329
bytes memory sigBase = _createSignature(signedHash, 1);
304330
bytes memory sigP1 = _createSignature(signedHash, 100);
305331
bytes memory sigP2 = _createSignature(signedHash, 101);
306332

307-
// Concatenate in strictly ascending address order to satisfy UnsortedSigners check
308-
address a0 = baseAddr;
309-
address a1 = partnerAddr1;
310-
address a2 = partnerAddr2;
311-
bytes memory s0 = sigBase;
312-
bytes memory s1 = sigP1;
313-
bytes memory s2 = sigP2;
314-
315-
bytes memory orderedSigs;
316-
if (a0 < a1) {
317-
if (a1 < a2) {
318-
orderedSigs = abi.encodePacked(s0, s1, s2);
319-
} else if (a0 < a2) {
320-
orderedSigs = abi.encodePacked(s0, s2, s1);
321-
} else {
322-
orderedSigs = abi.encodePacked(s2, s0, s1);
323-
}
324-
} else {
325-
if (a0 < a2) {
326-
orderedSigs = abi.encodePacked(s1, s0, s2);
327-
} else if (a1 < a2) {
328-
orderedSigs = abi.encodePacked(s1, s2, s0);
329-
} else {
330-
orderedSigs = abi.encodePacked(s2, s1, s0);
331-
}
332-
}
333+
// Order signatures by ascending address
334+
address baseAddr = vm.addr(1);
335+
address partnerAddr1 = vm.addr(100);
336+
address partnerAddr2 = vm.addr(101);
337+
bytes memory orderedSigs = _orderSignatures(baseAddr, partnerAddr1, partnerAddr2, sigBase, sigP1, sigP2);
333338

334339
// Expect revert due to duplicate partner entity (same index) detected by the bitmap
335340
vm.expectRevert(BridgeValidator.DuplicateSigner.selector);
336-
bridgeValidator.registerMessages(signedMessages, orderedSigs);
341+
testValidator.registerMessages(signedMessages, orderedSigs);
337342
}
338343

339-
function test_registerMessages_withPartnerValidatorThreshold() public {
340-
// Create a BridgeValidator with partner validator threshold > 0
341-
address testOracle = vm.addr(100);
344+
function _orderSignatures(
345+
address addr0,
346+
address addr1,
347+
address addr2,
348+
bytes memory sig0,
349+
bytes memory sig1,
350+
bytes memory sig2
351+
) private pure returns (bytes memory) {
352+
if (addr0 < addr1) {
353+
if (addr1 < addr2) return abi.encodePacked(sig0, sig1, sig2);
354+
if (addr0 < addr2) return abi.encodePacked(sig0, sig2, sig1);
355+
return abi.encodePacked(sig2, sig0, sig1);
356+
}
357+
if (addr0 < addr2) return abi.encodePacked(sig1, sig0, sig2);
358+
if (addr1 < addr2) return abi.encodePacked(sig1, sig2, sig0);
359+
return abi.encodePacked(sig2, sig1, sig0);
360+
}
342361

343-
// Initialize the base threshold to 0 and partner validator threshold to 1
344-
_mockBaseThreshold(0);
345-
_mockPartnerThreshold(1);
362+
function test_registerMessages_withPartnerValidatorThreshold() public {
363+
// Deploy a new BridgeValidator proxy with partner validator threshold 1
364+
// Use base threshold 1 (minimum allowed) - the test will provide base signature but no partner signature
365+
address impl = address(new BridgeValidator(address(bridge), cfg.partnerValidators, 1));
366+
address testValidatorAddr = ERC1967Factory(cfg.erc1967Factory)
367+
.deployAndCall({
368+
implementation: impl,
369+
admin: address(this),
370+
data: abi.encodeCall(BridgeValidator.initialize, (cfg.baseValidators, 1))
371+
});
372+
BridgeValidator testValidator = BridgeValidator(testValidatorAddr);
346373

347374
BridgeValidator.SignedMessage[] memory signedMessages = new BridgeValidator.SignedMessage[](1);
348375
signedMessages[0] = BridgeValidator.SignedMessage({
349376
outgoingMessagePubkey: TEST_OUTGOING_MESSAGE, innerMessageHash: TEST_MESSAGE_HASH_1
350377
});
351378

352379
// Calculate final hashes with the validator's current nonce
353-
bytes32[] memory finalHashes = _calculateFinalHashes(signedMessages);
380+
bytes32[] memory finalHashes = new bytes32[](1);
381+
uint256 currentNonce = testValidator.nextNonce();
382+
finalHashes[0] = keccak256(
383+
abi.encode(currentNonce, signedMessages[0].outgoingMessagePubkey, signedMessages[0].innerMessageHash)
384+
);
354385
bytes memory signedHash = abi.encode(finalHashes);
355386

356-
// Only BASE_ORACLE signature should fail threshold check
357-
bytes memory oracleSig = _createSignature(signedHash, 100);
387+
// Only base validator signature (no partner signature) should fail partner threshold check
388+
bytes memory baseSig = _createSignature(signedHash, 1); // This is a base validator
358389

359390
vm.expectRevert(BridgeValidator.PartnerThresholdNotMet.selector);
360-
vm.prank(testOracle);
361-
bridgeValidator.registerMessages(signedMessages, oracleSig);
391+
testValidator.registerMessages(signedMessages, baseSig);
362392
}
363393

364394
function test_registerMessages_withBaseAndPartnerSignatures_success() public {
@@ -412,7 +442,7 @@ contract BridgeValidatorTest is CommonTest {
412442
}
413443

414444
vm.expectRevert(VerificationLib.BaseSignerCountTooHigh.selector);
415-
bridgeValidator.initialize(validators, 3, 1);
445+
bridgeValidator.initialize(validators, 3);
416446
}
417447

418448
//////////////////////////////////////////////////////////////
@@ -421,7 +451,7 @@ contract BridgeValidatorTest is CommonTest {
421451

422452
function test_initialize_revertsWhenCalledTwice() public {
423453
vm.expectRevert(Initializable.InvalidInitialization.selector);
424-
bridgeValidator.initialize(cfg.baseValidators, cfg.baseSignatureThreshold, cfg.partnerValidatorThreshold);
454+
bridgeValidator.initialize(cfg.baseValidators, cfg.baseSignatureThreshold);
425455
}
426456

427457
function test_nextNonce_incrementsByBatchLength() public {

0 commit comments

Comments
 (0)