Skip to content

Commit 47c75a5

Browse files
Test checkpoint tracker
1 parent 502209c commit 47c75a5

4 files changed

Lines changed: 215 additions & 124 deletions

File tree

test/CheckpointTracker.t.sol

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {MockInbox} from "./mocks/MockInbox.sol";
5+
import {MockVerifier} from "./mocks/MockVerifier.sol";
6+
import "forge-std/Test.sol";
7+
import {CheckpointTracker} from "src/protocol/CheckpointTracker.sol";
8+
import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol";
9+
import {SignalService} from "src/protocol/SignalService.sol";
10+
11+
contract CheckpointTrackerTest is Test {
12+
CheckpointTracker tracker;
13+
MockInbox inbox;
14+
MockVerifier verifier;
15+
SignalService signalService;
16+
address proverManager = _randomAddress("proverManager");
17+
bytes32 genesis = keccak256(abi.encode("genesis"));
18+
19+
ICheckpointTracker.Checkpoint start;
20+
ICheckpointTracker.Checkpoint end;
21+
bytes proof = new bytes(0);
22+
23+
function setUp() public {
24+
inbox = new MockInbox();
25+
verifier = new MockVerifier();
26+
signalService = new SignalService();
27+
tracker =
28+
new CheckpointTracker(genesis, address(inbox), address(verifier), proverManager, address(signalService));
29+
}
30+
31+
function test_constructor_shouldRevertWithZeroGenesis() public {
32+
vm.expectRevert("genesis checkpoint commitment cannot be 0");
33+
new CheckpointTracker(bytes32(0), address(inbox), address(verifier), proverManager, address(signalService));
34+
}
35+
36+
function test_constructor_shouldSetExternalContracts() public view {
37+
assertEq(address(tracker.inbox()), address(inbox), "Did not set inbox");
38+
assertEq(address(tracker.verifier()), address(verifier), "Did not set verifier");
39+
assertEq(address(tracker.commitmentStore()), address(signalService), "Did not set commitment store");
40+
assertEq(address(tracker.proverManager()), proverManager, "Did not set prover manager");
41+
}
42+
43+
function test_constructor_shouldSetProvenPublicationId() public view {
44+
assertEq(tracker.provenPublicationId(), inbox.getNextPublicationId() - 1, "Proven publication set incorrectly");
45+
}
46+
47+
function test_constructor_shouldSaveGenesisCommitment() public view {
48+
bytes32 savedCommitment = signalService.commitmentAt(address(tracker), tracker.provenPublicationId());
49+
assertEq(savedCommitment, genesis, "Did not save genesis");
50+
}
51+
52+
function test_proveTransition_shouldRevertIfNotCalledByProverManager() public {
53+
_constructValidTransition();
54+
vm.expectRevert("Only the prover manager can call this function");
55+
tracker.proveTransition(start, end, proof);
56+
}
57+
58+
function test_proveTransition_shouldRevertIfStartCommitmentIsZero() public {
59+
_constructValidTransition();
60+
start.commitment = bytes32(0);
61+
vm.expectRevert("Start checkpoint commitment cannot be 0");
62+
vm.prank(proverManager);
63+
tracker.proveTransition(start, end, proof);
64+
}
65+
66+
function test_proveTransition_shouldRevertIfEndCommitmentIsZero() public {
67+
_constructValidTransition();
68+
end.commitment = bytes32(0);
69+
vm.expectRevert("End checkpoint commitment cannot be 0");
70+
vm.prank(proverManager);
71+
tracker.proveTransition(start, end, proof);
72+
}
73+
74+
function test_proveTransition_shouldRevertIfStartPublicationIsNotProven() public {
75+
_constructValidTransition();
76+
start.publicationId = tracker.provenPublicationId() + 1;
77+
vm.expectRevert("Start publication must precede latest proven checkpoint");
78+
vm.prank(proverManager);
79+
tracker.proveTransition(start, end, proof);
80+
}
81+
82+
function test_proveTransition_shouldNotRevertIfAllPublicationsAreDelayed() public {
83+
_constructValidTransition();
84+
end.totalDelayedPublications = start.totalDelayedPublications + (end.publicationId - start.publicationId);
85+
vm.expectRevert("Number of delayed publications cannot be greater than the total number of publications", 0);
86+
vm.prank(proverManager);
87+
tracker.proveTransition(start, end, proof);
88+
}
89+
90+
function test_proveTransition_shouldRevertIfDelayedPublicationsExceedPublications() public {
91+
_constructValidTransition();
92+
end.totalDelayedPublications = start.totalDelayedPublications + (end.publicationId - start.publicationId) + 1;
93+
vm.expectRevert("Number of delayed publications cannot be greater than the total number of publications");
94+
vm.prank(proverManager);
95+
tracker.proveTransition(start, end, proof);
96+
}
97+
98+
function test_proveTransition_shouldRevertWithUnknownEndPublication() public {
99+
_constructValidTransition();
100+
end.publicationId = inbox.getNextPublicationId();
101+
vm.expectRevert("End publication does not exist");
102+
vm.prank(proverManager);
103+
tracker.proveTransition(start, end, proof);
104+
}
105+
106+
function test_proveTransition_shouldRevertWithInvalidProof() public {
107+
_constructValidTransition();
108+
verifier.setValidity(false);
109+
vm.expectRevert();
110+
vm.prank(proverManager);
111+
tracker.proveTransition(start, end, proof);
112+
}
113+
114+
function test_proveTransition_shouldUpdateProvenPublication() public {
115+
_constructValidTransition();
116+
vm.prank(proverManager);
117+
tracker.proveTransition(start, end, proof);
118+
assertEq(tracker.provenPublicationId(), end.publicationId, "Proven publication not end publication");
119+
}
120+
121+
function test_proveTransition_shouldSaveEndCommitment() public {
122+
_constructValidTransition();
123+
vm.prank(proverManager);
124+
tracker.proveTransition(start, end, proof);
125+
bytes32 savedCommitment = signalService.commitmentAt(address(tracker), tracker.provenPublicationId());
126+
assertEq(savedCommitment, end.commitment, "Did not save end commitment");
127+
}
128+
129+
function test_proveTransition_shouldEmitEvent() public {
130+
_constructValidTransition();
131+
vm.prank(proverManager);
132+
vm.expectEmit();
133+
emit ICheckpointTracker.CommitmentSaved(end.publicationId, end.commitment);
134+
tracker.proveTransition(start, end, proof);
135+
}
136+
137+
function test_shouldReturnPublicationCounts() public {
138+
_constructValidTransition();
139+
vm.prank(proverManager);
140+
(uint256 nPublications, uint256 nDelayed) = tracker.proveTransition(start, end, proof);
141+
// assume there is no front-running proof that advances provenPublicationId
142+
assertEq(nPublications, end.publicationId - start.publicationId, "Incorrect publication count");
143+
uint256 expectedDelayedCount = end.totalDelayedPublications - start.totalDelayedPublications;
144+
assertEq(nDelayed, expectedDelayedCount, "Incorrect delayed publication count");
145+
}
146+
147+
function test_shouldAllowOverlappingProof() public {
148+
// Submit the first proof
149+
_constructValidTransition();
150+
vm.prank(proverManager);
151+
tracker.proveTransition(start, end, proof);
152+
assertEq(tracker.provenPublicationId(), end.publicationId, "Proven publication not set correctly");
153+
154+
// The second proof has the same start but it covers 6 more publications, 1 of them delayed
155+
inbox.publishMultiple(5);
156+
end.publicationId += 6;
157+
end.totalDelayedPublications += 1;
158+
end.commitment = keccak256(abi.encode("newEnd"));
159+
160+
vm.prank(proverManager);
161+
(uint256 nPublications, uint256 nDelayed) = tracker.proveTransition(start, end, proof);
162+
163+
assertEq(tracker.provenPublicationId(), end.publicationId, "Proven publication not updated correctly");
164+
assertEq(nPublications, 6, "Number of publications does not match extension");
165+
assertEq(nDelayed, 1, "Number of delayed publications does not match extension");
166+
}
167+
168+
// an arbitrary transition
169+
function _constructValidTransition() public {
170+
// ensure there are some publications to prover
171+
inbox.publishMultiple(10);
172+
173+
start.publicationId = tracker.provenPublicationId();
174+
start.commitment = keccak256(abi.encode("start"));
175+
start.totalDelayedPublications = 0;
176+
177+
end.publicationId = start.publicationId + 5;
178+
end.commitment = keccak256(abi.encode("end"));
179+
end.totalDelayedPublications = start.totalDelayedPublications + 2;
180+
}
181+
182+
function _randomAddress(string memory name) internal pure returns (address) {
183+
return address(uint160(uint256(keccak256(abi.encode(_domainSeparator(), name)))));
184+
}
185+
186+
function _domainSeparator() internal pure returns (bytes32) {
187+
return keccak256("CheckpointTracker");
188+
}
189+
}

