@keep-network/tbtc-v2 1.6.0-dev.5 → 1.6.0-dev.6

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.
Files changed (140) hide show
  1. package/artifacts/BLS.json +1 -1
  2. package/artifacts/Bank.json +3 -3
  3. package/artifacts/BeaconAuthorization.json +1 -1
  4. package/artifacts/BeaconDkg.json +1 -1
  5. package/artifacts/BeaconDkgValidator.json +1 -1
  6. package/artifacts/BeaconInactivity.json +1 -1
  7. package/artifacts/BeaconSortitionPool.json +3 -3
  8. package/artifacts/Bridge.json +5 -5
  9. package/artifacts/BridgeGovernance.json +2 -2
  10. package/artifacts/BridgeGovernanceParameters.json +2 -2
  11. package/artifacts/Deposit.json +2 -2
  12. package/artifacts/DepositSweep.json +2 -2
  13. package/artifacts/DonationVault.json +3 -3
  14. package/artifacts/EcdsaDkgValidator.json +1 -1
  15. package/artifacts/EcdsaInactivity.json +1 -1
  16. package/artifacts/EcdsaSortitionPool.json +3 -3
  17. package/artifacts/Fraud.json +2 -2
  18. package/artifacts/KeepRegistry.json +1 -1
  19. package/artifacts/KeepStake.json +2 -2
  20. package/artifacts/KeepToken.json +2 -2
  21. package/artifacts/KeepTokenStaking.json +1 -1
  22. package/artifacts/LightRelay.json +18 -18
  23. package/artifacts/LightRelayMaintainerProxy.json +17 -17
  24. package/artifacts/MaintainerProxy.json +19 -19
  25. package/artifacts/MovingFunds.json +2 -2
  26. package/artifacts/NuCypherStakingEscrow.json +1 -1
  27. package/artifacts/NuCypherToken.json +2 -2
  28. package/artifacts/RandomBeacon.json +2 -2
  29. package/artifacts/RandomBeaconChaosnet.json +2 -2
  30. package/artifacts/RandomBeaconGovernance.json +2 -2
  31. package/artifacts/Redemption.json +2 -2
  32. package/artifacts/ReimbursementPool.json +2 -2
  33. package/artifacts/T.json +2 -2
  34. package/artifacts/TBTC.json +3 -3
  35. package/artifacts/TBTCToken.json +3 -3
  36. package/artifacts/TBTCVault.json +23 -23
  37. package/artifacts/TokenStaking.json +1 -1
  38. package/artifacts/TokenholderGovernor.json +9 -9
  39. package/artifacts/TokenholderTimelock.json +8 -8
  40. package/artifacts/VendingMachine.json +3 -3
  41. package/artifacts/VendingMachineKeep.json +1 -1
  42. package/artifacts/VendingMachineNuCypher.json +1 -1
  43. package/artifacts/VendingMachineV2.json +3 -3
  44. package/artifacts/VendingMachineV3.json +3 -3
  45. package/artifacts/WalletProposalValidator.json +389 -0
  46. package/artifacts/WalletRegistry.json +5 -5
  47. package/artifacts/WalletRegistryGovernance.json +2 -2
  48. package/artifacts/Wallets.json +2 -2
  49. package/artifacts/solcInputs/{d2d7e276da75d7184610fe11a4a103b7.json → d46fa1d8846c35adf326ab51a3910266.json} +2 -2
  50. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  51. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  52. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +1 -1
  53. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  54. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  55. package/build/contracts/bridge/BridgeGovernanceParameters.sol/BridgeGovernanceParameters.dbg.json +1 -1
  56. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  57. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  58. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  59. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  60. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  61. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +1 -1
  62. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  63. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  64. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  65. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  66. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  67. package/build/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.dbg.json +1 -1
  68. package/build/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.dbg.json +1 -1
  69. package/build/contracts/bridge/WalletProposalValidator.sol/WalletProposalValidator.dbg.json +4 -0
  70. package/build/contracts/bridge/WalletProposalValidator.sol/WalletProposalValidator.json +287 -0
  71. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  72. package/build/contracts/l2/L2TBTC.sol/L2TBTC.dbg.json +1 -1
  73. package/build/contracts/l2/L2WormholeGateway.sol/IWormholeTokenBridge.dbg.json +1 -1
  74. package/build/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.dbg.json +1 -1
  75. package/build/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.dbg.json +1 -1
  76. package/build/contracts/relay/LightRelay.sol/ILightRelay.dbg.json +1 -1
  77. package/build/contracts/relay/LightRelay.sol/LightRelay.dbg.json +1 -1
  78. package/build/contracts/relay/LightRelay.sol/RelayUtils.dbg.json +1 -1
  79. package/build/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.dbg.json +1 -1
  80. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  81. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +1 -1
  82. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  83. package/build/contracts/vault/TBTCOptimisticMinting.sol/TBTCOptimisticMinting.dbg.json +1 -1
  84. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  85. package/contracts/bridge/{WalletCoordinator.sol → WalletProposalValidator.sol} +55 -448
  86. package/deploy/39_deploy_wallet_proposal_validator.ts +33 -0
  87. package/export/artifacts/contracts/bridge/Bridge.sol/Bridge.json +22 -22
  88. package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +6 -6
  89. package/export/artifacts/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.json +6 -6
  90. package/export/artifacts/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.json +6 -6
  91. package/export/artifacts/contracts/bridge/WalletProposalValidator.sol/WalletProposalValidator.json +22598 -0
  92. package/export/artifacts/contracts/l2/L2TBTC.sol/L2TBTC.json +40 -40
  93. package/export/artifacts/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +47 -47
  94. package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +88 -88
  95. package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +57 -57
  96. package/export/artifacts/contracts/relay/LightRelayMaintainerProxy.sol/LightRelayMaintainerProxy.json +31 -31
  97. package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +2 -2
  98. package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +58 -58
  99. package/export/artifacts/contracts/test/GoerliLightRelay.sol/GoerliLightRelay.json +59 -59
  100. package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +2 -2
  101. package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +59 -59
  102. package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +7 -7
  103. package/export/artifacts/contracts/test/SepoliaLightRelay.sol/SepoliaLightRelay.json +59 -59
  104. package/export/artifacts/contracts/test/SystemTestRelay.sol/SystemTestRelay.json +14 -14
  105. package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +6 -6
  106. package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +8 -8
  107. package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +2 -2
  108. package/export/artifacts/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.json +37 -37
  109. package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +2 -2
  110. package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +11 -11
  111. package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +135 -135
  112. package/export/deploy/39_deploy_wallet_proposal_validator.js +82 -0
  113. package/export/hardhat.config.js +0 -6
  114. package/export/typechain/factories/WalletProposalValidator__factory.js +366 -0
  115. package/export/typechain/index.js +3 -3
  116. package/package.json +1 -1
  117. package/artifacts/WalletCoordinator.json +0 -1107
  118. package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.dbg.json +0 -4
  119. package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +0 -1042
  120. package/deploy/34_deploy_wallet_coordinator.ts +0 -43
  121. package/deploy/35_add_coordinator_address.ts +0 -20
  122. package/deploy/36_transfer_wallet_coordinator_ownership.ts +0 -19
  123. package/deploy/81_upgrade_wallet_coordinator_v2.ts +0 -99
  124. package/export/artifacts/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +0 -33310
  125. package/export/deploy/34_deploy_wallet_coordinator.js +0 -115
  126. package/export/deploy/35_add_coordinator_address.js +0 -60
  127. package/export/deploy/36_transfer_wallet_coordinator_ownership.js +0 -60
  128. package/export/deploy/81_upgrade_wallet_coordinator_v2.js +0 -140
  129. package/export/typechain/factories/WalletCoordinator__factory.js +0 -1121
  130. /package/deploy/{37_deploy_light_relay_maintainer_proxy.ts → 34_deploy_light_relay_maintainer_proxy.ts} +0 -0
  131. /package/deploy/{38_authorize_maintainer_in_light_relay_maintainer_proxy.ts → 35_authorize_maintainer_in_light_relay_maintainer_proxy.ts} +0 -0
  132. /package/deploy/{39_transfer_light_relay_maintainer_proxy_ownership.ts → 36_transfer_light_relay_maintainer_proxy_ownership.ts} +0 -0
  133. /package/deploy/{40_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.ts → 37_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.ts} +0 -0
  134. /package/deploy/{41_authorize_light_relay_maintainer_proxy_in_light_relay.ts → 38_authorize_light_relay_maintainer_proxy_in_light_relay.ts} +0 -0
  135. /package/export/deploy/{37_deploy_light_relay_maintainer_proxy.js → 34_deploy_light_relay_maintainer_proxy.js} +0 -0
  136. /package/export/deploy/{38_authorize_maintainer_in_light_relay_maintainer_proxy.js → 35_authorize_maintainer_in_light_relay_maintainer_proxy.js} +0 -0
  137. /package/export/deploy/{39_transfer_light_relay_maintainer_proxy_ownership.js → 36_transfer_light_relay_maintainer_proxy_ownership.js} +0 -0
  138. /package/export/deploy/{40_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js → 37_authorize_light_relay_maintainer_proxy_in_reimbursement_pool.js} +0 -0
  139. /package/export/deploy/{41_authorize_light_relay_maintainer_proxy_in_light_relay.js → 38_authorize_light_relay_maintainer_proxy_in_light_relay.js} +0 -0
  140. /package/export/typechain/{WalletCoordinator.js → WalletProposalValidator.js} +0 -0
