Skip to content

Commit 8c43092

Browse files
jannikluhnclaude
andcommitted
feat(dkg): add IDKGContract interface and implement it on DKGContract
Introduce IDKGContract covering DKGContract's full public surface (the Phase enum, the seven external/public functions, and external-view getters for every public state variable) so other contracts -- notably KeyperSetManager in the next slice -- can call into a DKG contract without importing the concrete type and re-creating the KeyperSetManager <-> DKGContract import cycle. Key decisions: - The Phase enum moves from DKGContract to IDKGContract; DKGContract inherits it (references stay unqualified). DKGContract.t.sol now uses IDKGContract.Phase. - keyperSetManager()/keyBroadcastContract() in the interface return address, matching this repo's existing intf/ convention (IKeyperSet, IKeyperSetManager return address, never concrete types) and the comparison the next slice needs (IDKGContract(dkg).keyperSetManager() != address(this)). Returning concrete types would force IDKGContract to import KeyperSetManager, reinstating the very cycle this interface exists to break. To satisfy the address-typed getters while keeping internal calls typed, the two contract-typed immutables are renamed to internal _keyperSetManager/_keyBroadcastContract with explicit address getters. ABI is unchanged (the auto-getters already returned address), so Go bindings are unaffected. Files: src/common/intf/IDKGContract.sol (new), src/common/DKGContract.sol, test/DKGContract.t.sol. Verification: forge build clean (no new warnings vs baseline), forge test 131/131 pass, ABI diff confirms the interface covers exactly DKGContract's 14 public functions with matching return types. Notes for next iteration: unblocks 002-validate-dkg-address-on- registration. e2e suite (test-dkg-happy-path, test-dkg-offline-recovery) not run -- mise absent and its bootstrap is blocked by the sandbox classifier, as in every prior commit in this PRD area; this slice is Solidity-only with no ABI change and is fully covered by the forge suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8a3180f commit 8c43092

3 files changed

Lines changed: 119 additions & 38 deletions

File tree

src/common/DKGContract.sol

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@ pragma solidity ^0.8.22;
33

44
import "./KeyperSetManager.sol";
55
import "./KeyBroadcastContract.sol";
6+
import "./intf/IDKGContract.sol";
67

