Skip to content

Commit 6793e8c

Browse files
committed
[DO NOT MERGE - hard drain] D-2.2 slice 4: remove ecdsaWalletRegistry handle + heartbeat callback
1 parent 507141b commit 6793e8c

12 files changed

Lines changed: 228 additions & 917 deletions

solidity/contracts/bridge/Bridge.sol

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ pragma solidity 0.8.17;
1818
import "@keep-network/random-beacon/contracts/Governable.sol";
1919
import "@keep-network/random-beacon/contracts/ReimbursementPool.sol";
2020
// D-2 dropped `IWalletOwner` inheritance: the ECDSA wallet
21-
// registry's `__ecdsaWalletCreatedCallback` callback path is
22-
// removed entirely (no new ECDSA wallets after D-2 ships). The
23-
// heartbeat callback is preserved as a standalone external
24-
// function for existing-wallet lifecycle. Import path retained
25-
// for the registry contract handle (used via
26-
// `BridgeState.Storage.ecdsaWalletRegistry`).
21+
// registry's `__ecdsaWalletCreatedCallback` callback path was
22+
// removed entirely (no new ECDSA wallets after D-2 ships).
23+
// D-2.2 slice 4 also removes the ECDSA heartbeat-failure
24+
// callback. Import path retained for the legacy registry storage
25+
// slot and ABI getter (`BridgeState.Storage.ecdsaWalletRegistry`).
2726

2827
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
2928
import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
@@ -838,12 +837,8 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
838837
/// - The source wallet must not have pending redemption requests,
839838
/// - The source wallet must not have pending moved funds sweep requests,
840839
/// - The source wallet must not have submitted its commitment already,
841-
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
842-
/// be exactly the same as the hash stored under `membersIdsHash`
843-
/// for the given source wallet in the ECDSA registry. Those IDs are
844-
/// not directly stored in the contract for gas efficiency purposes
845-
/// but they can be read from appropriate `DkgResultSubmitted`
846-
/// and `DkgResultApproved` events,
840+
/// - The lifecycle router must confirm the caller is a member of the
841+
/// source wallet signing group at the provided member index,
847842
/// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],
848843
/// - The caller must be the member of the source wallet signing group
849844
/// at the position indicated by `walletMemberIndex` parameter,
@@ -1091,15 +1086,12 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
10911086
/// Ethereum chain. If there is no active wallet at the moment, or
10921087
/// the active wallet has no main UTXO, this parameter can be
10931088
/// empty as it is ignored,
1094-
/// - Wallet creation must not be in progress,
10951089
/// - If the active wallet is set, one of the following
10961090
/// conditions must be true:
10971091
/// - The active wallet BTC balance is above the minimum threshold
10981092
/// and the active wallet is old enough, i.e. the creation period
10991093
/// was elapsed since its creation time,
11001094
/// - The active wallet BTC balance is above the maximum threshold,
1101-
/// - `currentNewWalletScheme` must be `Frost` (any Ecdsa
1102-
/// selection reverts unconditionally in D-2),
11031095
/// - `frostWalletRegistry` must be set (governance one-
11041096
/// time setter; required before FROST wallet creation).
11051097
function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)
@@ -1160,21 +1152,6 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
11601152
self.registerNewFrostWallet(xOnlyOutputKey);
11611153
}
11621154

1163-
/// @notice A callback function that is called by the ECDSA Wallet Registry
1164-
/// once a wallet heartbeat failure is detected.
1165-
/// @param publicKeyX Wallet's public key's X coordinate.
1166-
/// @param publicKeyY Wallet's public key's Y coordinate.
1167-
/// @dev Requirements:
1168-
/// - The only caller authorized to call this function is `registry`,
1169-
/// - Wallet must be in Live state.
1170-
function __ecdsaWalletHeartbeatFailedCallback(
1171-
bytes32,
1172-
bytes32 publicKeyX,
1173-
bytes32 publicKeyY
1174-
) external {
1175-
self.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);
1176-
}
1177-
11781155
/// @notice Notifies that the wallet is either old enough or has too few
11791156
/// satoshi left and qualifies to be closed.
11801157
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
@@ -1198,7 +1175,7 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
11981175
}
11991176

12001177
/// @notice Notifies about the end of the closing period for the given wallet.
1201-
/// Closes the wallet ultimately and notifies the ECDSA registry
1178+
/// Closes the wallet ultimately and notifies the lifecycle router
12021179
/// about this fact.
12031180
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
12041181
/// @dev Requirements:
@@ -2243,11 +2220,8 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
22432220
}
22442221

