Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4a66758
add Arbitrum_Adapter.t.sol
grasphoper Dec 12, 2025
d565f07
refactor
grasphoper Dec 12, 2025
4ae0e6d
add more asserts to the new test
grasphoper Dec 12, 2025
62d70a0
Merge branch 'master' into if/move-tests-to-foundry
grasphoper Jan 7, 2026
46ab8ab
address incorrect constants usage
grasphoper Jan 8, 2026
25b48d6
initial HubPoolAdmin test - WIP
tbwebb22 Jan 9, 2026
32a879d
change expectEmit format
tbwebb22 Jan 9, 2026
8e26eb1
add mockOptimisticOracle to HubPoolFixture
tbwebb22 Jan 12, 2026
4eed1f7
clean up MockOptimisticOracle
tbwebb22 Jan 12, 2026
9e4dc2e
Clean up HubPool Admin test
tbwebb22 Jan 12, 2026
3dcba73
add DisputeRootBundle test
tbwebb22 Jan 12, 2026
b7a1686
replicate SkinnyOptimisticOracle functionality
tbwebb22 Jan 12, 2026
1a72b86
fix all disputeRootBundle tests
tbwebb22 Jan 12, 2026
f52d94e
clean up ExecuteRootBundle tests
tbwebb22 Jan 12, 2026
540c7e0
add HubPool LP test
tbwebb22 Jan 12, 2026
ea58ce6
Add liquidity provision fees test
tbwebb22 Jan 13, 2026
3fc337d
LiquidityProvisionHaircut test WIP
tbwebb22 Jan 13, 2026
984ece5
add PooledTokenSync test
tbwebb22 Jan 14, 2026
d689332
add missing assertion
tbwebb22 Jan 14, 2026
74fbf8b
add ProposeRootBundle test
tbwebb22 Jan 14, 2026
0ec73df
Add HubPool ProtocolFees test
tbwebb22 Jan 14, 2026
c6503e1
make HubPoolStore tests more thorough
tbwebb22 Jan 14, 2026
b17f2b8
consolidate repeated functions and constants into HubPoolTestBase
tbwebb22 Jan 15, 2026
49ee84d
fix Multicall test to expect return data on revert
tbwebb22 Jan 15, 2026
d740f87
Merge remote-tracking branch 'origin/master' into taylor/tests-migration
tbwebb22 Jan 15, 2026
7d425bf
update foundry test script
tbwebb22 Jan 15, 2026
13fa1b6
Merge branch 'master' into taylor/tests-migration
tbwebb22 Jan 20, 2026
fc5ec96
undo claude.md changes
tbwebb22 Jan 20, 2026
6d9d827
MegaETH chain name
tbwebb22 Jan 20, 2026
ce8b243
arbitrum adapter moved to chain-adapters folder
tbwebb22 Jan 20, 2026
e869abe
add HubPool WETH test
tbwebb22 Jan 20, 2026
56601dd
fix calculation bug
tbwebb22 Jan 20, 2026
e0c3b0e
MegaETH chain name
tbwebb22 Jan 20, 2026
8aa3945
delete hardhat HubPool tests
tbwebb22 Jan 20, 2026
1807f67
Merge branch 'master' into taylor/tests-migration
tbwebb22 Jan 20, 2026
148cc55
move constructSimpleTree from deleted executeRootBundle test into Bon…
tbwebb22 Jan 20, 2026
888e4e0
clean up HubPoolTestBase
tbwebb22 Jan 20, 2026
ad557e6
clean up tests
tbwebb22 Jan 20, 2026
e75433e
remove unused functions from HubPoolTestBase
tbwebb22 Jan 21, 2026
db132d2
Merge remote-tracking branch 'origin/master' into taylor/tests-migration
tbwebb22 Jan 21, 2026
1b7189c
update deployed-addresses
tbwebb22 Jan 21, 2026
d2c3324
reformat deployed addresses
tbwebb22 Jan 22, 2026
ab70069
check actual token name and symbol
tbwebb22 Jan 22, 2026
bd1bda0
fix test profile name in claude.md
tbwebb22 Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 242 additions & 8 deletions test/evm/foundry/local/HubPoolStore.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,262 @@
pragma solidity ^0.8.0;

