@keep-network/tbtc-v2 1.5.0-dev.3 → 1.5.0-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/artifacts/BLS.json +1 -1
- package/artifacts/Bank.json +3 -3
- package/artifacts/BeaconAuthorization.json +1 -1
- package/artifacts/BeaconDkg.json +1 -1
- package/artifacts/BeaconDkgValidator.json +1 -1
- package/artifacts/BeaconInactivity.json +1 -1
- package/artifacts/BeaconSortitionPool.json +3 -3
- package/artifacts/Bridge.json +5 -5
- package/artifacts/BridgeGovernance.json +2 -2
- package/artifacts/BridgeGovernanceParameters.json +2 -2
- package/artifacts/Deposit.json +2 -2
- package/artifacts/DepositSweep.json +2 -2
- package/artifacts/DonationVault.json +3 -3
- package/artifacts/EcdsaDkgValidator.json +1 -1
- package/artifacts/EcdsaInactivity.json +1 -1
- package/artifacts/EcdsaSortitionPool.json +3 -3
- package/artifacts/Fraud.json +2 -2
- package/artifacts/KeepRegistry.json +1 -1
- package/artifacts/KeepStake.json +2 -2
- package/artifacts/KeepToken.json +2 -2
- package/artifacts/KeepTokenStaking.json +1 -1
- package/artifacts/LightRelay.json +18 -18
- package/artifacts/LightRelayMaintainerProxy.json +8 -8
- package/artifacts/MaintainerProxy.json +19 -19
- package/artifacts/MovingFunds.json +2 -2
- package/artifacts/NuCypherStakingEscrow.json +1 -1
- package/artifacts/NuCypherToken.json +2 -2
- package/artifacts/RandomBeacon.json +2 -2
- package/artifacts/RandomBeaconChaosnet.json +2 -2
- package/artifacts/RandomBeaconGovernance.json +2 -2
- package/artifacts/Redemption.json +2 -2
- package/artifacts/ReimbursementPool.json +2 -2
- package/artifacts/T.json +2 -2
- package/artifacts/TBTC.json +3 -3
- package/artifacts/TBTCToken.json +3 -3
- package/artifacts/TBTCVault.json +23 -23
- package/artifacts/TokenStaking.json +1 -1
- package/artifacts/TokenholderGovernor.json +9 -9
- package/artifacts/TokenholderTimelock.json +8 -8
- package/artifacts/VendingMachine.json +3 -3
- package/artifacts/VendingMachineKeep.json +1 -1
- package/artifacts/VendingMachineNuCypher.json +1 -1
- package/artifacts/VendingMachineV2.json +3 -3
- package/artifacts/VendingMachineV3.json +3 -3
- package/artifacts/WalletCoordinator.json +280 -13
- package/artifacts/WalletRegistry.json +5 -5
- package/artifacts/WalletRegistryGovernance.json +2 -2
- package/artifacts/Wallets.json +2 -2
- package/artifacts/solcInputs/{62f0e623403ed4e28ae856e5d0acb6bc.json → 6cc4a0b423ac26dbfcae644b54bee6ae.json} +1 -1
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
- package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
- package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
- package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.dbg.json +1 -1
- package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.dbg.json +1 -1
- package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +269 -2
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
- package/build/contracts/l2/L2TBTC.sol/L2TBTC.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/IWormholeTokenBridge.dbg.json +1 -1
- package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.dbg.json +1 -1
- package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
- package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
- package/build/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
- package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
- package/contracts/bridge/WalletCoordinator.sol +322 -9
- package/export/artifacts/contracts/bridge/Bridge.sol/Bridge.json +22 -22
- package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.json +6 -6
- package/export/artifacts/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.json +6 -6
- package/export/artifacts/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +18112 -12052
- package/export/artifacts/contracts/l2/L2TBTC.sol/L2TBTC.json +40 -40
- package/export/artifacts/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +49 -49
- package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +88 -88
- package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +57 -57
- package/export/artifacts/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.json +31 -31
- package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +2 -2
- package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +58 -58
- package/export/artifacts/contracts/test/GoerliLightRelay.sol/GoerliLightRelay.json +59 -59
- package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +2 -2
- package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +59 -59
- package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +7 -7
- package/export/artifacts/contracts/test/SystemTestRelay.sol/SystemTestRelay.json +14 -14
- package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +6 -6
- package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +8 -8
- package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +2 -2
- package/export/artifacts/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.json +37 -37
- package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +2 -2
- package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +11 -11
- package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +135 -135
- package/export/hardhat.config.js +7 -0
- package/export/typechain/factories/WalletCoordinator__factory.js +268 -1
- package/package.json +4 -3
|
@@ -25,6 +25,7 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
|
25
25
|
import "./BitcoinTx.sol";
|
|
26
26
|
import "./Bridge.sol";
|
|
27
27
|
import "./Deposit.sol";
|
|
28
|
+
import "./Redemption.sol";
|
|
28
29
|
import "./Wallets.sol";
|
|
29
30
|
|
|
30
31
|
/// @title Wallet coordinator.
|
|
@@ -120,6 +121,19 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
120
121
|
bytes4 refundLocktime;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/// @notice Helper structure representing a redemption proposal.
|
|
125
|
+
struct RedemptionProposal {
|
|
126
|
+
// 20-byte public key hash of the target wallet.
|
|
127
|
+
bytes20 walletPubKeyHash;
|
|
128
|
+
// Array of the redeemers' output scripts that should be part of
|
|
129
|
+
// the redemption. Each output script MUST BE prefixed by its byte
|
|
130
|
+
// length, i.e. passed in the exactly same format as during the
|
|
131
|
+
// `Bridge.requestRedemption` transaction.
|
|
132
|
+
bytes[] redeemersOutputScripts;
|
|
133
|
+
// Proposed BTC fee for the entire transaction.
|
|
134
|
+
uint256 redemptionTxFee;
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
/// @notice Mapping that holds addresses allowed to submit proposals and
|
|
124
138
|
/// request heartbeats.
|
|
125
139
|
mapping(address => bool) public isCoordinator;
|
|
@@ -195,6 +209,55 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
195
209
|
/// the current conditions.
|
|
196
210
|
uint32 public depositSweepProposalSubmissionGasOffset;
|
|
197
211
|
|
|
212
|
+
/// @notice Determines the redemption proposal validity time. In other
|
|
213
|
+
/// words, this is the worst-case time for a redemption during
|
|
214
|
+
/// which the wallet is busy and cannot take another actions. This
|
|
215
|
+
/// is also the duration of the time lock applied to the wallet
|
|
216
|
+
/// once a new redemption proposal is submitted.
|
|
217
|
+
///
|
|
218
|
+
/// For example, if a redemption proposal was submitted at
|
|
219
|
+
/// 2 pm and redemptionProposalValidity is 2 hours, the next
|
|
220
|
+
/// proposal (of any type) can be submitted after 4 pm.
|
|
221
|
+
uint32 public redemptionProposalValidity;
|
|
222
|
+
|
|
223
|
+
/// @notice The minimum time that must elapse since the redemption request
|
|
224
|
+
/// creation before a request becomes eligible for a processing.
|
|
225
|
+
///
|
|
226
|
+
/// For example, if a request was created at 9 am and
|
|
227
|
+
/// redemptionRequestMinAge is 2 hours, the request is eligible for
|
|
228
|
+
/// processing after 11 am.
|
|
229
|
+
///
|
|
230
|
+
/// @dev Forcing request minimum age ensures block finality for Ethereum.
|
|
231
|
+
uint32 public redemptionRequestMinAge;
|
|
232
|
+
|
|
233
|
+
/// @notice Each redemption request can be technically handled until it
|
|
234
|
+
/// reaches its timeout timestamp after which it can be reported
|
|
235
|
+
/// as timed out. However, allowing the wallet to handle requests
|
|
236
|
+
/// that are close to their timeout timestamp may cause a race
|
|
237
|
+
/// between the wallet and the redeemer. In result, the wallet may
|
|
238
|
+
/// redeem the requested funds even though the redeemer already
|
|
239
|
+
/// received back their tBTC (locked during redemption request) upon
|
|
240
|
+
/// reporting the request timeout. In effect, the redeemer may end
|
|
241
|
+
/// out with both tBTC and redeemed BTC in their hands which has
|
|
242
|
+
/// a negative impact on the tBTC <-> BTC peg. In order to mitigate
|
|
243
|
+
/// that problem, this parameter determines a safety margin that
|
|
244
|
+
/// puts the latest moment a request can be handled far before the
|
|
245
|
+
/// point after which the request can be reported as timed out.
|
|
246
|
+
///
|
|
247
|
+
/// For example, if a request times out after 8 pm and
|
|
248
|
+
/// redemptionRequestTimeoutSafetyMargin is 2 hours, the request is
|
|
249
|
+
/// valid for processing only before 6 pm.
|
|
250
|
+
uint32 public redemptionRequestTimeoutSafetyMargin;
|
|
251
|
+
|
|
252
|
+
/// @notice The maximum count of redemption requests that can be processed
|
|
253
|
+
/// within a single redemption.
|
|
254
|
+
uint16 public redemptionMaxSize;
|
|
255
|
+
|
|
256
|
+
/// @notice Gas that is meant to balance the redemption proposal
|
|
257
|
+
/// submission overall cost. Can be updated by the owner based on
|
|
258
|
+
/// the current conditions.
|
|
259
|
+
uint32 public redemptionProposalSubmissionGasOffset;
|
|
260
|
+
|
|
198
261
|
event CoordinatorAdded(address indexed coordinator);
|
|
199
262
|
|
|
200
263
|
event CoordinatorRemoved(address indexed coordinator);
|
|
@@ -225,6 +288,19 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
225
288
|
address indexed coordinator
|
|
226
289
|
);
|
|
227
290
|
|
|
291
|
+
event RedemptionProposalParametersUpdated(
|
|
292
|
+
uint32 redemptionProposalValidity,
|
|
293
|
+
uint32 redemptionRequestMinAge,
|
|
294
|
+
uint32 redemptionRequestTimeoutSafetyMargin,
|
|
295
|
+
uint16 redemptionMaxSize,
|
|
296
|
+
uint32 redemptionProposalSubmissionGasOffset
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
event RedemptionProposalSubmitted(
|
|
300
|
+
RedemptionProposal proposal,
|
|
301
|
+
address indexed coordinator
|
|
302
|
+
);
|
|
303
|
+
|
|
228
304
|
modifier onlyCoordinator() {
|
|
229
305
|
require(isCoordinator[msg.sender], "Caller is not a coordinator");
|
|
230
306
|
_;
|
|
@@ -259,6 +335,12 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
259
335
|
depositRefundSafetyMargin = 24 hours;
|
|
260
336
|
depositSweepMaxSize = 5;
|
|
261
337
|
depositSweepProposalSubmissionGasOffset = 20_000; // optimized for 10 inputs
|
|
338
|
+
|
|
339
|
+
redemptionProposalValidity = 2 hours;
|
|
340
|
+
redemptionRequestMinAge = 600; // 10 minutes or ~50 blocks.
|
|
341
|
+
redemptionRequestTimeoutSafetyMargin = 2 hours;
|
|
342
|
+
redemptionMaxSize = 20;
|
|
343
|
+
redemptionProposalSubmissionGasOffset = 20_000;
|
|
262
344
|
}
|
|
263
345
|
|
|
264
346
|
/// @notice Adds the given address to the set of coordinator addresses.
|
|
@@ -467,7 +549,8 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
467
549
|
/// - Each deposit must have valid extra data (see `validateDepositExtraInfo`),
|
|
468
550
|
/// - Each deposit must have the refund safety margin preserved,
|
|
469
551
|
/// - Each deposit must be controlled by the same wallet,
|
|
470
|
-
/// - Each deposit must target the same vault
|
|
552
|
+
/// - Each deposit must target the same vault,
|
|
553
|
+
/// - Each deposit must be unique.
|
|
471
554
|
///
|
|
472
555
|
/// The following off-chain validation must be performed as a bare minimum:
|
|
473
556
|
/// - Inputs used for the sweep transaction have enough Bitcoin confirmations,
|
|
@@ -498,22 +581,28 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
498
581
|
|
|
499
582
|
address proposalVault = address(0);
|
|
500
583
|
|
|
584
|
+
uint256[] memory processedDepositKeys = new uint256[](
|
|
585
|
+
proposal.depositsKeys.length
|
|
586
|
+
);
|
|
587
|
+
|
|
501
588
|
for (uint256 i = 0; i < proposal.depositsKeys.length; i++) {
|
|
502
589
|
DepositKey memory depositKey = proposal.depositsKeys[i];
|
|
503
590
|
DepositExtraInfo memory depositExtraInfo = depositsExtraInfo[i];
|
|
504
591
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
depositKey.fundingTxHash,
|
|
511
|
-
depositKey.fundingOutputIndex
|
|
512
|
-
)
|
|
592
|
+
uint256 depositKeyUint = uint256(
|
|
593
|
+
keccak256(
|
|
594
|
+
abi.encodePacked(
|
|
595
|
+
depositKey.fundingTxHash,
|
|
596
|
+
depositKey.fundingOutputIndex
|
|
513
597
|
)
|
|
514
598
|
)
|
|
515
599
|
);
|
|
516
600
|
|
|
601
|
+
// slither-disable-next-line calls-loop
|
|
602
|
+
Deposit.DepositRequest memory depositRequest = bridge.deposits(
|
|
603
|
+
depositKeyUint
|
|
604
|
+
);
|
|
605
|
+
|
|
517
606
|
require(depositRequest.revealedAt != 0, "Deposit not revealed");
|
|
518
607
|
|
|
519
608
|
require(
|
|
@@ -554,6 +643,16 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
554
643
|
depositRequest.vault == proposalVault,
|
|
555
644
|
"Deposit targets different vault"
|
|
556
645
|
);
|
|
646
|
+
|
|
647
|
+
// Make sure there are no duplicates in the deposits list.
|
|
648
|
+
for (uint256 j = 0; j < i; j++) {
|
|
649
|
+
require(
|
|
650
|
+
processedDepositKeys[j] != depositKeyUint,
|
|
651
|
+
"Duplicated deposit"
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
processedDepositKeys[i] = depositKeyUint;
|
|
557
656
|
}
|
|
558
657
|
|
|
559
658
|
return true;
|
|
@@ -687,4 +786,218 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
|
|
|
687
786
|
|
|
688
787
|
revert("Extra info funding output script does not match");
|
|
689
788
|
}
|
|
789
|
+
|
|
790
|
+
/// @notice Updates parameters related to redemption proposal.
|
|
791
|
+
/// @param _redemptionProposalValidity The new value of `redemptionProposalValidity`.
|
|
792
|
+
/// @param _redemptionRequestMinAge The new value of `redemptionRequestMinAge`.
|
|
793
|
+
/// @param _redemptionRequestTimeoutSafetyMargin The new value of
|
|
794
|
+
/// `redemptionRequestTimeoutSafetyMargin`.
|
|
795
|
+
/// @param _redemptionMaxSize The new value of `redemptionMaxSize`.
|
|
796
|
+
/// @param _redemptionProposalSubmissionGasOffset The new value of
|
|
797
|
+
/// `redemptionProposalSubmissionGasOffset`.
|
|
798
|
+
/// @dev Requirements:
|
|
799
|
+
/// - The caller must be the owner.
|
|
800
|
+
function updateRedemptionProposalParameters(
|
|
801
|
+
uint32 _redemptionProposalValidity,
|
|
802
|
+
uint32 _redemptionRequestMinAge,
|
|
803
|
+
uint32 _redemptionRequestTimeoutSafetyMargin,
|
|
804
|
+
uint16 _redemptionMaxSize,
|
|
805
|
+
uint32 _redemptionProposalSubmissionGasOffset
|
|
806
|
+
) external onlyOwner {
|
|
807
|
+
redemptionProposalValidity = _redemptionProposalValidity;
|
|
808
|
+
redemptionRequestMinAge = _redemptionRequestMinAge;
|
|
809
|
+
redemptionRequestTimeoutSafetyMargin = _redemptionRequestTimeoutSafetyMargin;
|
|
810
|
+
redemptionMaxSize = _redemptionMaxSize;
|
|
811
|
+
redemptionProposalSubmissionGasOffset = _redemptionProposalSubmissionGasOffset;
|
|
812
|
+
|
|
813
|
+
emit RedemptionProposalParametersUpdated(
|
|
814
|
+
_redemptionProposalValidity,
|
|
815
|
+
_redemptionRequestMinAge,
|
|
816
|
+
_redemptionRequestTimeoutSafetyMargin,
|
|
817
|
+
_redemptionMaxSize,
|
|
818
|
+
_redemptionProposalSubmissionGasOffset
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/// @notice Submits a redemption proposal. Locks the target wallet
|
|
823
|
+
/// for a specific time, equal to the proposal validity period.
|
|
824
|
+
/// This function does not store the proposal in the state but
|
|
825
|
+
/// just emits an event that serves as a guiding light for wallet
|
|
826
|
+
/// off-chain members. Wallet members are supposed to validate
|
|
827
|
+
/// the proposal on their own, before taking any action.
|
|
828
|
+
/// @param proposal The redemption proposal
|
|
829
|
+
/// @dev Requirements:
|
|
830
|
+
/// - The caller is a coordinator,
|
|
831
|
+
/// - The wallet is not time-locked.
|
|
832
|
+
function submitRedemptionProposal(RedemptionProposal calldata proposal)
|
|
833
|
+
public
|
|
834
|
+
onlyCoordinator
|
|
835
|
+
onlyAfterWalletLock(proposal.walletPubKeyHash)
|
|
836
|
+
{
|
|
837
|
+
walletLock[proposal.walletPubKeyHash] = WalletLock(
|
|
838
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
839
|
+
uint32(block.timestamp) + redemptionProposalValidity,
|
|
840
|
+
WalletAction.Redemption
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
emit RedemptionProposalSubmitted(proposal, msg.sender);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/// @notice Wraps `submitRedemptionProposal` call and reimburses the
|
|
847
|
+
/// caller's transaction cost.
|
|
848
|
+
/// @dev See `submitRedemptionProposal` function documentation.
|
|
849
|
+
function submitRedemptionProposalWithReimbursement(
|
|
850
|
+
RedemptionProposal calldata proposal
|
|
851
|
+
) external {
|
|
852
|
+
uint256 gasStart = gasleft();
|
|
853
|
+
|
|
854
|
+
submitRedemptionProposal(proposal);
|
|
855
|
+
|
|
856
|
+
reimbursementPool.refund(
|
|
857
|
+
(gasStart - gasleft()) + redemptionProposalSubmissionGasOffset,
|
|
858
|
+
msg.sender
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/// @notice View function encapsulating the main rules of a valid redemption
|
|
863
|
+
/// proposal. This function is meant to facilitate the off-chain
|
|
864
|
+
/// validation of the incoming proposals. Thanks to it, most
|
|
865
|
+
/// of the work can be done using a single readonly contract call.
|
|
866
|
+
/// @param proposal The redemption proposal to validate.
|
|
867
|
+
/// @return True if the proposal is valid. Reverts otherwise.
|
|
868
|
+
/// @dev Requirements:
|
|
869
|
+
/// - The target wallet must be in the Live state,
|
|
870
|
+
/// - The number of redemption requests included in the redemption
|
|
871
|
+
/// proposal must be in the range [1, `redemptionMaxSize`],
|
|
872
|
+
/// - The proposed redemption tx fee must be grater than zero,
|
|
873
|
+
/// - The proposed redemption tx fee must be lesser than or equal to
|
|
874
|
+
/// the maximum total fee allowed by the Bridge
|
|
875
|
+
/// (`Bridge.redemptionTxMaxTotalFee`),
|
|
876
|
+
/// - The proposed maximum per-request redemption tx fee share must be
|
|
877
|
+
/// lesser than or equal to the maximum fee share allowed by the
|
|
878
|
+
/// given request (`RedemptionRequest.txMaxFee`),
|
|
879
|
+
/// - Each request must be a pending request registered in the Bridge,
|
|
880
|
+
/// - Each request must be old enough, i.e. at least `redemptionRequestMinAge`
|
|
881
|
+
/// elapsed since their creation time,
|
|
882
|
+
/// - Each request must have the timeout safety margin preserved,
|
|
883
|
+
/// - Each request must be unique.
|
|
884
|
+
function validateRedemptionProposal(RedemptionProposal calldata proposal)
|
|
885
|
+
external
|
|
886
|
+
view
|
|
887
|
+
returns (bool)
|
|
888
|
+
{
|
|
889
|
+
require(
|
|
890
|
+
bridge.wallets(proposal.walletPubKeyHash).state ==
|
|
891
|
+
Wallets.WalletState.Live,
|
|
892
|
+
"Wallet is not in Live state"
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
uint256 requestsCount = proposal.redeemersOutputScripts.length;
|
|
896
|
+
|
|
897
|
+
require(requestsCount > 0, "Redemption below the min size");
|
|
898
|
+
|
|
899
|
+
require(
|
|
900
|
+
requestsCount <= redemptionMaxSize,
|
|
901
|
+
"Redemption exceeds the max size"
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
(
|
|
905
|
+
,
|
|
906
|
+
,
|
|
907
|
+
,
|
|
908
|
+
uint64 redemptionTxMaxTotalFee,
|
|
909
|
+
uint32 redemptionTimeout,
|
|
910
|
+
,
|
|
911
|
+
|
|
912
|
+
) = bridge.redemptionParameters();
|
|
913
|
+
|
|
914
|
+
require(
|
|
915
|
+
proposal.redemptionTxFee > 0,
|
|
916
|
+
"Proposed transaction fee cannot be zero"
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// Make sure the proposed fee does not exceed the total fee limit.
|
|
920
|
+
require(
|
|
921
|
+
proposal.redemptionTxFee <= redemptionTxMaxTotalFee,
|
|
922
|
+
"Proposed transaction fee is too high"
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
// Compute the indivisible remainder that remains after dividing the
|
|
926
|
+
// redemption transaction fee over all requests evenly.
|
|
927
|
+
uint256 redemptionTxFeeRemainder = proposal.redemptionTxFee %
|
|
928
|
+
requestsCount;
|
|
929
|
+
// Compute the transaction fee per request by dividing the redemption
|
|
930
|
+
// transaction fee (reduced by the remainder) by the number of requests.
|
|
931
|
+
uint256 redemptionTxFeePerRequest = (proposal.redemptionTxFee -
|
|
932
|
+
redemptionTxFeeRemainder) / requestsCount;
|
|
933
|
+
|
|
934
|
+
uint256[] memory processedRedemptionKeys = new uint256[](requestsCount);
|
|
935
|
+
|
|
936
|
+
for (uint256 i = 0; i < requestsCount; i++) {
|
|
937
|
+
bytes memory script = proposal.redeemersOutputScripts[i];
|
|
938
|
+
|
|
939
|
+
// As the wallet public key hash is part of the redemption key,
|
|
940
|
+
// we have an implicit guarantee that all requests being part
|
|
941
|
+
// of the proposal target the same wallet.
|
|
942
|
+
uint256 redemptionKey = uint256(
|
|
943
|
+
keccak256(
|
|
944
|
+
abi.encodePacked(
|
|
945
|
+
keccak256(script),
|
|
946
|
+
proposal.walletPubKeyHash
|
|
947
|
+
)
|
|
948
|
+
)
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
// slither-disable-next-line calls-loop
|
|
952
|
+
Redemption.RedemptionRequest memory redemptionRequest = bridge
|
|
953
|
+
.pendingRedemptions(redemptionKey);
|
|
954
|
+
|
|
955
|
+
require(
|
|
956
|
+
redemptionRequest.requestedAt != 0,
|
|
957
|
+
"Not a pending redemption request"
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
require(
|
|
961
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
962
|
+
block.timestamp >
|
|
963
|
+
redemptionRequest.requestedAt + redemptionRequestMinAge,
|
|
964
|
+
"Redemption request min age not achieved yet"
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
// Calculate the timeout the given request times out at.
|
|
968
|
+
uint32 requestTimeout = redemptionRequest.requestedAt +
|
|
969
|
+
redemptionTimeout;
|
|
970
|
+
// Make sure we are far enough from the moment the request times out.
|
|
971
|
+
require(
|
|
972
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
973
|
+
block.timestamp <
|
|
974
|
+
requestTimeout - redemptionRequestTimeoutSafetyMargin,
|
|
975
|
+
"Redemption request timeout safety margin is not preserved"
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
uint256 feePerRequest = redemptionTxFeePerRequest;
|
|
979
|
+
// The last request incurs the fee remainder.
|
|
980
|
+
if (i == requestsCount - 1) {
|
|
981
|
+
feePerRequest += redemptionTxFeeRemainder;
|
|
982
|
+
}
|
|
983
|
+
// Make sure the redemption transaction fee share incurred by
|
|
984
|
+
// the given request fits in the limit for that request.
|
|
985
|
+
require(
|
|
986
|
+
feePerRequest <= redemptionRequest.txMaxFee,
|
|
987
|
+
"Proposed transaction per-request fee share is too high"
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
// Make sure there are no duplicates in the requests list.
|
|
991
|
+
for (uint256 j = 0; j < i; j++) {
|
|
992
|
+
require(
|
|
993
|
+
processedRedemptionKeys[j] != redemptionKey,
|
|
994
|
+
"Duplicated request"
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
processedRedemptionKeys[i] = redemptionKey;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
690
1003
|
}
|