-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redemptions veto mechanism #788
Changes from 17 commits
b42f031
c97e873
941cf9f
9fe9d9a
6c5dc8d
68d5efd
6aad28a
45d3afb
4556003
912f96a
c57f4f1
3ab6ee5
a62b2e9
8d7d51b
743e9c7
6b0a76a
584c4bb
d3cc2e8
7425eba
6a2932d
5346e0e
2556c28
b6aef91
c6c03a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,37 @@ import "./Wallets.sol"; | |
|
||
import "../bank/Bank.sol"; | ||
|
||
/// @notice Interface of the RedemptionWatchtower. | ||
interface IRedemptionWatchtower { | ||
/// @notice Determines whether a redemption request is considered safe. | ||
/// @param walletPubKeyHash 20-byte public key hash of the wallet that | ||
/// is meant to handle the redemption request. | ||
/// @param redeemerOutputScript The redeemer's length-prefixed output | ||
/// script (P2PKH, P2WPKH, P2SH or P2WSH) that is meant to | ||
/// receive the redeemed amount. | ||
/// @param balanceOwner The address of the Bank balance owner whose balance | ||
/// is getting redeemed. | ||
/// @param redeemer The address that requested the redemption. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am a little lost in what is the difference between those two. This will probably get clear as I move forward with the review of the rest of the code but could be an indicator we should improve the docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah: I'd love to copy-paste the explanation of the redeemer from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed as part of c6c03a3 |
||
/// @return True if the redemption request is safe, false otherwise. | ||
/// Specific safety criteria depend on the implementation. | ||
function isSafeRedemption( | ||
bytes20 walletPubKeyHash, | ||
bytes calldata redeemerOutputScript, | ||
address balanceOwner, | ||
address redeemer | ||
) external view returns (bool); | ||
|
||
/// @notice Returns the applicable redemption delay for a redemption | ||
/// request identified by the given redemption key. | ||
/// @param redemptionKey Redemption key built as | ||
/// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`. | ||
/// @return Redemption delay. | ||
function getRedemptionDelay(uint256 redemptionKey) | ||
external | ||
view | ||
returns (uint32); | ||
} | ||
|
||
/// @notice Aggregates functions common to the redemption transaction proof | ||
/// validation and to the moving funds transaction proof validation. | ||
library OutboundTx { | ||
|
@@ -402,6 +433,19 @@ library Redemption { | |
bytes memory redeemerOutputScript, | ||
uint64 amount | ||
) internal { | ||
if (self.redemptionWatchtower != address(0)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add this check to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed as part of c6c03a3 |
||
require( | ||
IRedemptionWatchtower(self.redemptionWatchtower) | ||
.isSafeRedemption( | ||
walletPubKeyHash, | ||
redeemerOutputScript, | ||
balanceOwner, | ||
redeemer | ||
), | ||
"Redemption request rejected by the watchtower" | ||
); | ||
} | ||
|
||
Wallets.Wallet storage wallet = self.registeredWallets[ | ||
walletPubKeyHash | ||
]; | ||
|
@@ -1075,4 +1119,64 @@ library Redemption { | |
} | ||
return key; | ||
} | ||
|
||
/// @notice Notifies that a redemption request was vetoed in the watchtower. | ||
/// This function is responsible for adjusting the Bridge's state | ||
/// accordingly. | ||
/// The results of calling this function: | ||
/// - the pending redemptions value for the wallet is decreased | ||
/// by the requested amount (minus treasury fee), | ||
/// - the request is removed from pending redemptions mapping, | ||
/// - the tokens taken from the redeemer on redemption request are | ||
/// detained and passed to the redemption watchtower | ||
/// (as Bank's balance) for further processing. | ||
/// @param walletPubKeyHash 20-byte public key hash of the wallet. | ||
/// @param redeemerOutputScript The redeemer's length-prefixed output | ||
/// script (P2PKH, P2WPKH, P2SH or P2WSH). | ||
/// @dev Requirements: | ||
/// - The caller must be the redemption watchtower, | ||
/// - The redemption request identified by `walletPubKeyHash` and | ||
/// `redeemerOutputScript` must exist. | ||
function notifyRedemptionVeto( | ||
BridgeState.Storage storage self, | ||
bytes20 walletPubKeyHash, | ||
bytes calldata redeemerOutputScript | ||
) external { | ||
require( | ||
msg.sender == self.redemptionWatchtower, | ||
"Caller is not the redemption watchtower" | ||
); | ||
|
||
uint256 redemptionKey = getRedemptionKey( | ||
walletPubKeyHash, | ||
redeemerOutputScript | ||
); | ||
Redemption.RedemptionRequest storage redemption = self | ||
.pendingRedemptions[redemptionKey]; | ||
|
||
// Should never happen, but just in case. | ||
require( | ||
redemption.requestedAt != 0, | ||
"Redemption request does not exist" | ||
); | ||
|
||
// Update the wallet's pending redemptions value. This is the | ||
// same logic as performed upon redemption request timeout. | ||
// If we don't do this, the wallet will hold the reserve | ||
// for a redemption request that will never be processed. | ||
self.registeredWallets[walletPubKeyHash].pendingRedemptionsValue -= | ||
redemption.requestedAmount - | ||
redemption.treasuryFee; | ||
|
||
// Capture the amount that should be transferred to the | ||
// redemption watchtower. | ||
uint64 detainedAmount = redemption.requestedAmount; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To confirm: we use the whole requested amount as a detained amount because the treasury fee is deducted in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, this is correct. I will drop a comment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed as part of c6c03a3 |
||
|
||
// Delete the redemption request from the pending redemptions | ||
// mapping. This is important to avoid this redemption request | ||
// to be processed by the wallet or reported as timed out. | ||
delete self.pendingRedemptions[redemptionKey]; | ||
|
||
self.bank.transferBalance(self.redemptionWatchtower, detainedAmount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of the Bank! Redemption Watchtower has a bank balance, I like it very much. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not allow the governance to update the watchtower address? I see it is deployed as a proxy but still... We would avoid upgrading the
Bridge
contract in case the watchtower address needs to be replaced.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done on purpose to provide a kind of social contract regarding how the mechanism works. We do not expect to change anything in the mechanism itself. Any security upgrade would likely require a
Bridge
upgrade anyway. Additional argument isBridge
contract size. We do not add anything that is not a must have now.