diff --git a/contracts/config/anvil.json b/contracts/config/anvil.json index 6931d469..720568bc 100644 --- a/contracts/config/anvil.json +++ b/contracts/config/anvil.json @@ -39,11 +39,11 @@ "minNumRequiredSignatures": 2, "startBlock": 1, "rewardsMessageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000", - "initialValidators": [ + "initialValidatorHashes": [ "0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7", "0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde" ], - "nextValidators": [ + "nextValidatorHashes": [ "0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7", "0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde" ] diff --git a/contracts/config/example.jsonc b/contracts/config/example.jsonc index 79517734..9c00b8fb 100644 --- a/contracts/config/example.jsonc +++ b/contracts/config/example.jsonc @@ -98,13 +98,13 @@ /// The origin linked to the Rewards Agent, the Agent contract who's allowed to submit /// new reward merkle roots to the RewardsRegistry contract. "rewardsMessageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000", - /// The initial validators for the BEEFY light client. - "initialValidators": [ + /// The initial validator hashes for the BEEFY light client. + "initialValidatorHashes": [ "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199914b9e5506744e80bd0fd33d", "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e" ], - /// The next validators for the BEEFY light client. - "nextValidators": [ + /// The next validator hashes for the BEEFY light client. + "nextValidatorHashes": [ "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199914b9e5506744e80bd0fd33d", "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e" ] diff --git a/contracts/script/deploy/Config.sol b/contracts/script/deploy/Config.sol index 509ef699..68c03f2d 100644 --- a/contracts/script/deploy/Config.sol +++ b/contracts/script/deploy/Config.sol @@ -8,8 +8,8 @@ contract Config { uint256 randaoCommitExpiration; uint256 minNumRequiredSignatures; uint64 startBlock; - bytes32[] initialValidators; - bytes32[] nextValidators; + bytes32[] initialValidatorHashes; + bytes32[] nextValidatorHashes; bytes32 rewardsMessageOrigin; } diff --git a/contracts/script/deploy/DeployLocal.s.sol b/contracts/script/deploy/DeployLocal.s.sol index ac8b9da7..7f69f0af 100644 --- a/contracts/script/deploy/DeployLocal.s.sol +++ b/contracts/script/deploy/DeployLocal.s.sol @@ -608,9 +608,9 @@ contract Deploy is Script, DeployParams, Accounts { ) internal returns (BeefyClient) { // Create validator sets using the MerkleUtils library BeefyClient.ValidatorSet memory validatorSet = - _buildValidatorSet(0, config.initialValidators); + _buildValidatorSet(0, config.initialValidatorHashes); BeefyClient.ValidatorSet memory nextValidatorSet = - _buildValidatorSet(1, config.nextValidators); + _buildValidatorSet(1, config.nextValidatorHashes); // Deploy BeefyClient vm.broadcast(_deployerPrivateKey); diff --git a/contracts/script/deploy/DeployParams.s.sol b/contracts/script/deploy/DeployParams.s.sol index f25c4625..4b4d519b 100644 --- a/contracts/script/deploy/DeployParams.s.sol +++ b/contracts/script/deploy/DeployParams.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.27; import {EmptyContract} from "eigenlayer-contracts/src/test/mocks/EmptyContract.sol"; import {Config} from "./Config.sol"; import {Script} from "forge-std/Script.sol"; +import {TestUtils} from "../../test/utils/TestUtils.sol"; contract DeployParams is Script, Config { function getSnowbridgeConfig() public view returns (SnowbridgeConfig memory) { @@ -27,13 +28,13 @@ contract DeployParams is Script, Config { bool isDevMode = keccak256(abi.encodePacked(vm.envOr("DEV_MODE", string("false")))) == keccak256(abi.encodePacked("true")); if (isDevMode) { - config.initialValidators = _generateMockValidators(10); - config.nextValidators = _generateMockValidators(10); + config.initialValidatorHashes = TestUtils.generateMockValidators(10); + config.nextValidatorHashes = TestUtils.generateMockValidators(10); } else { - config.initialValidators = - _loadValidatorsFromConfig(configJson, ".snowbridge.initialValidators"); - config.nextValidators = - _loadValidatorsFromConfig(configJson, ".snowbridge.nextValidators"); + config.initialValidatorHashes = + _loadValidatorsFromConfig(configJson, ".snowbridge.initialValidatorHashes"); + config.nextValidatorHashes = + _loadValidatorsFromConfig(configJson, ".snowbridge.nextValidatorHashes"); } return config; @@ -194,17 +195,6 @@ contract DeployParams is Script, Config { } } - function _generateMockValidators( - uint256 count - ) internal pure returns (bytes32[] memory) { - // Generate mock validators for testing - bytes32[] memory validators = new bytes32[](count); - for (uint256 i = 0; i < count; i++) { - validators[i] = keccak256(abi.encodePacked("validator", i + 1)); - } - return validators; - } - function _loadValidatorsFromConfig( string memory configJson, string memory path diff --git a/contracts/script/utils/Accounts.sol b/contracts/script/utils/Accounts.sol index 89d089ff..9971cc08 100644 --- a/contracts/script/utils/Accounts.sol +++ b/contracts/script/utils/Accounts.sol @@ -15,9 +15,9 @@ contract Accounts is Script { uint256(0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d) // Second pre-funded account from Anvil ); address internal _operator = vm.addr(_operatorPrivateKey); - bytes32 internal _operatorSolochainAddress = vm.envOr( + address internal _operatorSolochainAddress = vm.envOr( "OPERATOR_SOLOCHAIN_ADDRESS", - bytes32(0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266) // Placeholder + address(0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac) // Alith ); uint256 internal _executorMultisigPrivateKey = vm.envOr( diff --git a/contracts/src/DataHavenServiceManager.sol b/contracts/src/DataHavenServiceManager.sol index b03bef29..aec2f76c 100644 --- a/contracts/src/DataHavenServiceManager.sol +++ b/contracts/src/DataHavenServiceManager.sol @@ -49,7 +49,7 @@ contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager IGatewayV2 private _snowbridgeGateway; /// @inheritdoc IDataHavenServiceManager - mapping(address => bytes32) public validatorEthAddressToSolochainAddress; + mapping(address => address) public validatorEthAddressToSolochainAddress; /// @notice Sets the (immutable) `_registryCoordinator` address constructor( @@ -113,18 +113,14 @@ contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager address[] memory currentValidatorSet = _allocationManager.getMembers(operatorSet); // Build the new validator set message - bytes32[] memory newValidatorSet = new bytes32[](currentValidatorSet.length); + address[] memory newValidatorSet = new address[](currentValidatorSet.length); for (uint256 i = 0; i < currentValidatorSet.length; i++) { newValidatorSet[i] = validatorEthAddressToSolochainAddress[currentValidatorSet[i]]; } DataHavenSnowbridgeMessages.NewValidatorSetPayload memory newValidatorSetPayload = DataHavenSnowbridgeMessages.NewValidatorSetPayload({validators: newValidatorSet}); DataHavenSnowbridgeMessages.NewValidatorSet memory newValidatorSetMessage = - DataHavenSnowbridgeMessages.NewValidatorSet({ - nonce: 0, - topic: bytes32(0), - payload: newValidatorSetPayload - }); + DataHavenSnowbridgeMessages.NewValidatorSet({payload: newValidatorSetPayload}); // Return the encoded message return DataHavenSnowbridgeMessages.scaleEncodeNewValidatorSetMessage(newValidatorSetMessage); @@ -132,7 +128,7 @@ contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager /// @inheritdoc IDataHavenServiceManager function updateSolochainAddressForValidator( - bytes32 solochainAddress + address solochainAddress ) external onlyValidator { // Update the Solochain address for the Validator validatorEthAddressToSolochainAddress[msg.sender] = solochainAddress; @@ -168,8 +164,9 @@ contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager } // In the case of the Validators operator set, expect the data to have the Solochain address of the operator. - // TODO: We should have some sort of validation of this address that validators are setting for themselves. - validatorEthAddressToSolochainAddress[operator] = bytes32(data); + // Require validators to provide 20 bytes addresses. + require(data.length == 20, "Invalid solochain address length"); + validatorEthAddressToSolochainAddress[operator] = address(bytes20(data)); } // Case: BSP else if (operatorSetIds[0] == BSPS_SET_ID) { diff --git a/contracts/src/interfaces/IDataHavenServiceManager.sol b/contracts/src/interfaces/IDataHavenServiceManager.sol index ec1c8a8e..6434be9f 100644 --- a/contracts/src/interfaces/IDataHavenServiceManager.sol +++ b/contracts/src/interfaces/IDataHavenServiceManager.sol @@ -109,7 +109,7 @@ interface IDataHavenServiceManager is */ function validatorEthAddressToSolochainAddress( address validatorAddress - ) external view returns (bytes32); + ) external view returns (address); /** * @notice Initializes the DataHaven Service Manager @@ -152,7 +152,7 @@ interface IDataHavenServiceManager is * in the Validators operator set (operatorSetId = VALIDATORS_SET_ID) */ function updateSolochainAddressForValidator( - bytes32 solochainAddress + address solochainAddress ) external; /** diff --git a/contracts/src/libraries/DataHavenSnowbridgeMessages.sol b/contracts/src/libraries/DataHavenSnowbridgeMessages.sol index 1bf7a126..0f85b691 100644 --- a/contracts/src/libraries/DataHavenSnowbridgeMessages.sol +++ b/contracts/src/libraries/DataHavenSnowbridgeMessages.sol @@ -20,16 +20,8 @@ library DataHavenSnowbridgeMessages { /** * @title New Validator Set Snowbridge Message * @notice A struct representing a new validator set to be sent as a message through Snowbridge. - * This mimics the message format defined in the Snowbridge inbound pallet of the DataHaven - * solochain. - * !IMPORTANT: The fields in this struct are placeholder until we have the actual message format - * ! defined in the DataHaven solochain. */ struct NewValidatorSet { - /// @notice The nonce of the message - uint64 nonce; - /// @notice The topic of the message - bytes32 topic; /// @notice The payload of the message NewValidatorSetPayload payload; } @@ -42,7 +34,7 @@ library DataHavenSnowbridgeMessages { */ struct NewValidatorSetPayload { /// @notice The list of validators in the DataHaven network. - bytes32[] validators; + address[] validators; } /** @@ -53,11 +45,7 @@ library DataHavenSnowbridgeMessages { function scaleEncodeNewValidatorSetMessage( NewValidatorSet memory message ) public pure returns (bytes memory) { - return bytes.concat( - ScaleCodec.encodeU64(message.nonce), - message.topic, - scaleEncodeNewValidatorSetMessagePayload(message.payload) - ); + return scaleEncodeNewValidatorSetMessagePayload(message.payload); } /** @@ -69,9 +57,11 @@ library DataHavenSnowbridgeMessages { NewValidatorSetPayload memory payload ) public pure returns (bytes memory) { uint32 validatorsLen = uint32(payload.validators.length); - bytes32[] memory validatorSet = payload.validators; - // TODO: This shouldn't be hardcoded, but set to the corresponding epoch of this validator set. - uint48 epoch = 0; + address[] memory validatorSet = payload.validators; + + uint64 externalIndex = uint64(0); + + // Flatten the validator set into a single bytes array bytes memory validatorsFlattened; for (uint32 i = 0; i < validatorSet.length; i++) { validatorsFlattened = @@ -84,7 +74,7 @@ library DataHavenSnowbridgeMessages { bytes1(uint8(OutboundCommandV1.ReceiveValidators)), ScaleCodec.encodeCompactU32(validatorsLen), validatorsFlattened, - ScaleCodec.encodeU64(uint64(epoch)) + ScaleCodec.encodeU64(externalIndex) ); } } diff --git a/contracts/test/MessageEncoding.t.sol b/contracts/test/MessageEncoding.t.sol index 8a884c5f..ddce2fea 100644 --- a/contracts/test/MessageEncoding.t.sol +++ b/contracts/test/MessageEncoding.t.sol @@ -4,41 +4,25 @@ pragma solidity ^0.8.27; import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; import {DataHavenSnowbridgeMessages} from "../src/libraries/DataHavenSnowbridgeMessages.sol"; +import {TestUtils} from "./utils/TestUtils.sol"; -// This test is used to encode the receive validators message and log the hex string. -// The hex string is then used to generate the .bin file for the Rust test. -// To generate the .bin file, run: -// forge test --match-test testEncodeReceiveValidatorsMessageAndLog -// Then, copy the hex string and paste it into the Rust test file. -// Then, run: -// cargo test --test decode_receive_validators_message_from_file_correctly -// The test should pass. +// This test is used to encode the receive validators message and print the hex string. +// Run forge test --match-test testEncodeReceiveValidatorsMessage -vvv to see the hex encoded bytes. +// Use the helper script in operator/test/scripts/test_message_encoding.sh to test the encoding/decoding full cycle. contract MessageEncodingTest is Test { - function testEncodeReceiveValidatorsMessageAndLog() public pure { - // Mock Data - - uint64 mockNonce = 12345; - bytes32 mockTopic = 0x123456789012345678901234567890123456789012345678901234567890abcd; + function testEncodeReceiveValidatorsMessage() public pure { + // Use the utility function for consistency + address[] memory mockValidators = TestUtils.generateMockValidatorsAddresses(3); - bytes32[] memory mockValidators = new bytes32[](2); - mockValidators[0] = 0x0000000000000000000000000000000000000000000000000000000000000001; - mockValidators[1] = 0x0000000000000000000000000000000000000000000000000000000000000002; - // uint64 mockEpoch = 0; // This is hardcoded to 0 in the Solidity function's payload part - - DataHavenSnowbridgeMessages.NewValidatorSetPayload memory newValidatorSetPayload = + DataHavenSnowbridgeMessages.NewValidatorSetPayload memory payload = DataHavenSnowbridgeMessages.NewValidatorSetPayload({validators: mockValidators}); - // epoch is implicitly 0 in scaleEncodeNewValidatorSetMessagePayload DataHavenSnowbridgeMessages.NewValidatorSet memory newValidatorSetMessage = - DataHavenSnowbridgeMessages.NewValidatorSet({ - nonce: mockNonce, - topic: mockTopic, - payload: newValidatorSetPayload - }); + DataHavenSnowbridgeMessages.NewValidatorSet({payload: payload}); bytes memory encodedMessage = DataHavenSnowbridgeMessages.scaleEncodeNewValidatorSetMessage(newValidatorSetMessage); - console.log("Encoded NewValidatorSet message (hex):"); - console.logBytes(encodedMessage); // This will print the hex string + console.logBytes(encodedMessage); } } diff --git a/contracts/test/SnowbridgeIntegration.t.sol b/contracts/test/SnowbridgeIntegration.t.sol index 8dce1e19..72bfbbdb 100644 --- a/contracts/test/SnowbridgeIntegration.t.sol +++ b/contracts/test/SnowbridgeIntegration.t.sol @@ -170,13 +170,13 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { function test_sendNewValidatorsSetMessage() public { // Check that the current validators signed as operators have a registered address for the DataHaven solochain. - address[] memory currentValidators = allocationManager.getMembers( + address[] memory currentOperators = allocationManager.getMembers( OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()}) ); - for (uint256 i = 0; i < currentValidators.length; i++) { + for (uint256 i = 0; i < currentOperators.length; i++) { assertEq( - serviceManager.validatorEthAddressToSolochainAddress(currentValidators[i]), - initialValidators[i], + serviceManager.validatorEthAddressToSolochainAddress(currentOperators[i]), + address(uint160(uint256(initialValidatorHashes[i]))), "Validator should have a registered address for the DataHaven solochain" ); } @@ -374,7 +374,7 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { leaves[i] = keccak256(abi.encode(validators[i], points[i])); } - return _buildMerkleProof(leaves, leafIndex); + return MerkleUtils.buildMerkleProof(leaves, leafIndex); } function _buildMessagesMerkleTree( @@ -397,6 +397,6 @@ contract SnowbridgeIntegrationTest is SnowbridgeAndAVSDeployer { leaves[i] = keccak256(abi.encode(messages[i])); } - return _buildMerkleProof(leaves, leafIndex); + return MerkleUtils.buildMerkleProof(leaves, leafIndex); } } diff --git a/contracts/test/utils/SnowbridgeAndAVSDeployer.sol b/contracts/test/utils/SnowbridgeAndAVSDeployer.sol index 3d48fef4..56325045 100644 --- a/contracts/test/utils/SnowbridgeAndAVSDeployer.sol +++ b/contracts/test/utils/SnowbridgeAndAVSDeployer.sol @@ -13,6 +13,7 @@ import {ud60x18} from "snowbridge/lib/prb-math/src/UD60x18.sol"; import {BeefyClient} from "snowbridge/src/BeefyClient.sol"; import {AVSDeployer} from "./AVSDeployer.sol"; import {MerkleUtils} from "../../src/libraries/MerkleUtils.sol"; +import {TestUtils} from "./TestUtils.sol"; import {IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; @@ -47,32 +48,10 @@ contract SnowbridgeAndAVSDeployer is AVSDeployer { address[] public mspsAllowlist; // Snowbridge contracts params - // The addresses of the initial (current) Validators in the DataHaven solochain. - bytes32[] public initialValidators = [ - keccak256(abi.encodePacked("validator1")), - keccak256(abi.encodePacked("validator2")), - keccak256(abi.encodePacked("validator3")), - keccak256(abi.encodePacked("validator4")), - keccak256(abi.encodePacked("validator5")), - keccak256(abi.encodePacked("validator6")), - keccak256(abi.encodePacked("validator7")), - keccak256(abi.encodePacked("validator8")), - keccak256(abi.encodePacked("validator9")), - keccak256(abi.encodePacked("validator10")) - ]; - // The addresses of the next Validators in the DataHaven solochain. - bytes32[] public nextValidators = [ - keccak256(abi.encodePacked("validator11")), - keccak256(abi.encodePacked("validator12")), - keccak256(abi.encodePacked("validator13")), - keccak256(abi.encodePacked("validator14")), - keccak256(abi.encodePacked("validator15")), - keccak256(abi.encodePacked("validator16")), - keccak256(abi.encodePacked("validator17")), - keccak256(abi.encodePacked("validator18")), - keccak256(abi.encodePacked("validator19")), - keccak256(abi.encodePacked("validator20")) - ]; + // The hashes of the initial (current) Validators in the DataHaven solochain. + bytes32[] public initialValidatorHashes; + // The hashes of the next Validators in the DataHaven solochain. + bytes32[] public nextValidatorHashes; // In reality this should be set to MAX_SEED_LOOKAHEAD (4 epochs = 128 blocks/slots) // https://eth2book.info/capella/part3/config/preset/#time-parameters uint256 public constant RANDAO_COMMIT_DELAY = 4; @@ -111,8 +90,13 @@ contract SnowbridgeAndAVSDeployer is AVSDeployer { } function _deployMockSnowbridge() internal { - BeefyClient.ValidatorSet memory validatorSet = _buildValidatorSet(0, initialValidators); - BeefyClient.ValidatorSet memory nextValidatorSet = _buildValidatorSet(1, nextValidators); + // Generate validator arrays using the generator functions + initialValidatorHashes = TestUtils.generateMockValidators(10); + nextValidatorHashes = TestUtils.generateMockValidators(10, 10); + + BeefyClient.ValidatorSet memory validatorSet = _buildValidatorSet(0, initialValidatorHashes); + BeefyClient.ValidatorSet memory nextValidatorSet = + _buildValidatorSet(1, nextValidatorHashes); cheats.prank(regularDeployer); beefyClient = new BeefyClient( @@ -229,7 +213,7 @@ contract SnowbridgeAndAVSDeployer is AVSDeployer { .RegisterParams({ avs: address(serviceManager), operatorSetIds: operatorSetIds, - data: abi.encodePacked(initialValidators[i]) + data: abi.encodePacked(address(uint160(uint256(initialValidatorHashes[i])))) }); allocationManager.registerForOperatorSets(validatorsAllowlist[i], registerParams); cheats.stopPrank(); @@ -240,20 +224,16 @@ contract SnowbridgeAndAVSDeployer is AVSDeployer { function _buildValidatorSet( uint128 id, - bytes32[] memory validators + bytes32[] memory validatorHashes ) internal pure returns (BeefyClient.ValidatorSet memory) { - // Calculate the merkle root from the validators array using the shared library - bytes32 merkleRoot = MerkleUtils.calculateMerkleRoot(validators); + // Calculate the merkle root from the hashed leaves using the shared library + bytes32 merkleRoot = MerkleUtils.calculateMerkleRootUnsorted(validatorHashes); // Create and return the validator set with the calculated merkle root - return - BeefyClient.ValidatorSet({id: id, length: uint128(validators.length), root: merkleRoot}); - } - - function _buildMerkleProof( - bytes32[] memory leaves, - uint256 leafIndex - ) internal pure returns (bytes32[] memory) { - return MerkleUtils.buildMerkleProof(leaves, leafIndex); + return BeefyClient.ValidatorSet({ + id: id, + length: uint128(validatorHashes.length), + root: merkleRoot + }); } } diff --git a/contracts/test/utils/TestUtils.sol b/contracts/test/utils/TestUtils.sol new file mode 100644 index 00000000..950db987 --- /dev/null +++ b/contracts/test/utils/TestUtils.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +/** + * @title TestUtils + * @notice Utility functions for testing DataHaven contracts + */ +library TestUtils { + /** + * @notice Generates mock validator addresses for testing + * @param count Number of validators to generate + * @param startIndex Starting index for validator numbering (defaults to 0) + * @return Array of validator addresses + */ + function generateMockValidatorsAddresses( + uint256 count, + uint256 startIndex + ) internal pure returns (address[] memory) { + address[] memory validators = new address[](count); + for (uint256 i = 0; i < count; i++) { + validators[i] = address(uint160(uint256(bytes32(startIndex + i + 1)))); + } + return validators; + } + + /** + * @notice Generates mock validator addresses for testing (overload with default startIndex = 0) + * @param count Number of validators to generate + * @return Array of validator addresses + */ + function generateMockValidatorsAddresses( + uint256 count + ) internal pure returns (address[] memory) { + return generateMockValidatorsAddresses(count, 0); + } + + /** + * @notice Generates mock validator addresses for testing + * @param count Number of validators to generate + * @param startIndex Starting index for validator numbering (defaults to 0) + * @return Array of validator addresses + */ + function generateMockValidators( + uint256 count, + uint256 startIndex + ) internal pure returns (bytes32[] memory) { + bytes32[] memory validators = new bytes32[](count); + for (uint256 i = 0; i < count; i++) { + validators[i] = bytes32(startIndex + i + 1); + } + return validators; + } + + /** + * @notice Generates mock validator addresses for testing (overload with default startIndex = 0) + * @param count Number of validators to generate + * @return Array of validator addresses + */ + function generateMockValidators( + uint256 count + ) internal pure returns (bytes32[] memory) { + return generateMockValidators(count, 0); + } +} diff --git a/operator/primitives/bridge/src/lib.rs b/operator/primitives/bridge/src/lib.rs index 55c45140..f856138f 100644 --- a/operator/primitives/bridge/src/lib.rs +++ b/operator/primitives/bridge/src/lib.rs @@ -20,8 +20,8 @@ pub struct Payload where T: pallet_external_validators::Config, { - pub message: Message, pub message_id: [u8; 4], + pub message: Message, } #[derive(Encode, Decode)] diff --git a/operator/primitives/bridge/test_data/receive_validators_message.bin b/operator/primitives/bridge/test_data/receive_validators_message.bin index 23741a0e..475b1540 100644 Binary files a/operator/primitives/bridge/test_data/receive_validators_message.bin and b/operator/primitives/bridge/test_data/receive_validators_message.bin differ diff --git a/operator/runtime/stagenet/tests/snowbridge_message_processor.rs b/operator/runtime/stagenet/tests/snowbridge_message_processor.rs new file mode 100644 index 00000000..93f0d64a --- /dev/null +++ b/operator/runtime/stagenet/tests/snowbridge_message_processor.rs @@ -0,0 +1,176 @@ +// Copyright 2025 Moonbeam Foundation. +// This file is part of DataHaven. + +//! Snowbridge message processor static test for DataHaven stagenet runtime +//! +//! Tests for processing Snowbridge messages through DataHaven + +use datahaven_stagenet_runtime::{AccountId, Runtime}; +use dhp_bridge::InboundCommand; +use dhp_bridge::Message; + +use std::fs; + +const MOCK_EXTERNAL_INDEX: u64 = 0u64; + +fn get_expected_validators() -> Vec { + vec![ + hex_to_bytes20("0000000000000000000000000000000000000001").into(), + hex_to_bytes20("0000000000000000000000000000000000000002").into(), + hex_to_bytes20("0000000000000000000000000000000000000003").into(), + ] +} + +fn hex_to_bytes20(hex_str: &str) -> [u8; 20] { + let mut arr = [0u8; 20]; + hex::decode_to_slice(hex_str, &mut arr).expect("Failed to decode hex string to bytes20"); + arr +} + +#[test] +fn test_eigenlayer_message_processor_with_binary_file() { + let binary_file_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../primitives/bridge/test_data/receive_validators_message.bin"); + + // Read the binary file generated by the forge test + let binary_data = fs::read(&binary_file_path).unwrap_or_else(|e| { + panic!( + "Failed to read binary file at {}: {}", + binary_file_path.display(), + e + ); + }); + + println!("๐Ÿ“„ Binary file has {} bytes", binary_data.len()); + + if binary_data.len() <= 0 { + panic!("Binary file corrupted."); + } + + let payload_data = &binary_data[..binary_data.len()]; + // Log the expected structure breakdown + println!( + "๐Ÿ“Š Expected payload structure ({} bytes):", + payload_data.len() + ); + println!(" - EL_MESSAGE_ID: 4 bytes"); + println!(" - Message.V1: 1 byte"); + println!(" - OutboundCommandV1.ReceiveValidators: 1 byte"); + println!(" - validatorsLen (compact): 1 byte"); + println!( + " - validatorsFlattened: {} bytes ({} validators ร— 20 bytes each)", + payload_data.len() - 15, + (payload_data.len() - 15) / 20 + ); + println!(" - externalIndex: 8 bytes"); + + // Log the actual bytes as hex + println!("๐Ÿ” Payload bytes (hex):"); + println!(" Full payload: {}", hex::encode(payload_data)); + + // Split and log each expected chunk + if payload_data.len() >= 4 { + let message_id = &payload_data[0..4]; + println!(" EL_MESSAGE_ID (bytes 0-3): {}", hex::encode(message_id)); + } + + if payload_data.len() >= 5 { + let message_version = &payload_data[4..5]; + println!(" Message.V1 (byte 4): {}", hex::encode(message_version)); + } + + if payload_data.len() >= 6 { + let command = &payload_data[5..6]; + println!( + " OutboundCommandV1.ReceiveValidators (byte 5): {}", + hex::encode(command) + ); + } + + if payload_data.len() >= 7 { + let validators_len_compact = &payload_data[6..7]; + println!( + " validatorsLen compact (byte 6): {}", + hex::encode(validators_len_compact) + ); + } + + // Log validators data + if payload_data.len() >= 7 { + let validators_start = 7; + let validators_end = payload_data.len() - 8; // 8 bytes for external_index at the end + if validators_end > validators_start { + let validators_data = &payload_data[validators_start..validators_end]; + println!( + " validatorsFlattened (bytes {}-{}): {}", + validators_start, + validators_end - 1, + hex::encode(validators_data) + ); + + // Split into individual validator addresses + let validator_count = validators_data.len() / 20; + println!(" Individual validators:"); + for i in 0..validator_count { + let start = i * 20; + let end = start + 20; + let validator = &validators_data[start..end]; + println!(" Validator {}: {}", i, hex::encode(validator)); + } + } + } + + // Log external index + if payload_data.len() >= 8 { + let external_index_start = payload_data.len() - 8; + let external_index = &payload_data[external_index_start..]; + println!( + " externalIndex (last 8 bytes): {}", + hex::encode(external_index) + ); + } + + // Try to decode the payload data directly + let decoded_result = + dhp_bridge::EigenLayerMessageProcessor::::decode_message(payload_data); + + match decoded_result { + Ok(payload) => { + println!("โœ… Successfully decoded payload data"); + + match payload.message { + Message::V1(InboundCommand::ReceiveValidators { + validators, + external_index, + }) => { + println!( + "๐Ÿ“Š Decoded {} validators with external_index: {}", + validators.len(), + external_index + ); + + // Get expected validators that match Solidity TestUtils.generateMockValidators(3) + let expected_validators = get_expected_validators(); + + // Compare decoded validators with expected ones + assert_eq!( + validators, expected_validators, + "Decoded validators from binary file should match MOCK_VALIDATORS_HEX" + ); + assert_eq!( + external_index, MOCK_EXTERNAL_INDEX, + "External index should match" + ); + + println!( + "โœ… Binary file test passed - decoded validators match expected values" + ); + } + } + } + Err(e) => { + println!("โŒ Failed to decode payload data: {:?}", e); + panic!("Failed to decode binary file payload: {:?}", e); + } + } +} diff --git a/operator/runtime/testnet/src/lib.rs b/operator/runtime/testnet/src/lib.rs index 45828616..2a84a674 100644 --- a/operator/runtime/testnet/src/lib.rs +++ b/operator/runtime/testnet/src/lib.rs @@ -1104,93 +1104,3 @@ impl_runtime_apis! { } } } - -#[cfg(test)] -mod tests { - use super::*; - use codec::Encode; - use dhp_bridge::InboundCommand; - use dhp_bridge::{Message, Payload, EL_MESSAGE_ID}; - use snowbridge_inbound_queue_primitives::v2::{Message as SnowbridgeMessage, MessageProcessor}; - use sp_core::H256; - - const MOCK_NONCE: u64 = 12345u64; - const MOCK_VALIDATORS_HEX: [&str; 2] = [ - "0000000000000000000000000000000000000000000000000000000000000001", - "0000000000000000000000000000000000000000000000000000000000000002", - ]; - const MOCK_EXTERNAL_INDEX: u64 = 0u64; - - fn hex_to_bytes32(hex_str: &str) -> [u8; 32] { - let mut arr = [0u8; 32]; - hex::decode_to_slice(hex_str, &mut arr).expect("Failed to decode hex string to bytes32"); - arr - } - - #[test] - fn test_eigenlayer_message_processor() { - // Create mock validators - let validators: Vec = MOCK_VALIDATORS_HEX - .iter() - .map(|s| hex_to_bytes32(s).into()) - .collect(); - - // Create a mock message payload - let message = Message::V1(dhp_bridge::InboundCommand::ReceiveValidators { - validators: validators.clone(), - external_index: MOCK_EXTERNAL_INDEX, - }); - - let payload = Payload:: { - message, - message_id: EL_MESSAGE_ID, - }; - - // Create a mock Snowbridge message - let snowbridge_message = SnowbridgeMessage { - xcm: snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload.encode()), - gateway: H160::default(), - nonce: MOCK_NONCE, - origin: H160::default(), - assets: vec![], - claimer: None, - value: 0u128, - execution_fee: 0u128, - relayer_fee: 0u128, - }; - - // Test can_process_message - let mock_account = H256::from_slice(&[1u8; 32]); - assert!( - dhp_bridge::EigenLayerMessageProcessor::::can_process_message( - &mock_account, - &snowbridge_message - ), - "Message should be processable" - ); - - let payload = match &snowbridge_message.xcm { - snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload) => payload, - _ => panic!("Invalid Message"), - }; - - let decoded_result = - dhp_bridge::EigenLayerMessageProcessor::::decode_message(payload.as_slice()); - - let message = if let Ok(payload) = decoded_result { - payload.message - } else { - panic!("unable to parse the message payload"); - }; - - match message { - Message::V1(InboundCommand::ReceiveValidators { - validators, - external_index, - }) => { - assert_eq!(validators, validators); - assert_eq!(external_index, MOCK_EXTERNAL_INDEX); - } - } - } -} diff --git a/operator/test/scripts/test_message_encoding.sh b/operator/test/scripts/test_message_encoding.sh new file mode 100755 index 00000000..4db7a723 --- /dev/null +++ b/operator/test/scripts/test_message_encoding.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# This script is used to test the message encoding for the snowbridge message processor. +# You can pass the -v flag to run the test with a more verbose output. + +set -e + +echo "๐Ÿš€ Starting message encoding test..." + +cd ../../../ +echo "๐Ÿ“ Changed to contracts directory" + +cd contracts +echo "๐Ÿ”จ Compiling contracts with forge..." +forge build --force +echo "โœ… Contracts compiled successfully" + +mkdir -p ../operator/primitives/bridge/test_data +echo "๐Ÿ“‚ Create test_data directory if doesn't exist" + +echo "๐Ÿ”ง Running forge test to generate ReceiveValidators encoded message..." +forge test --match-test testEncodeReceiveValidatorsMessage -vvv | grep -A 10 "Logs:" | grep -E "0x[a-fA-F0-9]+" | tail -n 1 | sed 's/0x//' | xxd -r -p > ../operator/primitives/bridge/test_data/receive_validators_message.bin +echo "๐Ÿ’พ Generated receive_validators_message.bin file" + +cd ../operator +echo "๐Ÿ“ Changed to operator directory" + +echo "๐Ÿงช Running cargo test for snowbridge message processor..." +cargo test --test snowbridge_message_processor ${1:+-v -- --nocapture} +echo "โœ… Cargo test completed successfully!" \ No newline at end of file diff --git a/test/cli/handlers/common/datahaven.ts b/test/cli/handlers/common/datahaven.ts index a669989c..a41376d6 100644 --- a/test/cli/handlers/common/datahaven.ts +++ b/test/cli/handlers/common/datahaven.ts @@ -175,8 +175,8 @@ export const setupDataHavenValidatorConfig = async ( configJson.snowbridge = {}; } - configJson.snowbridge.initialValidators = authorityHashes; - configJson.snowbridge.nextValidators = authorityHashes; + configJson.snowbridge.initialValidatorHashes = authorityHashes; + configJson.snowbridge.nextValidatorHashes = authorityHashes; fs.writeFileSync(configFilePath, JSON.stringify(configJson, null, 2)); logger.success(`DataHaven authority hashes updated in: ${configFilePath}`); diff --git a/test/configs/validator-set.json b/test/configs/validator-set.json index e0f65a96..9741da52 100644 --- a/test/configs/validator-set.json +++ b/test/configs/validator-set.json @@ -3,28 +3,28 @@ { "publicKey": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "privateKey": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - "solochainAddress": "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + "solochainAddress": "0xE04CC55ebEE1cBCE552f250e85c57B70B2E2625b" }, { "publicKey": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "privateKey": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", - "solochainAddress": "0x00000000000000000000000070997970C51812dc3A010C7d01b50e0d17dc79C8" + "solochainAddress": "0x25451A4de12dcCc2D166922fA938E900fCc4ED24" }, { "publicKey": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "privateKey": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", - "solochainAddress": "0x0000000000000000000000003C44CdDdB6a900fa2b585dd299e03d12FA4293BC" + "solochainAddress": "0x9e250513a9f2f287d0cdd636dac97b2405098bd5" }, { "publicKey": "0x90F79bf6EB2c4f870365E785982E1f101E93b906", "privateKey": "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", - "solochainAddress": "0x00000000000000000000000090F79bf6EB2c4f870365E785982E1f101E93b906" + "solochainAddress": "0x6babb2c13fa50cbae5c7d256ff4e3064e3110dab" }, { "publicKey": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", "privateKey": "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", - "solochainAddress": "0x00000000000000000000000015d34AAf54267DB7D7c367839AAf71A00a2C6A65" + "solochainAddress": "0x20e02a8ec521095e82b0cafa8ac5b22854eec7e8" } ], - "notes": "This is a placeholder validator set based on Anvil funded accounts. The solochainAddress fields are dummy placeholders derived from the Ethereum addresses and should be replaced with actual DataHaven chain addresses when full E2E flow is done." + "notes": "This validator set maps the first five anvil funded addresses with the Ethereum-compatible addresses of Substrate validators. Check conversion in compressedPubKeyToEthereumAddress() in cli/handlers/common/datahaven.ts" } \ No newline at end of file diff --git a/test/contract-bindings/generated.ts b/test/contract-bindings/generated.ts index d98da537..d1965921 100644 --- a/test/contract-bindings/generated.ts +++ b/test/contract-bindings/generated.ts @@ -2639,7 +2639,7 @@ export const dataHavenServiceManagerAbi = [ { type: 'function', inputs: [ - { name: 'solochainAddress', internalType: 'bytes32', type: 'bytes32' }, + { name: 'solochainAddress', internalType: 'address', type: 'address' }, ], name: 'updateSolochainAddressForValidator', outputs: [], @@ -2649,7 +2649,7 @@ export const dataHavenServiceManagerAbi = [ type: 'function', inputs: [{ name: '', internalType: 'address', type: 'address' }], name: 'validatorEthAddressToSolochainAddress', - outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + outputs: [{ name: '', internalType: 'address', type: 'address' }], stateMutability: 'view', }, {