22452222
/// @notice Sets the BridgeLifecycleRouter address. The router is
2246-
/// the authorized dispatcher for FROST-scheme wallet
2247-
/// lifecycle operations (closeWallet, seize,
2248-
/// isWalletMember). ECDSA lifecycle operations bypass it
2249-
/// entirely and continue to call ecdsaWalletRegistry
2250-
/// directly.
2223+
/// the authorized dispatcher for wallet lifecycle
2224+
/// operations (closeWallet, seize, isWalletMember).
22512225
/// @param lifecycleRouter Address of the BridgeLifecycleRouter.
22522226
/// @dev Requirements:
22532227
/// - The caller must be the governance,

solidity/contracts/bridge/BridgeState.sol

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -382,17 +382,13 @@ library BridgeState {
382382
// lifecycle; see EcdsaFraudRouter / P2TRSignatureFraudRouter.
383383
address p2trFraudRouter;
384384
// Address of the BridgeLifecycleRouter. The router is the
385-
// authorized dispatcher for FROST-scheme wallet lifecycle
386-
// operations (closeWallet, seize, isWalletMember). It reads
387-
// wallet state from this Bridge and forwards to the configured
388-
// frostWalletRegistry. ECDSA lifecycle operations are NOT
389-
// routed through it -- they continue to call
390-
// ecdsaWalletRegistry directly, preserving the current
391-
// ownership/callback model unchanged. Set exactly once via
392-
// governance; FROST wallet registration is gated on this
393-
// being non-zero (see registerNewFrostWallet) so a FROST
394-
// wallet cannot enter Live state without a configured
395-
// lifecycle router.
385+
// authorized dispatcher for wallet lifecycle operations
386+
// (closeWallet, seize, isWalletMember). It reads wallet state
387+
// from this Bridge and forwards to the configured
388+
// frostWalletRegistry. Set exactly once via governance; FROST
389+
// wallet registration is gated on this being non-zero (see
390+
// registerNewFrostWallet) so a FROST wallet cannot enter Live
391+
// state without a configured lifecycle router.
396392
address lifecycleRouter;
397393
// Reverse mapping for canonical wallet ID lookup. For ECDSA
398394
// wallets the walletID can be derived on-chain from the
@@ -407,12 +403,11 @@ library BridgeState {
407403
// wallet pubKeyHash that does not have a FROST registration
408404
// entry.
409405
mapping(bytes20 => bytes32) walletIDByWalletPubKeyHash;
410-
// Currently selected wallet scheme for `requestNewWallet`.
411-
// Governance flips via `setNewWalletScheme`. Defaults to
412-
// `Ecdsa` on C-2 activation (preserves prior behavior;
413-
// FROST opt-in must be explicit). Sits at slot 38 offset
414-
// 0 (the preceding field is a mapping, which always
415-
// occupies a full slot).
406+
// Previously selected wallet scheme for `requestNewWallet`.
407+
// Preserved for upgrade-safety; D-2.2 slice 3 removed the
408+
// scheme dispatch path so no production code reads this value.
409+
// Sits at slot 38 offset 0 (the preceding field is a mapping,
410+
// which always occupies a full slot).
416411
WalletScheme currentNewWalletScheme;
417412
// Total ECDSA wallets ever registered on this Bridge,
418413
// counted from the C-2.1 activation block forward.
@@ -423,33 +418,18 @@ library BridgeState {
423418
// historical-count `seedEcdsaWalletCount` setter
424419
// originally specified in RFC v6 is deferred — see
425420
// §"Deferred from C-2.1" in the migration plan doc).
426-
// Strictly monotonic: incremented in
427-
// `Wallets.registerNewWallet`; NEVER decremented.
421+
// Strictly monotonic: incremented by the legacy ECDSA
422+
// registration callback before D-2.2 slice 4; NEVER decremented.
428423
//
429424
// Packs into the SAME slot as `currentNewWalletScheme`
430425
// above (slot 38): solc places `uint128` (16B) at offset
431426
// 1, right after the 1-byte enum. Total slot 38 usage:
432427
// 17 bytes of 32. No __gap change from C-2.
433428
uint128 ecdsaWalletCount;
434-
// D-1 soft-retirement flag. Set by the one-time
435-
// governance setter `Bridge.retireEcdsa()` (the setter
436-
// itself is deferred to D-2 — see Bridge.sol). When
437-
// true, `Wallets.requestNewWallet` rejects new requests
438-
// routed to the ECDSA registry. Late callbacks from an
439-
// in-flight DKG that started BEFORE retirement are NOT
440-
// blocked: reverting `Wallets.registerNewWallet` would
441-
// strand the ECDSA registry in a non-IDLE state and
442-
// freeze every subsequent FROST wallet creation via
443-
// the unconditional IDLE precheck in `requestNewWallet`
444-
// (per PR #443 review). Governance therefore
445-
// enforces the "no late ECDSA wallets" invariant
446-
// operationally: pause Bridge, wait for in-flight DKGs
447-
// to settle (registry returns to IDLE), set the flag,
448-
// unpause. Existing ECDSA wallets' lifecycle paths
449-
// (sweeps, redemptions, fraud, moving funds, closing,
450-
// termination) remain fully functional — D-1 only
451-
// closes the *new wallet creation* boundary; D-2
452-
// performs the final structural removal.
429+
// D-1 soft-retirement flag. D-2 keeps it as an on-chain
430+
// audit marker, while D-2.2 structurally removes the ECDSA
431+
// request and callback paths. Existing storage is preserved
432+
// for upgrade-safety and ABI-compatible observation.
453433
//
454434
// Packs at slot 38 offset 17 (just after
455435
// `ecdsaWalletCount`, which occupies bytes 1-16). Total

solidity/contracts/bridge/IBridgeLifecycleRouter.sol

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ pragma solidity 0.8.17;
55
/// @title IBridgeLifecycleRouter
66
/// @notice Interface for the Bridge's FROST-scheme lifecycle router.
77
///
8-
/// The Bridge dispatches FROST-scheme wallet lifecycle operations
8+
/// The Bridge dispatches wallet lifecycle operations
99
/// (closeWallet, seize, isWalletMember) to a router implementing this
1010
/// interface. The router resolves the wallet's canonical walletID from
1111
/// the Bridge's `walletIDByWalletPubKeyHash` mapping and forwards the
12-
/// call to the configured `frostWalletRegistry`. ECDSA-scheme lifecycle
13-
/// operations bypass the router entirely and continue to call
14-
/// `ecdsaWalletRegistry` directly, preserving the existing
15-
/// ownership/callback model for ECDSA wallets.
12+
/// call to the configured `frostWalletRegistry`.
1613
///
1714
/// The Bridge passes only the 20-byte wallet public key hash to the
1815
/// router; the router reads the rest of the lifecycle state from the

solidity/contracts/bridge/MovingFunds.sol

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,8 @@ library MovingFunds {
140140
/// - The source wallet must not have pending redemption requests,
141141
/// - The source wallet must not have pending moved funds sweep requests,
142142
/// - The source wallet must not have submitted its commitment already,
143-
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
144-
/// be exactly the same as the hash stored under `membersIdsHash`
145-
/// for the given source wallet in the ECDSA registry. Those IDs are
146-
/// not directly stored in the contract for gas efficiency purposes
147-
/// but they can be read from appropriate `DkgResultSubmitted`
148-
/// and `DkgResultApproved` events,
143+
/// - The lifecycle router must confirm the caller is a member of the
144+
/// source wallet signing group at the provided member index,
149145
/// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],
150146
/// - The caller must be the member of the source wallet signing group
151147
/// at the position indicated by `walletMemberIndex` parameter,
@@ -194,23 +190,13 @@ library MovingFunds {
194190
"Target wallets commitment already submitted"
195191
);
196192

197-
bool isMember;
198-
if (wallet.ecdsaWalletID != bytes32(0)) {
199-
isMember = self.ecdsaWalletRegistry.isWalletMember(
200-
wallet.ecdsaWalletID,
193+
bool isMember = IBridgeLifecycleRouter(self.lifecycleRouter)
194+
.isWalletMember(
195+
walletPubKeyHash,
201196
walletMembersIDs,
202197
msg.sender,
203198
walletMemberIndex
204199
);
205-
} else {
206-
isMember = IBridgeLifecycleRouter(self.lifecycleRouter)
207-
.isWalletMember(
208-
walletPubKeyHash,
209-
walletMembersIDs,
210-
msg.sender,
211-
walletMemberIndex
212-
);
213-
}
214200
require(isMember, "Caller is not a member of the source wallet");
215201

216202
uint64 walletBtcBalance = self.getWalletBtcBalance(

0 commit comments

Comments
 (0)