Skip to content

Commit 1b2bfd6

Browse files
tbwebb22grasphoper
andauthored
chore: HubPool tests migration (#1237)
* add Arbitrum_Adapter.t.sol Signed-off-by: Ihor Farion <ihor@umaproject.org> * refactor Signed-off-by: Ihor Farion <ihor@umaproject.org> * add more asserts to the new test Signed-off-by: Ihor Farion <ihor@umaproject.org> * address incorrect constants usage Signed-off-by: Ihor Farion <ihor@umaproject.org> * initial HubPoolAdmin test - WIP Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * change expectEmit format Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add mockOptimisticOracle to HubPoolFixture Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * clean up MockOptimisticOracle Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * Clean up HubPool Admin test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add DisputeRootBundle test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * replicate SkinnyOptimisticOracle functionality Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * fix all disputeRootBundle tests Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * clean up ExecuteRootBundle tests Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add HubPool LP test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * Add liquidity provision fees test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * LiquidityProvisionHaircut test WIP Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add PooledTokenSync test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add missing assertion Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add ProposeRootBundle test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * Add HubPool ProtocolFees test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * make HubPoolStore tests more thorough Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * consolidate repeated functions and constants into HubPoolTestBase Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * fix Multicall test to expect return data on revert Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * update foundry test script Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * undo claude.md changes Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * MegaETH chain name Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * arbitrum adapter moved to chain-adapters folder Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * add HubPool WETH test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * fix calculation bug Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * MegaETH chain name Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * delete hardhat HubPool tests Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * move constructSimpleTree from deleted executeRootBundle test into BondToken test Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * clean up HubPoolTestBase Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * clean up tests Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * remove unused functions from HubPoolTestBase Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * update deployed-addresses Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * reformat deployed addresses Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * check actual token name and symbol Signed-off-by: Taylor Webb <tbwebb22@gmail.com> * fix test profile name in claude.md Signed-off-by: Taylor Webb <tbwebb22@gmail.com> --------- Signed-off-by: Ihor Farion <ihor@umaproject.org> Signed-off-by: Taylor Webb <tbwebb22@gmail.com> Co-authored-by: Ihor Farion <ihor@umaproject.org>
1 parent 5462b68 commit 1b2bfd6

25 files changed

+3032
-1441
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ yarn build-evm # Hardhat
3333

3434
# Run tests
3535
yarn test-evm-foundry # Foundry local tests (recommended)
36-
FOUNDRY_PROFILE=local forge test # Same as above
36+
FOUNDRY_PROFILE=local-test forge test # Same as above
3737
yarn test-evm-hardhat # Hardhat tests (legacy)
3838

3939
# Run specific Foundry tests

test/evm/foundry/local/HubPoolStore.t.sol

Lines changed: 242 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,262 @@
22
pragma solidity ^0.8.0;
33

44
import { Test } from "forge-std/Test.sol";
5+
import { Vm } from "forge-std/Vm.sol";
56
import { HubPoolStore } from "../../../../contracts/chain-adapters/Universal_Adapter.sol";
7+
import { HubPoolInterface } from "../../../../contracts/interfaces/HubPoolInterface.sol";
8+
9+
/// @dev Simple mock that implements only what HubPoolStore needs from HubPool
10+
contract MockHubPoolForStore {
11+
HubPoolInterface.RootBundle public rootBundleProposal;
12+
13+
function setPendingRootBundle(HubPoolInterface.RootBundle memory _rootBundle) external {
14+
rootBundleProposal = _rootBundle;
15+
}
16+
}
617

718
contract HubPoolStoreTest is Test {
819
HubPoolStore store;
9-
10-
address hubPool;
20+
MockHubPoolForStore hubPool;
1121

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

25+
event StoredCallData(address indexed target, bytes data, uint256 indexed nonce);
26+
1527
function setUp() public {
16-
hubPool = vm.addr(1);
17-
store = new HubPoolStore(hubPool);
28+
hubPool = new MockHubPoolForStore();
29+
store = new HubPoolStore(address(hubPool));
30+
}
31+
32+
// ============ Constructor Tests ============
33+
34+
function testConstructor() public view {
35+
assertEq(store.hubPool(), address(hubPool));
36+
}
37+
38+
// ============ Access Control Tests ============
39+
40+
function testOnlyHubPoolCanStore() public {
41+
vm.expectRevert(HubPoolStore.NotHubPool.selector);
42+
store.storeRelayMessageCalldata(target, message, true);
43+
}
44+
45+
function testOnlyHubPoolCanStore_arbitraryAddress() public {
46+
vm.prank(makeAddr("randomCaller"));
47+
vm.expectRevert(HubPoolStore.NotHubPool.selector);
48+
store.storeRelayMessageCalldata(target, message, true);
1849
}
1950

20-
function testStoreRelayMessageCalldata() public {
21-
// Only hub pool can call this function.
22-
vm.expectRevert();
51+
// ============ Admin Message Tests (isAdminSender = true) ============
52+
53+
function testStoreAdminMessage() public {
54+
vm.prank(address(hubPool));
55+
store.storeRelayMessageCalldata(target, message, true);
56+
57+
// First admin message should use nonce 0
58+
assertEq(store.relayMessageCallData(0), keccak256(abi.encode(target, message)));
59+
}
60+
61+
function testStoreAdminMessage_emitsEvent() public {
62+
vm.prank(address(hubPool));
63+
vm.expectEmit(true, true, true, true);
64+
emit StoredCallData(target, message, 0);
2365
store.storeRelayMessageCalldata(target, message, true);
66+
}
67+
68+
function testStoreAdminMessage_incrementsNonce() public {
69+
vm.startPrank(address(hubPool));
2470

25-
vm.prank(hubPool);
71+
// First call uses nonce 0
2672
store.storeRelayMessageCalldata(target, message, true);
2773
assertEq(store.relayMessageCallData(0), keccak256(abi.encode(target, message)));
74+
75+
// Second call uses nonce 1
76+
bytes memory message2 = abi.encode("message2");
77+
store.storeRelayMessageCalldata(target, message2, true);
78+
assertEq(store.relayMessageCallData(1), keccak256(abi.encode(target, message2)));
79+
80+
// Third call uses nonce 2
81+
address target2 = makeAddr("target2");
82+
store.storeRelayMessageCalldata(target2, message, true);
83+
assertEq(store.relayMessageCallData(2), keccak256(abi.encode(target2, message)));
84+
85+
vm.stopPrank();
86+
}
87+
88+
function testStoreAdminMessage_includesTargetInHash() public {
89+
vm.startPrank(address(hubPool));
90+
91+
// Same message but different targets should produce different hashes
92+
address target1 = makeAddr("target1");
93+
address target2 = makeAddr("target2");
94+
95+
store.storeRelayMessageCalldata(target1, message, true);
96+
store.storeRelayMessageCalldata(target2, message, true);
97+
98+
bytes32 hash1 = store.relayMessageCallData(0);
99+
bytes32 hash2 = store.relayMessageCallData(1);
100+
101+
// Verify the hashes are different
102+
assertTrue(hash1 != hash2);
103+
104+
// Verify each hash matches expected
105+
assertEq(hash1, keccak256(abi.encode(target1, message)));
106+
assertEq(hash2, keccak256(abi.encode(target2, message)));
107+
108+
vm.stopPrank();
109+
}
110+
111+
// ============ Non-Admin Message Tests (isAdminSender = false) ============
112+
113+
function testStoreNonAdminMessage() public {
114+
uint32 challengePeriodTimestamp = uint32(block.timestamp);
115+
hubPool.setPendingRootBundle(
116+
HubPoolInterface.RootBundle({
117+
challengePeriodEndTimestamp: challengePeriodTimestamp,
118+
poolRebalanceRoot: bytes32("poolRoot"),
119+
relayerRefundRoot: bytes32("refundRoot"),
120+
slowRelayRoot: bytes32("slowRoot"),
121+
claimedBitMap: 0,
122+
proposer: address(0),
123+
unclaimedPoolRebalanceLeafCount: 0
124+
})
125+
);
126+
127+
vm.prank(address(hubPool));
128+
store.storeRelayMessageCalldata(target, message, false);
129+
130+
// Non-admin message uses challengePeriodEndTimestamp as nonce
131+
// Target is overwritten to address(0) in the hash
132+
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));
133+
}
134+
135+
function testStoreNonAdminMessage_emitsEvent() public {
136+
uint32 challengePeriodTimestamp = uint32(block.timestamp);
137+
hubPool.setPendingRootBundle(
138+
HubPoolInterface.RootBundle({
139+
challengePeriodEndTimestamp: challengePeriodTimestamp,
140+
poolRebalanceRoot: bytes32(0),
141+
relayerRefundRoot: bytes32(0),
142+
slowRelayRoot: bytes32(0),
143+
claimedBitMap: 0,
144+
proposer: address(0),
145+
unclaimedPoolRebalanceLeafCount: 0
146+
})
147+
);
148+
149+
vm.prank(address(hubPool));
150+
// Event should have address(0) as target for non-admin messages
151+
vm.expectEmit(true, true, true, true);
152+
emit StoredCallData(address(0), message, challengePeriodTimestamp);
153+
store.storeRelayMessageCalldata(target, message, false);
154+
}
155+
156+
function testStoreNonAdminMessage_duplicateDoesNotOverwrite() public {
157+
uint32 challengePeriodTimestamp = uint32(block.timestamp);
158+
hubPool.setPendingRootBundle(
159+
HubPoolInterface.RootBundle({
160+
challengePeriodEndTimestamp: challengePeriodTimestamp,
161+
poolRebalanceRoot: bytes32(0),
162+
relayerRefundRoot: bytes32(0),
163+
slowRelayRoot: bytes32(0),
164+
claimedBitMap: 0,
165+
proposer: address(0),
166+
unclaimedPoolRebalanceLeafCount: 0
167+
})
168+
);
169+
170+
vm.startPrank(address(hubPool));
171+
172+
// First call stores data
173+
vm.recordLogs();
174+
store.storeRelayMessageCalldata(target, message, false);
175+
176+
// Second call with same challengePeriodTimestamp should NOT emit event
177+
// because data is already stored
178+
store.storeRelayMessageCalldata(target, message, false);
179+
180+
Vm.Log[] memory logs = vm.getRecordedLogs();
181+
// Only one StoredCallData event should be emitted
182+
uint256 storedCallDataCount = 0;
183+
for (uint256 i = 0; i < logs.length; i++) {
184+
if (logs[i].topics[0] == keccak256("StoredCallData(address,bytes,uint256)")) {
185+
storedCallDataCount++;
186+
}
187+
}
188+
assertEq(storedCallDataCount, 1);
189+
190+
// Data should still be the original
191+
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));
192+
193+
vm.stopPrank();
194+
}
195+
196+
function testStoreNonAdminMessage_differentTimestampCreatesNewEntry() public {
197+
uint32 timestamp1 = uint32(block.timestamp);
198+
hubPool.setPendingRootBundle(
199+
HubPoolInterface.RootBundle({
200+
challengePeriodEndTimestamp: timestamp1,
201+
poolRebalanceRoot: bytes32(0),
202+
relayerRefundRoot: bytes32(0),
203+
slowRelayRoot: bytes32(0),
204+
claimedBitMap: 0,
205+
proposer: address(0),
206+
unclaimedPoolRebalanceLeafCount: 0
207+
})
208+
);
209+
210+
vm.startPrank(address(hubPool));
211+
212+
// Store first message
213+
store.storeRelayMessageCalldata(target, message, false);
214+
assertEq(store.relayMessageCallData(timestamp1), keccak256(abi.encode(address(0), message)));
215+
216+
// Update to new timestamp
217+
uint32 timestamp2 = timestamp1 + 1;
218+
vm.warp(timestamp2);
219+
hubPool.setPendingRootBundle(
220+
HubPoolInterface.RootBundle({
221+
challengePeriodEndTimestamp: timestamp2,
222+
poolRebalanceRoot: bytes32(0),
223+
relayerRefundRoot: bytes32(0),
224+
slowRelayRoot: bytes32(0),
225+
claimedBitMap: 0,
226+
proposer: address(0),
227+
unclaimedPoolRebalanceLeafCount: 0
228+
})
229+
);
230+
231+
// Store second message with different data
232+
bytes memory message2 = abi.encode("different message");
233+
store.storeRelayMessageCalldata(target, message2, false);
234+
235+
// Both entries should exist
236+
assertEq(store.relayMessageCallData(timestamp1), keccak256(abi.encode(address(0), message)));
237+
assertEq(store.relayMessageCallData(timestamp2), keccak256(abi.encode(address(0), message2)));
238+
239+
vm.stopPrank();
240+
}
241+
242+
function testStoreNonAdminMessage_targetIgnoredInHash() public {
243+
uint32 challengePeriodTimestamp = uint32(block.timestamp);
244+
hubPool.setPendingRootBundle(
245+
HubPoolInterface.RootBundle({
246+
challengePeriodEndTimestamp: challengePeriodTimestamp,
247+
poolRebalanceRoot: bytes32(0),
248+
relayerRefundRoot: bytes32(0),
249+
slowRelayRoot: bytes32(0),
250+
claimedBitMap: 0,
251+
proposer: address(0),
252+
unclaimedPoolRebalanceLeafCount: 0
253+
})
254+
);
255+
256+
vm.prank(address(hubPool));
257+
// Even though we pass a target, it should be replaced with address(0)
258+
store.storeRelayMessageCalldata(makeAddr("someTarget"), message, false);
259+
260+
// Verify the hash uses address(0) not the passed target
261+
assertEq(store.relayMessageCallData(challengePeriodTimestamp), keccak256(abi.encode(address(0), message)));
28262
}
29263
}

0 commit comments

Comments
 (0)