import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import { HubPoolStore } from "../../../../contracts/chain-adapters/Universal_Adapter.sol";
import { HubPoolInterface } from "../../../../contracts/interfaces/HubPoolInterface.sol";

/// @dev Simple mock that implements only what HubPoolStore needs from HubPool
contract MockHubPoolForStore {
HubPoolInterface.RootBundle public rootBundleProposal;

function setPendingRootBundle(HubPoolInterface.RootBundle memory _rootBundle) external {
rootBundleProposal = _rootBundle;
}
}

contract HubPoolStoreTest is Test {
HubPoolStore store;

address hubPool;
MockHubPoolForStore hubPool;

bytes message = abi.encode("message");
address target = makeAddr("target");

event StoredCallData(address indexed target, bytes data, uint256 indexed nonce);

function setUp() public {
hubPool = vm.addr(1);
store = new HubPoolStore(hubPool);
hubPool = new MockHubPoolForStore();
store = new HubPoolStore(address(hubPool));
}

// ============ Constructor Tests ============

function testConstructor() public view {
assertEq(store.hubPool(), address(hubPool));
}

// ============ Access Control Tests ============

function testOnlyHubPoolCanStore() public {
vm.expectRevert(HubPoolStore.NotHubPool.selector);
store.storeRelayMessageCalldata(target, message, true);
}

function testOnlyHubPoolCanStore_arbitraryAddress() public {
vm.prank(makeAddr("randomCaller"));
vm.expectRevert(HubPoolStore.NotHubPool.selector);
store.storeRelayMessageCalldata(target, message, true);
}

function testStoreRelayMessageCalldata() public {
// Only hub pool can call this function.
vm.expectRevert();
// ============ Admin Message Tests (isAdminSender = true) ============

function testStoreAdminMessage() public {
vm.prank(address(hubPool));
store.storeRelayMessageCalldata(target, message, true);

// First admin message should use nonce 0
assertEq(store.relayMessageCallData(0), keccak256(abi.encode(target, message)));
}

function testStoreAdminMessage_emitsEvent() public {
vm.prank(address(hubPool));
vm.expectEmit(true, true, true, true);
emit StoredCallData(target, message, 0);
store.storeRelayMessageCalldata(target, message, true);
}

function testStoreAdminMessage_incrementsNonce() public {
vm.startPrank(address(hubPool));

vm.prank(hubPool);
// First call uses nonce 0
store.storeRelayMessageCalldata(target, message, true);
assertEq(store.relayMessageCallData(0), keccak256(abi.encode(target, message)));

// Second call uses nonce 1
bytes memory message2 = abi.encode("message2");
store.storeRelayMessageCalldata(target, message2, true);
assertEq(store.relayMessageCallData(1), keccak256(abi.encode(target, message2)));

// Third call uses nonce 2
address target2 = makeAddr("target2");
store.storeRelayMessageCalldata(target2, message, true);
assertEq(store.relayMessageCallData(2), keccak256(abi.encode(target2, message)));

vm.stopPrank();
}

function testStoreAdminMessage_includesTargetInHash() public {
vm.startPrank(address(hubPool));

// Same message but different targets should produce different hashes
address target1 = makeAddr("target1");
address target2 = makeAddr("target2");

store.storeRelayMessageCalldata(target1, message, true);
store.storeRelayMessageCalldata(target2, message, true);

bytes32 hash1 = store.relayMessageCallData(0);
bytes32 hash2 = store.relayMessageCallData(1);

// Verify the hashes are different
assertTrue(hash1 != hash2);

// Verify each hash matches expected
assertEq(hash1, keccak256(abi.encode(target1, message)));
assertEq(hash2, keccak256(abi.encode(target2, message)));

vm.stopPrank();
}

// ============ Non-Admin Message Tests (isAdminSender = false) ============

function testStoreNonAdminMessage() public {
uint32 challengePeriodTimestamp = uint32(block.timestamp);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: challengePeriodTimestamp,
poolRebalanceRoot: bytes32("poolRoot"),
relayerRefundRoot: bytes32("refundRoot"),
slowRelayRoot: bytes32("slowRoot"),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

vm.prank(address(hubPool));
store.storeRelayMessageCalldata(target, message, false);

// Non-admin message uses challengePeriodEndTimestamp as nonce
// Target is overwritten to address(0) in the hash
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));
}

