Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 10 additions & 34 deletions solidity/contracts/bridge/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ pragma solidity 0.8.17;
import "@keep-network/random-beacon/contracts/Governable.sol";
import "@keep-network/random-beacon/contracts/ReimbursementPool.sol";
// D-2 dropped `IWalletOwner` inheritance: the ECDSA wallet
// registry's `__ecdsaWalletCreatedCallback` callback path is
// removed entirely (no new ECDSA wallets after D-2 ships). The
// heartbeat callback is preserved as a standalone external
// function for existing-wallet lifecycle. Import path retained
// for the registry contract handle (used via
// `BridgeState.Storage.ecdsaWalletRegistry`).
// registry's `__ecdsaWalletCreatedCallback` callback path was
// removed entirely (no new ECDSA wallets after D-2 ships).
// D-2.2 slice 4 also removes the ECDSA heartbeat-failure
// callback. Import path retained for the legacy registry storage
// slot and ABI getter (`BridgeState.Storage.ecdsaWalletRegistry`).

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
Expand Down Expand Up @@ -886,12 +885,8 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
/// - The source wallet must not have pending redemption requests,
/// - The source wallet must not have pending moved funds sweep requests,
/// - The source wallet must not have submitted its commitment already,
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
/// be exactly the same as the hash stored under `membersIdsHash`
/// for the given source wallet in the ECDSA registry. Those IDs are
/// not directly stored in the contract for gas efficiency purposes
/// but they can be read from appropriate `DkgResultSubmitted`
/// and `DkgResultApproved` events,
/// - The lifecycle router must confirm the caller is a member of the
/// source wallet signing group at the provided member index,
/// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],
/// - The caller must be the member of the source wallet signing group
/// at the position indicated by `walletMemberIndex` parameter,
Expand Down Expand Up @@ -1139,7 +1134,6 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
/// Ethereum chain. If there is no active wallet at the moment, or
/// the active wallet has no main UTXO, this parameter can be
/// empty as it is ignored,
/// - Wallet creation must not be in progress,
/// - If the active wallet is set, one of the following
/// conditions must be true:
/// - The active wallet BTC balance is above the minimum threshold
Expand Down Expand Up @@ -1209,21 +1203,6 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
self.registerNewFrostWallet(xOnlyOutputKey);
}

/// @notice A callback function that is called by the ECDSA Wallet Registry
/// once a wallet heartbeat failure is detected.
/// @param publicKeyX Wallet's public key's X coordinate.
/// @param publicKeyY Wallet's public key's Y coordinate.
/// @dev Requirements:
/// - The only caller authorized to call this function is `registry`,
/// - Wallet must be in Live state.
function __ecdsaWalletHeartbeatFailedCallback(
bytes32,
bytes32 publicKeyX,
bytes32 publicKeyY
) external {
self.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);
}

/// @notice Notifies that the wallet is either old enough or has too few
/// satoshi left and qualifies to be closed.
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
Expand All @@ -1247,7 +1226,7 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
}

/// @notice Notifies about the end of the closing period for the given wallet.
/// Closes the wallet ultimately and notifies the ECDSA registry
/// Closes the wallet ultimately and notifies the lifecycle router
/// about this fact.
/// @param walletPubKeyHash 20-byte public key hash of the wallet.
/// @dev Requirements:
Expand Down Expand Up @@ -2301,11 +2280,8 @@ contract Bridge is Governable, Initializable, IReceiveBalanceApproval {
}

