mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
Fix: 🏗️ Message encoding / decoding (#113)
## Summary of changes - We decided to remove the topics and nonce from the massage encoding since we don't use them (original commit:ee2a3f2fd4). - Besides, we already have a nonce at the Snowbridge message levelf4ab5c2b2e/operator/primitives/snowbridge/inbound-queue/src/v2/message.rs (L105)- I had to recreate the static test for _encoding_ (happens in [DataHavenSnowbridgeMessages.sol](d12d40634f/contracts/src/libraries/DataHavenSnowbridgeMessages.sol) ) / _decoding_ (happens in [operator/primitives/bridge/src/lib.rs)](f9f9cc65fe/operator/primitives/bridge/src/lib.rs). Now it matches the current structure. The idea is that now we can test that we don't break the decoding in followup refactoring. - Fixes a problem with EigenLayer validator addresses. In all our contracts we were using `bytes32` to refer to a Solochain validator address. But on our Substrate change we actually expect AccountId20, so only 20 bytes. This was causing the decoding to fail. - I opted for the minimal change that would be to take the right-most 20 bytes to send that to our chain. But we might want aswell to limit our EigenLayer contracts to be only 20 bytes long. @ahmadkaouk showcase this [here](92a34c273c) - Adds a bash script to run the static test. The test will compile the contracts, run the encoding test, compile the operator, and run the decoding test. This saves a huge amount of time since we don't need to run the full e2e setup. The way of running it is the following: ```bash cd operator/test/scripts ./test_message_encoding.sh ``` - As a consequence of this PR, the execution relayer now works properly. EDIT: > [!IMPORTANT] **We decided to use 20-byte addresses in our contracts**. So what is stated above is not valid anymore. The change implies that the mapping from Ethereum addresses to bytes32 addresses now it's a mapping as follows:dd3ba99ac0/contracts/src/DataHavenServiceManager.sol (L51-L52)I've updated helper functions, tests, etc to be compliant with this change. The execution relayer and beefy relayer look stable now. --------- Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com> Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
This commit is contained in:
parent
01962656d7
commit
e9fc4f271f
21 changed files with 354 additions and 233 deletions
|
|
@ -39,11 +39,11 @@
|
|||
"minNumRequiredSignatures": 2,
|
||||
"startBlock": 1,
|
||||
"rewardsMessageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"initialValidators": [
|
||||
"initialValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
],
|
||||
"nextValidators": [
|
||||
"nextValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
"0xf68aec7304bf37f340dae2ea20fb5271ee28a3128812b84a615da4789e458bde"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ contract Config {
|
|||
uint256 randaoCommitExpiration;
|
||||
uint256 minNumRequiredSignatures;
|
||||
uint64 startBlock;
|
||||
bytes32[] initialValidators;
|
||||
bytes32[] nextValidators;
|
||||
bytes32[] initialValidatorHashes;
|
||||
bytes32[] nextValidatorHashes;
|
||||
bytes32 rewardsMessageOrigin;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
contracts/test/utils/TestUtils.sol
Normal file
64
contracts/test/utils/TestUtils.sol
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ pub struct Payload<T>
|
|||
where
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
pub message: Message<T>,
|
||||
pub message_id: [u8; 4],
|
||||
pub message: Message<T>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
|
|
|
|||
Binary file not shown.
176
operator/runtime/stagenet/tests/snowbridge_message_processor.rs
Normal file
176
operator/runtime/stagenet/tests/snowbridge_message_processor.rs
Normal file
|
|
@ -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<AccountId> {
|
||||
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::<Runtime>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AccountId> = 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::<Runtime> {
|
||||
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::<Runtime>::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::<Runtime>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
30
operator/test/scripts/test_message_encoding.sh
Executable file
30
operator/test/scripts/test_message_encoding.sh
Executable file
|
|
@ -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!"
|
||||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue