From 024f390ccdddded983d3b7fa81530364ee3818a3 Mon Sep 17 00:00:00 2001 From: Roberto Sousa Date: Mon, 15 Dec 2025 12:49:21 +0000 Subject: [PATCH] feat: add rescue bank feat --- .../wormhole/L1BTCRedeemerWormhole.sol | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/solidity/contracts/cross-chain/wormhole/L1BTCRedeemerWormhole.sol b/solidity/contracts/cross-chain/wormhole/L1BTCRedeemerWormhole.sol index e215d2538..d347111ee 100644 --- a/solidity/contracts/cross-chain/wormhole/L1BTCRedeemerWormhole.sol +++ b/solidity/contracts/cross-chain/wormhole/L1BTCRedeemerWormhole.sol @@ -55,6 +55,8 @@ contract L1BTCRedeemerWormhole is error CallerNotOwner(); error SourceAddressNotAuthorized(); error WormholeTokenBridgeAlreadySet(); + error RecoveryAddressNotSet(); + error RecipientNotRecoveryAddress(); /// @notice Reference to the Wormhole Token Bridge contract. IWormholeTokenBridge public wormholeTokenBridge; @@ -70,6 +72,10 @@ contract L1BTCRedeemerWormhole is /// in Wormhole format (bytes32). mapping(bytes32 => bool) public allowedSenders; + /// @notice Address that receives rescued Bank balances (e.g. a recovery/ops address) + /// used to return funds for failed cross-chain redemptions. + address public recoveryAddress; + event RedemptionRequested( uint256 indexed redemptionKey, bytes20 indexed walletPubKeyHash, @@ -87,6 +93,10 @@ contract L1BTCRedeemerWormhole is event AllowedSenderUpdated(bytes32 indexed sender, bool allowed); + event RecoveryAddressUpdated(address indexed recoveryAddress); + + event BankBalanceRescued(address indexed recipient, uint256 amount); + /// @dev This modifier comes from the `Reimbursable` base contract and /// must be overridden to protect the `updateReimbursementPool` call. modifier onlyReimbursableAdmin() override { @@ -165,6 +175,33 @@ contract L1BTCRedeemerWormhole is emit AllowedSenderUpdated(_sender, _allowed); } + /// @notice Updates the recovery address used by `rescueBankBalance`. + /// @dev Can be called only by the contract owner. + /// @param _recoveryAddress New recovery address. + function setRecoveryAddress(address _recoveryAddress) external onlyOwner { + if (_recoveryAddress == address(0)) revert ZeroAddress(); + + recoveryAddress = _recoveryAddress; + emit RecoveryAddressUpdated(_recoveryAddress); + } + + /// @notice Transfers this contract's Bank balance to the configured recovery address. + /// @dev Can be called only by the contract owner. + /// This is intended to recover funds returned to this contract as part of + /// failed cross-chain redemption flows. + /// @param recipient Must match the configured `recoveryAddress`. + /// @param amount Amount of Bank balance to transfer. + function rescueBankBalance(address recipient, uint256 amount) + external + onlyOwner + { + if (recoveryAddress == address(0)) revert RecoveryAddressNotSet(); + if (recipient != recoveryAddress) revert RecipientNotRecoveryAddress(); + + bank.transferBalance(recipient, amount); + emit BankBalanceRescued(recipient, amount); + } + /// @notice Initiates a redemption on L1 using tBTC received from another chain (e.g., L2) /// via a Wormhole VAA. The tBTC is then used to request a Bitcoin redemption /// from the main tBTC Bridge.