mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
### PR Description Add a comprehensive end-to-end test that validates rewards distribution across the full system (chain → bridge → execution environment). ### Use cases covered - Verify the rewards infrastructure is correctly deployed and reachable. - Detect the end-of-era rewards emission and capture its essential data. - Confirm the cross-chain delivery and execution of the rewards message. - Ensure the rewards registry updates with the new root and can be queried. - Generate per-validator proofs for claiming rewards. - Successfully claim rewards for a validator and validate the payout is reflected. - Prevent a second (double) claim for the same index with a proper rejection. --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
675 lines
23 KiB
Solidity
675 lines
23 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.13;
|
|
|
|
/* solhint-disable func-name-mixedcase */
|
|
|
|
import {Test, console, stdError} from "forge-std/Test.sol";
|
|
|
|
import {AVSDeployer} from "./utils/AVSDeployer.sol";
|
|
import {RewardsRegistry} from "../src/middleware/RewardsRegistry.sol";
|
|
import {IRewardsRegistry, IRewardsRegistryErrors} from "../src/interfaces/IRewardsRegistry.sol";
|
|
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
|
|
|
|
contract RewardsRegistryTest is AVSDeployer {
|
|
address public nonRewardsAgent;
|
|
address public operatorAddress;
|
|
|
|
// Test data
|
|
bytes32 public merkleRoot;
|
|
bytes32 public newMerkleRoot;
|
|
uint256 public operatorPoints;
|
|
uint256 public leafIndex;
|
|
uint256 public numberOfLeaves;
|
|
bytes32[] public validProof;
|
|
bytes32[] public invalidProof;
|
|
|
|
// Events
|
|
event RewardsMerkleRootUpdated(bytes32 oldRoot, bytes32 newRoot, uint256 newRootIndex);
|
|
event RewardsClaimedForIndex(
|
|
address indexed operatorAddress,
|
|
uint256 indexed rootIndex,
|
|
uint256 points,
|
|
uint256 rewardsAmount
|
|
);
|
|
event RewardsBatchClaimedForIndices(
|
|
address indexed operatorAddress,
|
|
uint256[] rootIndices,
|
|
uint256[] points,
|
|
uint256 totalRewardsAmount
|
|
);
|
|
|
|
function setUp() public {
|
|
_deployMockEigenLayerAndAVS();
|
|
|
|
// Set up test addresses
|
|
nonRewardsAgent = address(0x5678);
|
|
operatorAddress = address(0xABCD);
|
|
|
|
// Set up test data
|
|
operatorPoints = 100;
|
|
leafIndex = 0; // Position of our leaf in the tree
|
|
numberOfLeaves = 2; // Simple tree with 2 leaves
|
|
|
|
// For Substrate-compatible Merkle proofs, we need to use SCALE encoding
|
|
// Our leaf (the one we want to prove exists in the tree)
|
|
bytes memory preimage =
|
|
abi.encodePacked(operatorAddress, ScaleCodec.encodeU32(uint32(operatorPoints)));
|
|
bytes32 leaf = keccak256(preimage);
|
|
|
|
// Sibling leaf (another element in the Merkle tree)
|
|
bytes memory siblingPreimage =
|
|
abi.encodePacked(address(0x1234), ScaleCodec.encodeU32(uint32(50)));
|
|
bytes32 siblingLeaf = keccak256(siblingPreimage);
|
|
|
|
// For Substrate positional merkle proof, we construct the root based on position
|
|
// Since leafIndex = 0, our leaf is on the left
|
|
merkleRoot = keccak256(abi.encodePacked(leaf, siblingLeaf));
|
|
|
|
// The proof to verify our leaf is just the sibling leaf
|
|
validProof = new bytes32[](1);
|
|
validProof[0] = siblingLeaf;
|
|
|
|
// For tests that need a second Merkle root
|
|
bytes memory newSiblingPreimage =
|
|
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
|
|
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
|
|
newMerkleRoot = keccak256(abi.encodePacked(leaf, newSiblingLeaf));
|
|
|
|
// An invalid proof
|
|
invalidProof = new bytes32[](1);
|
|
invalidProof[0] = keccak256(abi.encodePacked("wrong sibling"));
|
|
}
|
|
|
|
// Helper to test our proof construction
|
|
function test_verifyProofConstruction() public {
|
|
// Test that our proof construction is valid using the contract's internal validation
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
vm.prank(address(serviceManager));
|
|
// This should not revert if the proof is valid
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Proof verification should succeed"
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Constructor Tests *
|
|
*
|
|
*/
|
|
function test_constructor() public view {
|
|
assertEq(
|
|
rewardsRegistry.avs(), address(serviceManager), "AVS address should be set correctly"
|
|
);
|
|
assertEq(
|
|
rewardsRegistry.rewardsAgent(),
|
|
mockRewardsAgent,
|
|
"Rewards agent address should be set correctly"
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* updateRewardsMerkleRoot Tests *
|
|
*
|
|
*/
|
|
function test_updateRewardsMerkleRoot() public {
|
|
vm.prank(mockRewardsAgent);
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit RewardsMerkleRootUpdated(bytes32(0), merkleRoot, 0);
|
|
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
assertEq(rewardsRegistry.getLatestMerkleRoot(), merkleRoot, "Merkle root should be updated");
|
|
}
|
|
|
|
function test_updateRewardsMerkleRoot_NotRewardsAgent() public {
|
|
vm.prank(nonRewardsAgent);
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyRewardsAgent.selector));
|
|
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
}
|
|
|
|
function test_updateRewardsMerkleRoot_EmitEvent() public {
|
|
// First update
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Second update with expectation of emitting event with correct old and new roots
|
|
vm.prank(mockRewardsAgent);
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit RewardsMerkleRootUpdated(merkleRoot, newMerkleRoot, 1);
|
|
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* setRewardsAgent Tests *
|
|
*
|
|
*/
|
|
function test_setRewardsAgent() public {
|
|
address newRewardsAgent = address(0x9876);
|
|
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.setRewardsAgent(newRewardsAgent);
|
|
|
|
assertEq(rewardsRegistry.rewardsAgent(), newRewardsAgent, "Rewards agent should be updated");
|
|
}
|
|
|
|
function test_setRewardsAgent_NotAVS() public {
|
|
vm.prank(nonRewardsAgent);
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyAVS.selector));
|
|
|
|
rewardsRegistry.setRewardsAgent(address(0x9876));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* claimRewards Tests *
|
|
*
|
|
*/
|
|
function test_claimLatestRewards() public {
|
|
// First update merkle root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Add ETH to contract for rewards
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
uint256 initialBalance = operatorAddress.balance;
|
|
|
|
vm.prank(address(serviceManager));
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints);
|
|
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Verify state changes
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Operator should have claimed from the latest root index"
|
|
);
|
|
assertEq(
|
|
operatorAddress.balance,
|
|
initialBalance + operatorPoints,
|
|
"Operator should receive correct rewards"
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_NotAVS() public {
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(nonRewardsAgent);
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.OnlyAVS.selector));
|
|
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_AlreadyClaimed() public {
|
|
// First update merkle root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Add ETH to contract for rewards
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// First claim succeeds
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Second claim fails
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
|
|
);
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_InvalidProof() public {
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleProof.selector));
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, invalidProof
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_NoMerkleRoot() public {
|
|
// No merkle roots exist yet
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsMerkleRootNotSet.selector)
|
|
);
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_DifferentRoot() public {
|
|
// First merkle root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Add ETH to contract for rewards
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// First claim succeeds
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Update to new merkle root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
// Create a new valid proof for the new root
|
|
bytes32[] memory newProof = new bytes32[](1);
|
|
bytes memory newSiblingPreimage =
|
|
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
|
|
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
|
|
newProof[0] = newSiblingLeaf;
|
|
|
|
// Operator can claim again with new merkle root
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, newProof
|
|
);
|
|
|
|
// Verify both indices are now claimed
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Operator should have claimed from first root index"
|
|
);
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
|
|
"Operator should have claimed from second root index"
|
|
);
|
|
}
|
|
|
|
function test_claimLatestRewards_InsufficientBalance() public {
|
|
// Set merkle root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// No ETH in contract for rewards - ensure contract has 0 balance
|
|
vm.deal(address(rewardsRegistry), 0);
|
|
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsTransferFailed.selector)
|
|
);
|
|
rewardsRegistry.claimLatestRewards(
|
|
operatorAddress, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_receive() public {
|
|
// Test that the contract can receive ETH
|
|
uint256 amount = 1 ether;
|
|
vm.deal(address(this), amount);
|
|
|
|
(bool success,) = address(rewardsRegistry).call{value: amount}("");
|
|
assertTrue(success, "Contract should be able to receive ETH");
|
|
assertEq(address(rewardsRegistry).balance, amount, "Contract balance should increase");
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Merkle Root History Tests *
|
|
*
|
|
*/
|
|
function test_getMerkleRootByIndex() public {
|
|
// Add first root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Add second root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
// Test accessing by index
|
|
assertEq(
|
|
rewardsRegistry.getMerkleRootByIndex(0),
|
|
merkleRoot,
|
|
"First root should be accessible by index 0"
|
|
);
|
|
assertEq(
|
|
rewardsRegistry.getMerkleRootByIndex(1),
|
|
newMerkleRoot,
|
|
"Second root should be accessible by index 1"
|
|
);
|
|
}
|
|
|
|
function test_getMerkleRootByIndex_InvalidIndex() public {
|
|
// Add one root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
// Try to access invalid index
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector)
|
|
);
|
|
rewardsRegistry.getMerkleRootByIndex(1);
|
|
}
|
|
|
|
function test_getLatestMerkleRootIndex() public {
|
|
// Initially should return 0 when no roots exist
|
|
assertEq(
|
|
rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 when no roots exist"
|
|
);
|
|
|
|
// Add first root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 0, "Should return 0 for first root");
|
|
|
|
// Add second root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
assertEq(rewardsRegistry.getLatestMerkleRootIndex(), 1, "Should return 1 for second root");
|
|
}
|
|
|
|
function test_getMerkleRootHistoryLength() public {
|
|
// Initially should be 0
|
|
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 0, "Should be 0 initially");
|
|
|
|
// Add first root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 1, "Should be 1 after first root");
|
|
|
|
// Add second root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
assertEq(rewardsRegistry.getMerkleRootHistoryLength(), 2, "Should be 2 after second root");
|
|
}
|
|
|
|
function test_historyPreservesQuickAccess() public {
|
|
// Add multiple roots
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
// Latest root should be accessible directly without index
|
|
assertEq(
|
|
rewardsRegistry.getLatestMerkleRoot(),
|
|
newMerkleRoot,
|
|
"getLatestMerkleRoot should return latest root"
|
|
);
|
|
|
|
// But we should also be able to access by index
|
|
assertEq(
|
|
rewardsRegistry.getMerkleRootByIndex(1),
|
|
newMerkleRoot,
|
|
"Latest root should also be accessible by index"
|
|
);
|
|
assertEq(
|
|
rewardsRegistry.getMerkleRootByIndex(0),
|
|
merkleRoot,
|
|
"Previous root should be accessible by index"
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Index-based Claim Tests *
|
|
*
|
|
*/
|
|
function test_claimRewards() public {
|
|
// Add multiple roots
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
// Add ETH to contract for rewards
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
uint256 initialBalance = operatorAddress.balance;
|
|
|
|
// Claim from first root (index 0)
|
|
vm.prank(address(serviceManager));
|
|
|
|
vm.expectEmit(true, true, true, true);
|
|
emit RewardsClaimedForIndex(operatorAddress, 0, operatorPoints, operatorPoints);
|
|
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Verify state changes
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Operator should have claimed from index 0"
|
|
);
|
|
assertEq(
|
|
operatorAddress.balance,
|
|
initialBalance + operatorPoints,
|
|
"Operator should receive correct rewards"
|
|
);
|
|
}
|
|
|
|
function test_claimRewards_InvalidIndex() public {
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.InvalidMerkleRootIndex.selector)
|
|
);
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_claimRewards_AlreadyClaimed() public {
|
|
// Add root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// First claim succeeds
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Second claim fails
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
|
|
);
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
}
|
|
|
|
function test_hasClaimedByIndex() public {
|
|
// Add root
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// Initially not claimed
|
|
assertFalse(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Should not have claimed initially"
|
|
);
|
|
|
|
// Claim
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Now claimed
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0), "Should have claimed after claim"
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Batch Claim Tests *
|
|
*
|
|
*/
|
|
function test_claimRewardsBatch() public {
|
|
// Add multiple roots
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// Prepare batch claim data
|
|
uint256[] memory rootIndices = new uint256[](2);
|
|
rootIndices[0] = 0;
|
|
rootIndices[1] = 1;
|
|
|
|
uint256[] memory points = new uint256[](2);
|
|
points[0] = operatorPoints;
|
|
points[1] = operatorPoints;
|
|
|
|
bytes32[][] memory proofs = new bytes32[][](2);
|
|
proofs[0] = validProof;
|
|
|
|
// Create proof for second root
|
|
bytes32[] memory newProof = new bytes32[](1);
|
|
bytes memory newSiblingPreimage =
|
|
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
|
|
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
|
|
newProof[0] = newSiblingLeaf;
|
|
proofs[1] = newProof;
|
|
|
|
uint256 initialBalance = operatorAddress.balance;
|
|
|
|
// Batch claim
|
|
vm.prank(address(serviceManager));
|
|
vm.expectEmit(true, true, true, true);
|
|
emit RewardsBatchClaimedForIndices(operatorAddress, rootIndices, points, operatorPoints * 2);
|
|
uint256[] memory widths = new uint256[](2);
|
|
widths[0] = numberOfLeaves;
|
|
widths[1] = numberOfLeaves;
|
|
uint256[] memory leafIdxs = new uint256[](2);
|
|
leafIdxs[0] = leafIndex;
|
|
leafIdxs[1] = leafIndex;
|
|
rewardsRegistry.claimRewardsBatch(
|
|
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
|
|
);
|
|
|
|
// Verify both indices are claimed
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 0),
|
|
"Should have claimed from index 0"
|
|
);
|
|
assertTrue(
|
|
rewardsRegistry.hasClaimedByIndex(operatorAddress, 1),
|
|
"Should have claimed from index 1"
|
|
);
|
|
|
|
// Verify total rewards received
|
|
assertEq(
|
|
operatorAddress.balance,
|
|
initialBalance + (operatorPoints * 2),
|
|
"Should receive rewards from both claims"
|
|
);
|
|
}
|
|
|
|
function test_claimRewardsBatch_ArrayLengthMismatch() public {
|
|
uint256[] memory rootIndices = new uint256[](2);
|
|
uint256[] memory points = new uint256[](1); // Wrong length
|
|
bytes32[][] memory proofs = new bytes32[][](2);
|
|
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(abi.encodeWithSelector(IRewardsRegistryErrors.ArrayLengthMismatch.selector));
|
|
uint256[] memory widths = new uint256[](2);
|
|
uint256[] memory leafIdxs = new uint256[](2);
|
|
rewardsRegistry.claimRewardsBatch(
|
|
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
|
|
);
|
|
}
|
|
|
|
function test_claimRewardsBatch_PartialClaimFailure() public {
|
|
// Add roots
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(merkleRoot);
|
|
|
|
vm.prank(mockRewardsAgent);
|
|
rewardsRegistry.updateRewardsMerkleRoot(newMerkleRoot);
|
|
|
|
vm.deal(address(rewardsRegistry), 1000 ether);
|
|
|
|
// Claim from index 0 first
|
|
vm.prank(address(serviceManager));
|
|
rewardsRegistry.claimRewards(
|
|
operatorAddress, 0, operatorPoints, numberOfLeaves, leafIndex, validProof
|
|
);
|
|
|
|
// Now try batch claim that includes already claimed index 0
|
|
uint256[] memory rootIndices = new uint256[](2);
|
|
rootIndices[0] = 0; // Already claimed
|
|
rootIndices[1] = 1;
|
|
|
|
uint256[] memory points = new uint256[](2);
|
|
points[0] = operatorPoints;
|
|
points[1] = operatorPoints;
|
|
|
|
bytes32[][] memory proofs = new bytes32[][](2);
|
|
proofs[0] = validProof;
|
|
|
|
bytes32[] memory newProof = new bytes32[](1);
|
|
bytes memory newSiblingPreimage =
|
|
abi.encodePacked(address(0x5678), ScaleCodec.encodeU32(uint32(75)));
|
|
bytes32 newSiblingLeaf = keccak256(newSiblingPreimage);
|
|
newProof[0] = newSiblingLeaf;
|
|
proofs[1] = newProof;
|
|
|
|
// Should fail because index 0 is already claimed
|
|
vm.prank(address(serviceManager));
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IRewardsRegistryErrors.RewardsAlreadyClaimedForIndex.selector)
|
|
);
|
|
uint256[] memory widths = new uint256[](2);
|
|
widths[0] = numberOfLeaves;
|
|
widths[1] = numberOfLeaves;
|
|
uint256[] memory leafIdxs = new uint256[](2);
|
|
leafIdxs[0] = leafIndex;
|
|
leafIdxs[1] = leafIndex;
|
|
rewardsRegistry.claimRewardsBatch(
|
|
operatorAddress, rootIndices, points, widths, leafIdxs, proofs
|
|
);
|
|
}
|
|
}
|