/// @notice Sets the BridgeLifecycleRouter address. The router is
/// the authorized dispatcher for FROST-scheme wallet
/// lifecycle operations (closeWallet, seize,
/// isWalletMember). ECDSA lifecycle operations bypass it
/// entirely and continue to call ecdsaWalletRegistry
/// directly.
/// the authorized dispatcher for wallet lifecycle
/// operations (closeWallet, seize, isWalletMember).
/// @param lifecycleRouter Address of the BridgeLifecycleRouter.
/// @dev Requirements:
/// - The caller must be the governance,
Expand Down
41 changes: 11 additions & 30 deletions solidity/contracts/bridge/BridgeState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -379,17 +379,13 @@ library BridgeState {
// lifecycle; see EcdsaFraudRouter / P2TRSignatureFraudRouter.
address p2trFraudRouter;
// Address of the BridgeLifecycleRouter. The router is the
// authorized dispatcher for FROST-scheme wallet lifecycle
// operations (closeWallet, seize, isWalletMember). It reads
// wallet state from this Bridge and forwards to the configured
// frostWalletRegistry. ECDSA lifecycle operations are NOT
// routed through it -- they continue to call
// ecdsaWalletRegistry directly, preserving the current
// ownership/callback model unchanged. Set exactly once via
// governance; FROST wallet registration is gated on this
// being non-zero (see registerNewFrostWallet) so a FROST
// wallet cannot enter Live state without a configured
// lifecycle router.
// authorized dispatcher for wallet lifecycle operations
// (closeWallet, seize, isWalletMember). It reads wallet state
// from this Bridge and forwards to the configured
// frostWalletRegistry. Set exactly once via governance; FROST
// wallet registration is gated on this being non-zero (see
// registerNewFrostWallet) so a FROST wallet cannot enter Live
// state without a configured lifecycle router.
address lifecycleRouter;
// Reverse mapping for canonical wallet ID lookup. For ECDSA
// wallets the walletID can be derived on-chain from the
Expand Down Expand Up @@ -427,25 +423,10 @@ library BridgeState {
// 1, right after the 1-byte enum. Total slot 37 usage:
// 17 bytes of 32. No __gap change from C-2.
uint128 ecdsaWalletCount;
// D-1 soft-retirement flag. Set by the one-time
// governance setter `Bridge.retireEcdsa()` (the setter
// itself is deferred to D-2 — see Bridge.sol). When
// true, `Wallets.requestNewWallet` rejects new requests
// routed to the ECDSA registry. Late callbacks from an
// in-flight DKG that started BEFORE retirement are NOT
// blocked: reverting `Wallets.registerNewWallet` would
// strand the ECDSA registry in a non-IDLE state and
// freeze every subsequent FROST wallet creation via
// the unconditional IDLE precheck in `requestNewWallet`
// (per PR #443 review). Governance therefore
// enforces the "no late ECDSA wallets" invariant
// operationally: pause Bridge, wait for in-flight DKGs
// to settle (registry returns to IDLE), set the flag,
// unpause. Existing ECDSA wallets' lifecycle paths
// (sweeps, redemptions, fraud, moving funds, closing,
// termination) remain fully functional — D-1 only
// closes the *new wallet creation* boundary; D-2
// performs the final structural removal.
// D-1 soft-retirement flag. D-2 keeps it as an on-chain
// audit marker, while D-2.2 structurally removes the ECDSA
// request and callback paths. Existing storage is preserved
// for upgrade-safety and ABI-compatible observation.
//
// Packs at slot 37 offset 17 (just after
// `ecdsaWalletCount`, which occupies bytes 1-16). Total
Expand Down
7 changes: 2 additions & 5 deletions solidity/contracts/bridge/IBridgeLifecycleRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ pragma solidity 0.8.17;
/// @title IBridgeLifecycleRouter
/// @notice Interface for the Bridge's FROST-scheme lifecycle router.
///
/// The Bridge dispatches FROST-scheme wallet lifecycle operations
/// The Bridge dispatches wallet lifecycle operations
/// (closeWallet, seize, isWalletMember) to a router implementing this
/// interface. The router resolves the wallet's canonical walletID from
/// the Bridge's `walletIDByWalletPubKeyHash` mapping and forwards the
/// call to the configured `frostWalletRegistry`. ECDSA-scheme lifecycle
/// operations bypass the router entirely and continue to call
/// `ecdsaWalletRegistry` directly, preserving the existing
/// ownership/callback model for ECDSA wallets.
/// call to the configured `frostWalletRegistry`.
///
/// The Bridge passes only the 20-byte wallet public key hash to the
/// router; the router reads the rest of the lifecycle state from the
Expand Down
24 changes: 5 additions & 19 deletions solidity/contracts/bridge/MovingFunds.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,8 @@ library MovingFunds {
/// - The source wallet must not have pending redemption requests,
/// - The source wallet must not have pending moved funds sweep requests,
/// - The source wallet must not have submitted its commitment already,
/// - The expression `keccak256(abi.encode(walletMembersIDs))` must
/// be exactly the same as the hash stored under `membersIdsHash`
/// for the given source wallet in the ECDSA registry. Those IDs are
/// not directly stored in the contract for gas efficiency purposes
/// but they can be read from appropriate `DkgResultSubmitted`
/// and `DkgResultApproved` events,
/// - The lifecycle router must confirm the caller is a member of the
/// source wallet signing group at the provided member index,
/// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],
/// - The caller must be the member of the source wallet signing group
/// at the position indicated by `walletMemberIndex` parameter,
Expand Down Expand Up @@ -194,23 +190,13 @@ library MovingFunds {
"Target wallets commitment already submitted"
);

bool isMember;
if (wallet.ecdsaWalletID != bytes32(0)) {
isMember = self.ecdsaWalletRegistry.isWalletMember(
wallet.ecdsaWalletID,
bool isMember = IBridgeLifecycleRouter(self.lifecycleRouter)
.isWalletMember(
walletPubKeyHash,
walletMembersIDs,
msg.sender,
walletMemberIndex
);
} else {
isMember = IBridgeLifecycleRouter(self.lifecycleRouter)
.isWalletMember(
walletPubKeyHash,
walletMembersIDs,
msg.sender,
walletMemberIndex
);
}
require(isMember, "Caller is not a member of the source wallet");

uint64 walletBtcBalance = self.getWalletBtcBalance(
Expand Down
Loading
Loading