function testStoreNonAdminMessage_emitsEvent() public {
uint32 challengePeriodTimestamp = uint32(block.timestamp);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: challengePeriodTimestamp,
poolRebalanceRoot: bytes32(0),
relayerRefundRoot: bytes32(0),
slowRelayRoot: bytes32(0),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

vm.prank(address(hubPool));
// Event should have address(0) as target for non-admin messages
vm.expectEmit(true, true, true, true);
emit StoredCallData(address(0), message, challengePeriodTimestamp);
store.storeRelayMessageCalldata(target, message, false);
}

function testStoreNonAdminMessage_duplicateDoesNotOverwrite() public {
uint32 challengePeriodTimestamp = uint32(block.timestamp);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: challengePeriodTimestamp,
poolRebalanceRoot: bytes32(0),
relayerRefundRoot: bytes32(0),
slowRelayRoot: bytes32(0),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

vm.startPrank(address(hubPool));

// First call stores data
vm.recordLogs();
store.storeRelayMessageCalldata(target, message, false);

// Second call with same challengePeriodTimestamp should NOT emit event
// because data is already stored
store.storeRelayMessageCalldata(target, message, false);

Vm.Log[] memory logs = vm.getRecordedLogs();
// Only one StoredCallData event should be emitted
uint256 storedCallDataCount = 0;
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics[0] == keccak256("StoredCallData(address,bytes,uint256)")) {
storedCallDataCount++;
}
}
assertEq(storedCallDataCount, 1);

// Data should still be the original
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));

vm.stopPrank();
}

function testStoreNonAdminMessage_differentTimestampCreatesNewEntry() public {
uint32 timestamp1 = uint32(block.timestamp);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: timestamp1,
poolRebalanceRoot: bytes32(0),
relayerRefundRoot: bytes32(0),
slowRelayRoot: bytes32(0),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

vm.startPrank(address(hubPool));

// Store first message
store.storeRelayMessageCalldata(target, message, false);
assertEq(store.relayMessageCallData(timestamp1), keccak256(abi.encode(address(0), message)));

// Update to new timestamp
uint32 timestamp2 = timestamp1 + 1;
vm.warp(timestamp2);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: timestamp2,
poolRebalanceRoot: bytes32(0),
relayerRefundRoot: bytes32(0),
slowRelayRoot: bytes32(0),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

// Store second message with different data
bytes memory message2 = abi.encode("different message");
store.storeRelayMessageCalldata(target, message2, false);

// Both entries should exist
assertEq(store.relayMessageCallData(timestamp1), keccak256(abi.encode(address(0), message)));
assertEq(store.relayMessageCallData(timestamp2), keccak256(abi.encode(address(0), message2)));

vm.stopPrank();
}

function testStoreNonAdminMessage_targetIgnoredInHash() public {
uint32 challengePeriodTimestamp = uint32(block.timestamp);
hubPool.setPendingRootBundle(
HubPoolInterface.RootBundle({
challengePeriodEndTimestamp: challengePeriodTimestamp,
poolRebalanceRoot: bytes32(0),
relayerRefundRoot: bytes32(0),
slowRelayRoot: bytes32(0),
claimedBitMap: 0,
proposer: address(0),
unclaimedPoolRebalanceLeafCount: 0
})
);

vm.prank(address(hubPool));
// Even though we pass a target, it should be replaced with address(0)
store.storeRelayMessageCalldata(makeAddr("someTarget"), message, false);

// Verify the hash uses address(0) not the passed target
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));
}
}
Loading
Loading