@@ -17,10 +17,6 @@ pragma solidity 0.8.17;
17
17
 
18
18
  import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
19
19
  import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
20
- import "@keep-network/random-beacon/contracts/Reimbursable.sol";
21
- import "@keep-network/random-beacon/contracts/ReimbursementPool.sol";
22
-
23
- import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
24
20
 
25
21
  import "./BitcoinTx.sol";
26
22
  import "./Bridge.sol";
@@ -28,50 +24,14 @@ import "./Deposit.sol";
28
24
  import "./Redemption.sol";
29
25
  import "./Wallets.sol";
30
26
 
31
- /// @title Wallet coordinator.
32
- /// @notice The wallet coordinator contract aims to facilitate the coordination
33
- /// of the off-chain wallet members during complex multi-chain wallet
34
- /// operations like deposit sweeping, redemptions, or moving funds.
35
- /// Such processes involve various moving parts and many steps that each
36
- /// individual wallet member must do. Given the distributed nature of
37
- /// the off-chain wallet software, full off-chain implementation is
38
- /// challenging and prone to errors, especially byzantine faults.
39
- /// This contract provides a single and trusted on-chain coordination
40
- /// point thus taking the riskiest part out of the off-chain software.
41
- /// The off-chain wallet members can focus on the core tasks and do not
42
- /// bother about electing a trusted coordinator or aligning internal
43
- /// states using complex consensus algorithms.
44
- contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
27
+ /// @title Wallet proposal validator.
28
+ /// @notice This contract exposes several view functions allowing to validate
29
+ /// specific wallet action proposals. This contract is non-upgradeable
30
+ /// and does not have any write functions.
31
+ contract WalletProposalValidator {
45
32
  using BTCUtils for bytes;
46
33
  using BytesLib for bytes;
47
34
 
48
- /// @notice Represents wallet action:
49
- enum WalletAction {
50
- /// @dev The wallet does not perform any action.
51
- Idle,
52
- /// @dev The wallet is executing heartbeat.
53
- Heartbeat,
54
- /// @dev The wallet is handling a deposit sweep action.
55
- DepositSweep,
56
- /// @dev The wallet is handling a redemption action.
57
- Redemption,
58
- /// @dev The wallet is handling a moving funds action.
59
- MovingFunds,
60
- /// @dev The wallet is handling a moved funds sweep action.
61
- MovedFundsSweep
62
- }
63
-
64
- /// @notice Holds information about a wallet time lock.
65
- struct WalletLock {
66
- /// @notice A UNIX timestamp defining the moment until which the wallet
67
- /// is locked and cannot receive new proposals. The value of 0
68
- /// means the wallet is not locked and can receive a proposal
69
- /// at any time.
70
- uint32 expiresAt;
71
- /// @notice The wallet action being the cause of the lock.
72
- WalletAction cause;
73
- }
74
-
75
35
  /// @notice Helper structure representing a deposit sweep proposal.
76
36
  struct DepositSweepProposal {
77
37
  // 20-byte public key hash of the target wallet.
@@ -134,54 +94,28 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
134
94
  uint256 redemptionTxFee;
135
95
  }
136
96
 
137
- /// @notice Mapping that holds addresses allowed to submit proposals and
138
- /// request heartbeats.
139
- mapping(address => bool) public isCoordinator;
140
-
141
- /// @notice Mapping that holds wallet time locks. The key is a 20-byte
142
- /// wallet public key hash.
143
- mapping(bytes20 => WalletLock) public walletLock;
97
+ /// @notice Helper structure representing a heartbeat proposal.
98
+ struct HeartbeatProposal {
99
+ // 20-byte public key hash of the target wallet.
100
+ bytes20 walletPubKeyHash;
101
+ // Message to be signed as part of the heartbeat.
102
+ bytes message;
103
+ }
144
104
 
145
105
  /// @notice Handle to the Bridge contract.
146
- Bridge public bridge;
147
-
148
- /// @notice Determines the wallet heartbeat request validity time. In other
149
- /// words, this is the worst-case time for a wallet heartbeat
150
- /// during which the wallet is busy and canot take other actions.
151
- /// This is also the duration of the time lock applied to the wallet
152
- /// once a new heartbeat request is submitted.
153
- ///
154
- /// For example, if a deposit sweep proposal was submitted at
155
- /// 2 pm and heartbeatRequestValidity is 1 hour, the next request or
156
- /// proposal (of any type) can be submitted after 3 pm.
157
- uint32 public heartbeatRequestValidity;
158
-
159
- /// @notice Gas that is meant to balance the heartbeat request overall cost.
160
- /// Can be updated by the owner based on the current conditions.
161
- uint32 public heartbeatRequestGasOffset;
162
-
163
- /// @notice Determines the deposit sweep proposal validity time. In other
164
- /// words, this is the worst-case time for a deposit sweep during
165
- /// which the wallet is busy and cannot take another actions. This
166
- /// is also the duration of the time lock applied to the wallet
167
- /// once a new deposit sweep proposal is submitted.
168
- ///
169
- /// For example, if a deposit sweep proposal was submitted at
170
- /// 2 pm and depositSweepProposalValidity is 4 hours, the next
171
- /// proposal (of any type) can be submitted after 6 pm.
172
- uint32 public depositSweepProposalValidity;
106
+ Bridge public immutable bridge;
173
107
 
174
108
  /// @notice The minimum time that must elapse since the deposit reveal
175
109
  /// before a deposit becomes eligible for a deposit sweep.
176
110
  ///
177
- /// For example, if a deposit was revealed at 9 am and depositMinAge
111
+ /// For example, if a deposit was revealed at 9 am and DEPOSIT_MIN_AGE
178
112
  /// is 2 hours, the deposit is eligible for sweep after 11 am.
179
113
  ///
180
114
  /// @dev Forcing deposit minimum age ensures block finality for Ethereum.
181
115
  /// In the happy path case, i.e. where the deposit is revealed immediately
182
116
  /// after being broadcast on the Bitcoin network, the minimum age
183
117
  /// check also ensures block finality for Bitcoin.
184
- uint32 public depositMinAge;
118
+ uint32 public constant DEPOSIT_MIN_AGE = 2 hours;
185
119
 
186
120
  /// @notice Each deposit can be technically swept until it reaches its
187
121
  /// refund timestamp after which it can be taken back by the depositor.
@@ -196,39 +130,23 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
196
130
  /// the deposit becomes refundable.
197
131
  ///
198
132
  /// For example, if a deposit becomes refundable after 8 pm and
199
- /// depositRefundSafetyMargin is 6 hours, the deposit is valid for
133
+ /// DEPOSIT_REFUND_SAFETY_MARGIN is 6 hours, the deposit is valid
200
134
  /// for a sweep only before 2 pm.
201
- uint32 public depositRefundSafetyMargin;
135
+ uint32 public constant DEPOSIT_REFUND_SAFETY_MARGIN = 24 hours;
202
136
 
203
137
  /// @notice The maximum count of deposits that can be swept within a
204
138
  /// single sweep.
205
- uint16 public depositSweepMaxSize;
206
-
207
- /// @notice Gas that is meant to balance the deposit sweep proposal
208
- /// submission overall cost. Can be updated by the owner based on
209
- /// the current conditions.
210
- uint32 public depositSweepProposalSubmissionGasOffset;
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;
139
+ uint16 public constant DEPOSIT_SWEEP_MAX_SIZE = 20;
222
140
 
223
141
  /// @notice The minimum time that must elapse since the redemption request
224
142
  /// creation before a request becomes eligible for a processing.
225
143
  ///
226
144
  /// 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.
145
+ /// REDEMPTION_REQUEST_MIN_AGE is 2 hours, the request is
146
+ /// eligible for processing after 11 am.
229
147
  ///
230
148
  /// @dev Forcing request minimum age ensures block finality for Ethereum.
231
- uint32 public redemptionRequestMinAge;
149
+ uint32 public constant REDEMPTION_REQUEST_MIN_AGE = 600; // 10 minutes or ~50 blocks.
232
150
 
233
151
  /// @notice Each redemption request can be technically handled until it
234
152
  /// reaches its timeout timestamp after which it can be reported
@@ -245,276 +163,16 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
245
163
  /// point after which the request can be reported as timed out.
246
164
  ///
247
165
  /// 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;
166
+ /// REDEMPTION_REQUEST_TIMEOUT_SAFETY_MARGIN is 2 hours, the
167
+ /// request is valid for processing only before 6 pm.
168
+ uint32 public constant REDEMPTION_REQUEST_TIMEOUT_SAFETY_MARGIN = 2 hours;
251
169
 
252
170
  /// @notice The maximum count of redemption requests that can be processed
253
171
  /// 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
-
261
- event CoordinatorAdded(address indexed coordinator);
262
-
263
- event CoordinatorRemoved(address indexed coordinator);
264
-
265
- event WalletManuallyUnlocked(bytes20 indexed walletPubKeyHash);
266
-
267
- event HeartbeatRequestParametersUpdated(
268
- uint32 heartbeatRequestValidity,
269
- uint32 heartbeatRequestGasOffset
270
- );
271
-
272
- event HeartbeatRequestSubmitted(
273
- bytes20 walletPubKeyHash,
274
- bytes message,
275
- address indexed coordinator
276
- );
277
-
278
- event DepositSweepProposalParametersUpdated(
279
- uint32 depositSweepProposalValidity,
280
- uint32 depositMinAge,
281
- uint32 depositRefundSafetyMargin,
282
- uint16 depositSweepMaxSize,
283
- uint32 depositSweepProposalSubmissionGasOffset
284
- );
285
-
286
- event DepositSweepProposalSubmitted(
287
- DepositSweepProposal proposal,
288
- address indexed coordinator
289
- );
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
-
304
- modifier onlyCoordinator() {
305
- require(isCoordinator[msg.sender], "Caller is not a coordinator");
306
- _;
307
- }
308
-
309
- modifier onlyAfterWalletLock(bytes20 walletPubKeyHash) {
310
- require(
311
- /* solhint-disable-next-line not-rely-on-time */
312
- block.timestamp > walletLock[walletPubKeyHash].expiresAt,
313
- "Wallet locked"
314
- );
315
- _;
316
- }
317
-
318
- modifier onlyReimbursableAdmin() override {
319
- require(owner() == msg.sender, "Caller is not the owner");
320
- _;
321
- }
322
-
323
- function initialize(Bridge _bridge) external initializer {
324
- __Ownable_init();
172
+ uint16 public constant REDEMPTION_MAX_SIZE = 20;
325
173
 
174
+ constructor(Bridge _bridge) {
326
175
  bridge = _bridge;
327
- // Pre-fetch addresses to save gas later.
328
- (, , , reimbursementPool) = _bridge.contractReferences();
329
-
330
- heartbeatRequestValidity = 1 hours;
331
- heartbeatRequestGasOffset = 10_000;
332
-
333
- depositSweepProposalValidity = 4 hours;
334
- depositMinAge = 2 hours;
335
- depositRefundSafetyMargin = 24 hours;
336
- depositSweepMaxSize = 5;
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;
344
- }
345
-
346
- /// @notice Adds the given address to the set of coordinator addresses.
347
- /// @param coordinator Address of the new coordinator.
348
- /// @dev Requirements:
349
- /// - The caller must be the owner,
350
- /// - The `coordinator` must not be an existing coordinator.
351
- function addCoordinator(address coordinator) external onlyOwner {
352
- require(
353
- !isCoordinator[coordinator],
354
- "This address is already a coordinator"
355
- );
356
- isCoordinator[coordinator] = true;
357
- emit CoordinatorAdded(coordinator);
358
- }
359
-
360
- /// @notice Removes the given address from the set of coordinator addresses.
361
- /// @param coordinator Address of the existing coordinator.
362
- /// @dev Requirements:
363
- /// - The caller must be the owner,
364
- /// - The `coordinator` must be an existing coordinator.
365
- function removeCoordinator(address coordinator) external onlyOwner {
366
- require(
367
- isCoordinator[coordinator],
368
- "This address is not a coordinator"
369
- );
370
- delete isCoordinator[coordinator];
371
- emit CoordinatorRemoved(coordinator);
372
- }
373
-
374
- /// @notice Allows to unlock the given wallet before their time lock expires.
375
- /// This function should be used in exceptional cases where
376
- /// something went wrong and there is a need to unlock the wallet
377
- /// without waiting.
378
- /// @param walletPubKeyHash 20-byte public key hash of the wallet
379
- /// @dev Requirements:
380
- /// - The caller must be the owner.
381
- function unlockWallet(bytes20 walletPubKeyHash) external onlyOwner {
382
- // Just in case, allow the owner to unlock the wallet earlier.
383
- walletLock[walletPubKeyHash] = WalletLock(0, WalletAction.Idle);
384
- emit WalletManuallyUnlocked(walletPubKeyHash);
385
- }
386
-
387
- /// @notice Updates parameters related to heartbeat request.
388
- /// @param _heartbeatRequestValidity The new value of `heartbeatRequestValidity`.
389
- /// @param _heartbeatRequestGasOffset The new value of `heartbeatRequestGasOffset`.
390
- /// @dev Requirements:
391
- /// - The caller must be the owner.
392
- function updateHeartbeatRequestParameters(
393
- uint32 _heartbeatRequestValidity,
394
- uint32 _heartbeatRequestGasOffset
395
- ) external onlyOwner {
396
- heartbeatRequestValidity = _heartbeatRequestValidity;
397
- heartbeatRequestGasOffset = _heartbeatRequestGasOffset;
398
- emit HeartbeatRequestParametersUpdated(
399
- _heartbeatRequestValidity,
400
- _heartbeatRequestGasOffset
401
- );
402
- }
403
-
404
- /// @notice Updates parameters related to deposit sweep proposal.
405
- /// @param _depositSweepProposalValidity The new value of `depositSweepProposalValidity`.
406
- /// @param _depositMinAge The new value of `depositMinAge`.
407
- /// @param _depositRefundSafetyMargin The new value of `depositRefundSafetyMargin`.
408
- /// @param _depositSweepMaxSize The new value of `depositSweepMaxSize`.
409
- /// @dev Requirements:
410
- /// - The caller must be the owner.
411
- function updateDepositSweepProposalParameters(
412
- uint32 _depositSweepProposalValidity,
413
- uint32 _depositMinAge,
414
- uint32 _depositRefundSafetyMargin,
415
- uint16 _depositSweepMaxSize,
416
- uint32 _depositSweepProposalSubmissionGasOffset
417
- ) external onlyOwner {
418
- depositSweepProposalValidity = _depositSweepProposalValidity;
419
- depositMinAge = _depositMinAge;
420
- depositRefundSafetyMargin = _depositRefundSafetyMargin;
421
- depositSweepMaxSize = _depositSweepMaxSize;
422
- depositSweepProposalSubmissionGasOffset = _depositSweepProposalSubmissionGasOffset;
423
-
424
- emit DepositSweepProposalParametersUpdated(
425
- _depositSweepProposalValidity,
426
- _depositMinAge,
427
- _depositRefundSafetyMargin,
428
- _depositSweepMaxSize,
429
- _depositSweepProposalSubmissionGasOffset
430
- );
431
- }
432
-
433
- /// @notice Submits a heartbeat request from the wallet. Locks the wallet
434
- /// for a specific time, equal to the request validity period.
435
- /// This function validates the proposed heartbeat messge to see
436
- /// if it matches the heartbeat format expected by the Bridge.
437
- /// @param walletPubKeyHash 20-byte public key hash of the wallet that is
438
- /// supposed to execute the heartbeat.
439
- /// @param message The proposed heartbeat message for the wallet to sign.
440
- /// @dev Requirements:
441
- /// - The caller is a coordinator,
442
- /// - The wallet is not time-locked,
443
- /// - The message to sign is a valid heartbeat message.
444
- function requestHeartbeat(bytes20 walletPubKeyHash, bytes calldata message)
445
- public
446
- onlyCoordinator
447
- onlyAfterWalletLock(walletPubKeyHash)
448
- {
449
- require(
450
- Heartbeat.isValidHeartbeatMessage(message),
451
- "Not a valid heartbeat message"
452
- );
453
-
454
- walletLock[walletPubKeyHash] = WalletLock(
455
- /* solhint-disable-next-line not-rely-on-time */
456
- uint32(block.timestamp) + heartbeatRequestValidity,
457
- WalletAction.Heartbeat
458
- );
459
-
460
- emit HeartbeatRequestSubmitted(walletPubKeyHash, message, msg.sender);
461
- }
462
-
463
- /// @notice Wraps `requestHeartbeat` call and reimburses the caller's
464
- /// transaction cost.
465
- /// @dev See `requestHeartbeat` function documentation.
466
- function requestHeartbeatWithReimbursement(
467
- bytes20 walletPubKeyHash,
468
- bytes calldata message
469
- ) external {
470
- uint256 gasStart = gasleft();
471
-
472
- requestHeartbeat(walletPubKeyHash, message);
473
-
474
- reimbursementPool.refund(
475
- (gasStart - gasleft()) + heartbeatRequestGasOffset,
476
- msg.sender
477
- );
478
- }
479
-
480
- /// @notice Submits a deposit sweep proposal. Locks the target wallet
481
- /// for a specific time, equal to the proposal validity period.
482
- /// This function does not store the proposal in the state but
483
- /// just emits an event that serves as a guiding light for wallet
484
- /// off-chain members. Wallet members are supposed to validate
485
- /// the proposal on their own, before taking any action.
486
- /// @param proposal The deposit sweep proposal
487
- /// @dev Requirements:
488
- /// - The caller is a coordinator,
489
- /// - The wallet is not time-locked.
490
- function submitDepositSweepProposal(DepositSweepProposal calldata proposal)
491
- public
492
- onlyCoordinator
493
- onlyAfterWalletLock(proposal.walletPubKeyHash)
494
- {
495
- walletLock[proposal.walletPubKeyHash] = WalletLock(
496
- /* solhint-disable-next-line not-rely-on-time */
497
- uint32(block.timestamp) + depositSweepProposalValidity,
498
- WalletAction.DepositSweep
499
- );
500
-
501
- emit DepositSweepProposalSubmitted(proposal, msg.sender);
502
- }
503
-
504
- /// @notice Wraps `submitDepositSweepProposal` call and reimburses the
505
- /// caller's transaction cost.
506
- /// @dev See `submitDepositSweepProposal` function documentation.
507
- function submitDepositSweepProposalWithReimbursement(
508
- DepositSweepProposal calldata proposal
509
- ) external {
510
- uint256 gasStart = gasleft();
511
-
512
- submitDepositSweepProposal(proposal);
513
-
514
- reimbursementPool.refund(
515
- (gasStart - gasleft()) + depositSweepProposalSubmissionGasOffset,
516
- msg.sender
517
- );
518
176
  }
519
177
 
520
178
  /// @notice View function encapsulating the main rules of a valid deposit
@@ -535,7 +193,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
535
193
  /// @dev Requirements:
536
194
  /// - The target wallet must be in the Live state,
537
195
  /// - The number of deposits included in the sweep must be in
538
- /// the range [1, `depositSweepMaxSize`],
196
+ /// the range [1, `DEPOSIT_SWEEP_MAX_SIZE`],
539
197
  /// - The length of `depositsExtraInfo` array must be equal to the
540
198
  /// length of `proposal.depositsKeys`, i.e. each deposit must
541
199
  /// have exactly one set of corresponding extra data,
@@ -543,7 +201,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
543
201
  /// - The proposed maximum per-deposit sweep tx fee must be lesser than
544
202
  /// or equal the maximum fee allowed by the Bridge (`Bridge.depositTxMaxFee`),
545
203
  /// - Each deposit must be revealed to the Bridge,
546
- /// - Each deposit must be old enough, i.e. at least `depositMinAge`
204
+ /// - Each deposit must be old enough, i.e. at least `DEPOSIT_MIN_AGE
547
205
  /// elapsed since their reveal time,
548
206
  /// - Each deposit must not be swept yet,
549
207
  /// - Each deposit must have valid extra data (see `validateDepositExtraInfo`),
@@ -568,7 +226,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
568
226
  require(proposal.depositsKeys.length > 0, "Sweep below the min size");
569
227
 
570
228
  require(
571
- proposal.depositsKeys.length <= depositSweepMaxSize,
229
+ proposal.depositsKeys.length <= DEPOSIT_SWEEP_MAX_SIZE,
572
230
  "Sweep exceeds the max size"
573
231
  );
574
232
 
@@ -607,7 +265,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
607
265
 
608
266
  require(
609
267
  /* solhint-disable-next-line not-rely-on-time */
610
- block.timestamp > depositRequest.revealedAt + depositMinAge,
268
+ block.timestamp > depositRequest.revealedAt + DEPOSIT_MIN_AGE,
611
269
  "Deposit min age not achieved yet"
612
270
  );
613
271
 
@@ -625,7 +283,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
625
283
  require(
626
284
  /* solhint-disable-next-line not-rely-on-time */
627
285
  block.timestamp <
628
- depositRefundableTimestamp - depositRefundSafetyMargin,
286
+ depositRefundableTimestamp - DEPOSIT_REFUND_SAFETY_MARGIN,
629
287
  "Deposit refund safety margin is not preserved"
630
288
  );
631
289
 
@@ -787,78 +445,6 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
787
445
  revert("Extra info funding output script does not match");
788
446
  }
789
447
 
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
448
  /// @notice View function encapsulating the main rules of a valid redemption
863
449
  /// proposal. This function is meant to facilitate the off-chain
864
450
  /// validation of the incoming proposals. Thanks to it, most
@@ -897,7 +483,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
897
483
  require(requestsCount > 0, "Redemption below the min size");
898
484
 
899
485
  require(
900
- requestsCount <= redemptionMaxSize,
486
+ requestsCount <= REDEMPTION_MAX_SIZE,
901
487
  "Redemption exceeds the max size"
902
488
  );
903
489
 
@@ -960,7 +546,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
960
546
  require(
961
547
  /* solhint-disable-next-line not-rely-on-time */
962
548
  block.timestamp >
963
- redemptionRequest.requestedAt + redemptionRequestMinAge,
549
+ redemptionRequest.requestedAt + REDEMPTION_REQUEST_MIN_AGE,
964
550
  "Redemption request min age not achieved yet"
965
551
  );
966
552
 
@@ -971,7 +557,7 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
971
557
  require(
972
558
  /* solhint-disable-next-line not-rely-on-time */
973
559
  block.timestamp <
974
- requestTimeout - redemptionRequestTimeoutSafetyMargin,
560
+ requestTimeout - REDEMPTION_REQUEST_TIMEOUT_SAFETY_MARGIN,
975
561
  "Redemption request timeout safety margin is not preserved"
976
562
  );
977
563
 
@@ -1000,4 +586,25 @@ contract WalletCoordinator is OwnableUpgradeable, Reimbursable {
1000
586
 
1001
587
  return true;
1002
588
  }
589
+
590
+ /// @notice View function encapsulating the main rules of a valid heartbeat
591
+ /// proposal. This function is meant to facilitate the off-chain
592
+ /// validation of the incoming proposals. Thanks to it, most
593
+ /// of the work can be done using a single readonly contract call.
594
+ /// @param proposal The heartbeat proposal to validate.
595
+ /// @return True if the proposal is valid. Reverts otherwise.
596
+ /// @dev Requirements:
597
+ /// - The message to sign is a valid heartbeat message.
598
+ function validateHeartbeatProposal(HeartbeatProposal calldata proposal)
599
+ external
600
+ view
601
+ returns (bool)
602
+ {
603
+ require(
604
+ Heartbeat.isValidHeartbeatMessage(proposal.message),
605
+ "Not a valid heartbeat message"
606
+ );
607
+
608
+ return true;
609
+ }
1003
610
  }
@@ -0,0 +1,33 @@
1
+ import { HardhatRuntimeEnvironment } from "hardhat/types"
2
+ import { DeployFunction } from "hardhat-deploy/types"
3
+
4
+ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
5
+ const { deployments, helpers, getNamedAccounts } = hre
6
+ const { deploy } = deployments
7
+ const { deployer } = await getNamedAccounts()
8
+
9
+ const Bridge = await deployments.get("Bridge")
10
+
11
+ const walletProposalValidator = await deploy("WalletProposalValidator", {
12
+ from: deployer,
13
+ args: [Bridge.address],
14
+ log: true,
15
+ waitConfirmations: 1,
16
+ })
17
+
18
+ if (hre.network.tags.etherscan) {
19
+ await helpers.etherscan.verify(walletProposalValidator)
20
+ }
21
+
22
+ if (hre.network.tags.tenderly) {
23
+ await hre.tenderly.verify({
24
+ name: "WalletProposalValidator",
25
+ address: walletProposalValidator.address,
26
+ })
27
+ }
28
+ }
29
+
30
+ export default func
31
+
32
+ func.tags = ["WalletProposalValidator"]
33
+ func.dependencies = ["Bridge"]