7-
contract DKGContract {
8-
enum Phase {
9-
None,
10-
Dealing,
11-
Accusing,
12-
Apologizing,
13-
Finalizing
14-
}
15-
8+
contract DKGContract is IDKGContract {
169
error WrongPhase();
1710
error NotKeyperAtIndex();
1811
error AlreadySucceeded();
@@ -55,8 +48,11 @@ contract DKGContract {
5548

5649
uint64 public immutable PHASE_LENGTH;
5750
uint64 public immutable DKG_LEAD_LENGTH;
58-
KeyperSetManager public immutable keyperSetManager;
59-
KeyBroadcastContract public immutable keyBroadcastContract;
51+
// Held as concrete types for internal calls; exposed as address through the
52+
// IDKGContract getters below so the interface stays free of concrete-contract
53+
// imports (which would re-create the KeyperSetManager <-> DKGContract cycle).
54+
KeyperSetManager internal immutable _keyperSetManager;
55+
KeyBroadcastContract internal immutable _keyBroadcastContract;
6056

6157
// Set to true once a DKG Instance for Keyper Set Index k has reached the
6258
// success-vote threshold. Never reset — gates the success-triggering logic
@@ -75,12 +71,20 @@ contract DKGContract {
7571
) {
7672
PHASE_LENGTH = phaseLength;
7773
DKG_LEAD_LENGTH = dkgLeadLength;
78-
keyperSetManager = KeyperSetManager(keyperSetManagerAddress);
79-
keyBroadcastContract = KeyBroadcastContract(
74+
_keyperSetManager = KeyperSetManager(keyperSetManagerAddress);
75+
_keyBroadcastContract = KeyBroadcastContract(
8076
keyBroadcastContractAddress
8177
);
8278
}
8379

80+
function keyperSetManager() external view returns (address) {
81+
return address(_keyperSetManager);
82+
}
83+
84+
function keyBroadcastContract() external view returns (address) {
85+
return address(_keyBroadcastContract);
86+
}
87+
8488
function cycleLength() public view returns (uint64) {
8589
return 4 * PHASE_LENGTH;
8690
}
@@ -89,7 +93,7 @@ contract DKGContract {
8993
uint64 keyperSetIndex,
9094
uint64 retryCounter
9195
) public view returns (int256) {
92-
uint64 activationBlock = keyperSetManager.getKeyperSetActivationBlock(
96+
uint64 activationBlock = _keyperSetManager.getKeyperSetActivationBlock(
9397
keyperSetIndex
9498
);
9599
return
@@ -121,7 +125,7 @@ contract DKGContract {
121125
}
122126

123127
function _checkDKGContract(uint64 keyperSetIndex) internal view {
124-
address keyperSetAddress = keyperSetManager.getKeyperSetAddress(
128+
address keyperSetAddress = _keyperSetManager.getKeyperSetAddress(
125129
keyperSetIndex
126130
);
127131
if (KeyperSet(keyperSetAddress).getDKGContract() != address(this)) {
@@ -133,7 +137,7 @@ contract DKGContract {
133137
uint64 keyperSetIndex,
134138
uint64 keyperIndex
135139
) internal view {
136-
address keyperSetAddress = keyperSetManager.getKeyperSetAddress(
140+
address keyperSetAddress = _keyperSetManager.getKeyperSetAddress(
137141
keyperSetIndex
138142
);
139143
if (KeyperSet(keyperSetAddress).getMember(keyperIndex) != msg.sender) {
@@ -246,7 +250,7 @@ contract DKGContract {
246250
// emit the event above, but do not re-trigger success once it has
247251
// already been recorded.
248252
if (!succeeded[keyperSetIndex]) {
249-
address keyperSetAddress = keyperSetManager.getKeyperSetAddress(
253+
address keyperSetAddress = _keyperSetManager.getKeyperSetAddress(
250254
keyperSetIndex
251255
);
252256
uint64 threshold = KeyperSet(keyperSetAddress).getThreshold();
@@ -256,7 +260,7 @@ contract DKGContract {
256260
// been broadcast (or the publisher is not authorized), the DKG
257261
// outcome is still considered successful.
258262
try
259-
keyBroadcastContract.broadcastEonKey(
263+
_keyBroadcastContract.broadcastEonKey(
260264
keyperSetIndex,
261265
eonPublicKey
262266
)

src/common/intf/IDKGContract.sol

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
interface IDKGContract {
5+
enum Phase {
6+
None,
7+
Dealing,
8+
Accusing,
9+
Apologizing,
10+
Finalizing
11+
}
12+
13+
function PHASE_LENGTH() external view returns (uint64);
14+
15+
function DKG_LEAD_LENGTH() external view returns (uint64);
16+
17+
function keyperSetManager() external view returns (address);
18+
19+
function keyBroadcastContract() external view returns (address);
20+
21+
function succeeded(uint64 keyperSetIndex) external view returns (bool);
22+
23+
function voteCount(
24+
uint64 keyperSetIndex,
25+
uint64 retryCounter,
26+
bytes32 keyHash
27+
) external view returns (uint64);
28+
29+
function hasVoted(
30+
uint64 keyperSetIndex,
31+
uint64 retryCounter,
32+
address voter
33+
) external view returns (bool);
34+
35+
function cycleLength() external view returns (uint64);
36+
37+
function dkgStart(
38+
uint64 keyperSetIndex,
39+
uint64 retryCounter
40+
) external view returns (int256);
41+
42+
function currentPhase(
43+
uint64 keyperSetIndex,
44+
uint64 retryCounter
45+
) external view returns (Phase);
46+
47+
function submitDealing(
48+
uint64 keyperSetIndex,
49+
uint64 retryCounter,
50+
uint64 keyperIndex,
51+
bytes calldata commitment,
52+
bytes[] calldata polyEvals
53+
) external;
54+
55+
function submitAccusation(
56+
uint64 keyperSetIndex,
57+
uint64 retryCounter,
58+
uint64 keyperIndex,
59+
uint64[] calldata accusedIndices
60+
) external;
61+
62+
function submitApology(
63+
uint64 keyperSetIndex,
64+
uint64 retryCounter,
65+
uint64 keyperIndex,
66+
uint64[] calldata accuserIndices,
67+
bytes[] calldata polyEvalData
68+
) external;
69+
70+
function submitSuccessVote(
71+
uint64 keyperSetIndex,
72+
uint64 retryCounter,
73+
uint64 keyperIndex,
74+
bytes calldata eonPublicKey
75+
) external;
76+
}

test/DKGContract.t.sol

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "../src/common/DKGContract.sol";
66
import "../src/common/KeyperSet.sol";
77
import "../src/common/KeyperSetManager.sol";
88
import "../src/common/KeyBroadcastContract.sol";
9+
import "../src/common/intf/IDKGContract.sol";
910

1011
contract DKGContractTest is Test {
1112
DKGContract public dkgContract;
@@ -106,7 +107,7 @@ contract DKGContractTest is Test {
106107
function _assertPhase(
107108
uint64 blockNumber,
108109
uint64 retryCounter,
109-
DKGContract.Phase expected
110+
IDKGContract.Phase expected
110111
) internal {
111112
vm.roll(blockNumber);
112113
assertEq(
@@ -124,15 +125,15 @@ contract DKGContractTest is Test {
124125
uint64 constant DKG_START_R0 = ACTIVATION_BLOCK_0 - DKG_LEAD_LENGTH;
125126

126127
function testCurrentPhaseBeforeDkgStartIsNone() public {
127-
_assertPhase(DKG_START_R0 - 1, 0, DKGContract.Phase.None);
128+
_assertPhase(DKG_START_R0 - 1, 0, IDKGContract.Phase.None);
128129
}
129130

130131
function testCurrentPhaseDealingBoundaries() public {
131-
_assertPhase(DKG_START_R0, 0, DKGContract.Phase.Dealing);
132+
_assertPhase(DKG_START_R0, 0, IDKGContract.Phase.Dealing);
132133
_assertPhase(
133134
DKG_START_R0 + PHASE_LENGTH - 1,
134135
0,
135-
DKGContract.Phase.Dealing
136+
IDKGContract.Phase.Dealing
136137
);
137138
}
138139

@@ -141,19 +142,19 @@ contract DKGContractTest is Test {
141142
_assertPhase(
142143
DKG_START_R0 + PHASE_LENGTH - 1,
143144
0,
144-
DKGContract.Phase.Dealing
145+
IDKGContract.Phase.Dealing
145146
);
146147
// first block of Accusing
147148
_assertPhase(
148149
DKG_START_R0 + PHASE_LENGTH,
149150
0,
150-
DKGContract.Phase.Accusing
151+
IDKGContract.Phase.Accusing
151152
);
152153
// last block of Accusing
153154
_assertPhase(
154155
DKG_START_R0 + 2 * PHASE_LENGTH - 1,
155156
0,
156-
DKGContract.Phase.Accusing
157+
IDKGContract.Phase.Accusing
157158
);
158159
}
159160

@@ -162,19 +163,19 @@ contract DKGContractTest is Test {
162163
_assertPhase(
163164
DKG_START_R0 + 2 * PHASE_LENGTH - 1,
164165
0,
165-
DKGContract.Phase.Accusing
166+
IDKGContract.Phase.Accusing
166167
);
167168
// first block of Apologizing
168169
_assertPhase(
169170
DKG_START_R0 + 2 * PHASE_LENGTH,
170171
0,
171-
DKGContract.Phase.Apologizing
172+
IDKGContract.Phase.Apologizing
172173
);
173174
// last block of Apologizing
174175
_assertPhase(
175176
DKG_START_R0 + 3 * PHASE_LENGTH - 1,
176177
0,
177-
DKGContract.Phase.Apologizing
178+
IDKGContract.Phase.Apologizing
178179
);
179180
}
180181

@@ -183,19 +184,19 @@ contract DKGContractTest is Test {
183184
_assertPhase(
184185
DKG_START_R0 + 3 * PHASE_LENGTH - 1,
185186
0,
186-
DKGContract.Phase.Apologizing
187+
IDKGContract.Phase.Apologizing
187188
);
188189
// first block of Finalizing
189190
_assertPhase(
190191
DKG_START_R0 + 3 * PHASE_LENGTH,
191192
0,
192-
DKGContract.Phase.Finalizing
193+
IDKGContract.Phase.Finalizing
193194
);
194195
// last block of Finalizing
195196
_assertPhase(
196197
DKG_START_R0 + 4 * PHASE_LENGTH - 1,
197198
0,
198-
DKGContract.Phase.Finalizing
199+
IDKGContract.Phase.Finalizing
199200
);
200201
}
201202

@@ -204,7 +205,7 @@ contract DKGContractTest is Test {
204205
_assertPhase(
205206
DKG_START_R0 + 4 * PHASE_LENGTH,
206207
0,
207-
DKGContract.Phase.None
208+
IDKGContract.Phase.None
208209
);
209210
}
210211

@@ -213,19 +214,19 @@ contract DKGContractTest is Test {
213214
// r=0 Finalizing ends at DKG_START_R0 + 4 * PHASE_LENGTH = 1000.
214215
// r=1 Dealing should begin at the same block.
215216
uint64 boundary = DKG_START_R0 + 4 * PHASE_LENGTH;
216-
_assertPhase(boundary, 1, DKGContract.Phase.Dealing);
217-
_assertPhase(boundary, 0, DKGContract.Phase.None);
217+
_assertPhase(boundary, 1, IDKGContract.Phase.Dealing);
218+
_assertPhase(boundary, 0, IDKGContract.Phase.None);
218219

219220
// one block before boundary: r=0 still Finalizing, r=1 still None
220-
_assertPhase(boundary - 1, 0, DKGContract.Phase.Finalizing);
221-
_assertPhase(boundary - 1, 1, DKGContract.Phase.None);
221+
_assertPhase(boundary - 1, 0, IDKGContract.Phase.Finalizing);
222+
_assertPhase(boundary - 1, 1, IDKGContract.Phase.None);
222223
}
223224

224225
function testRetryDealingLastBlockBeforeAccusing() public {
225226
// r=1 Dealing: [1000, 1010)
226227
uint64 r1Start = DKG_START_R0 + CYCLE_LENGTH;
227-
_assertPhase(r1Start + PHASE_LENGTH - 1, 1, DKGContract.Phase.Dealing);
228-
_assertPhase(r1Start + PHASE_LENGTH, 1, DKGContract.Phase.Accusing);
228+
_assertPhase(r1Start + PHASE_LENGTH - 1, 1, IDKGContract.Phase.Dealing);
229+
_assertPhase(r1Start + PHASE_LENGTH, 1, IDKGContract.Phase.Accusing);
229230
}
230231

231232
function testDkgStartArithmeticPureView() public view {

0 commit comments

Comments
 (0)