test/CheckpointTracker.t.sol.outdated

Lines changed: 0 additions & 117 deletions
This file was deleted.

test/mocks/MockInbox.sol

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {IInbox} from "src/protocol/IInbox.sol";
55

66
/// @notice Mock implementation of IInbox for testing
77
contract MockInbox is IInbox {
8+
// Start at 1 so there is always a previous hash
9+
uint256 private numPublications = 1;
10+
811
mapping(bytes32 headerHash => bool) private isInvalid;
912

1013
error NotImplemented();
@@ -13,19 +16,26 @@ contract MockInbox is IInbox {
1316
revert NotImplemented();
1417
}
1518

16-
function getPublicationHash(uint256) external pure returns (bytes32) {
17-
revert NotImplemented();
19+
function getPublicationHash(uint256 id) external view returns (bytes32) {
20+
if (id >= numPublications) return 0;
21+
return keccak256(abi.encode("MockInbox", id));
1822
}
1923

20-
function getNextPublicationId() external pure returns (uint256) {
21-
revert NotImplemented();
24+
function getNextPublicationId() external view returns (uint256) {
25+
return numPublications;
2226
}
2327

2428
function validateHeader(PublicationHeader calldata header) external view returns (bool) {
2529
return !isInvalid[keccak256(abi.encode(header))];
2630
}
2731

32+
// Mock functionality
33+
2834
function setInvalidHeader(PublicationHeader calldata header) external {
2935
isInvalid[keccak256(abi.encode(header))] = true;
3036
}
37+
38+
function publishMultiple(uint256 count) external {
39+
numPublications += count;
40+
}
3141
}
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ pragma solidity ^0.8.28;
33

44
import {IVerifier} from "src/protocol/IVerifier.sol";
55

6-
contract NullVerifier is IVerifier {
7-
/// @notice Do nothing. All proofs are accepted.
6+
contract MockVerifier is IVerifier {
7+
bool private validProof = true;
8+
9+
error VerificationFailed();
10+
811
function verifyProof(
912
bytes32, /* startPublicationHash */
1013
bytes32, /* endPublicationHash */
@@ -13,5 +16,11 @@ contract NullVerifier is IVerifier {
1316
bytes32, /* intermediateCheckPoint */
1417
uint256, /* numDelayedPublications */
1518
bytes calldata /* proof */
16-
) external {}
19+
) external view {
20+
require(validProof, VerificationFailed());
21+
}
22+
23+
function setValidity(bool isValid) external {
24+
validProof = isValid;
25+
}
1726
}

0 commit comments

Comments
 (0)