@keep-network/tbtc-v2 1.3.0-dev.0 → 1.3.0-dev.1
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/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/WalletRegistry.json +5 -5
- package/artifacts/WalletRegistryGovernance.json +2 -2
- package/artifacts/Wallets.json +2 -2
- package/artifacts/solcInputs/{8445e3932e9b9683df0e9fb9258d7b11.json → eaed73ec5af35f1b3d40df457dd99d0b.json} +3 -0
- 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 +4 -0
- package/build/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +727 -0
- 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/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 +648 -0
- package/export/artifacts/@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol/BTCUtils.json +20 -20
- package/export/artifacts/@keep-network/ecdsa/contracts/EcdsaDkgValidator.sol/EcdsaDkgValidator.json +1986 -1986
- package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol/EcdsaDkg.json +20 -20
- package/export/artifacts/@keep-network/ecdsa/contracts/libraries/EcdsaInactivity.sol/EcdsaInactivity.json +1836 -1836
- package/export/artifacts/@keep-network/random-beacon/contracts/ReimbursementPool.sol/ReimbursementPool.json +545 -545
- package/export/artifacts/@keep-network/sortition-pools/contracts/Chaosnet.sol/Chaosnet.json +506 -506
- package/export/artifacts/@keep-network/sortition-pools/contracts/Rewards.sol/Rewards.json +23 -23
- package/export/artifacts/@keep-network/sortition-pools/contracts/SortitionPool.sol/SortitionPool.json +2456 -2456
- package/export/artifacts/@keep-network/sortition-pools/contracts/SortitionTree.sol/SortitionTree.json +465 -465
- package/export/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json +568 -568
- package/export/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json +603 -603
- package/export/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +1001 -1001
- package/export/artifacts/@openzeppelin/contracts/token/ERC721/ERC721.sol/ERC721.json +1877 -1877
- package/export/artifacts/@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol/ERC20Upgradeable.json +654 -654
- package/export/artifacts/@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol/ERC20WithPermit.json +2534 -2534
- package/export/artifacts/@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol/MisfundRecovery.json +736 -736
- package/export/artifacts/contracts/bank/Bank.sol/Bank.json +1872 -1872
- package/export/artifacts/contracts/bridge/Bridge.sol/Bridge.json +7083 -7083
- package/export/artifacts/contracts/bridge/VendingMachine.sol/VendingMachine.json +1257 -1257
- package/export/artifacts/contracts/bridge/VendingMachineV2.sol/VendingMachineV2.json +972 -972
- package/export/artifacts/contracts/bridge/VendingMachineV3.sol/VendingMachineV3.json +1122 -1122
- package/export/artifacts/contracts/bridge/WalletCoordinator.sol/WalletCoordinator.json +25433 -0
- package/export/artifacts/contracts/l2/L2TBTC.sol/L2TBTC.json +3002 -3002
- package/export/artifacts/contracts/l2/L2WormholeGateway.sol/L2WormholeGateway.json +2472 -2472
- package/export/artifacts/contracts/maintainer/MaintainerProxy.sol/MaintainerProxy.json +2393 -2393
- package/export/artifacts/contracts/relay/LightRelay.sol/LightRelay.json +1922 -1922
- package/export/artifacts/contracts/test/BankStub.sol/BankStub.json +1874 -1874
- package/export/artifacts/contracts/test/BridgeStub.sol/BridgeStub.json +8122 -8122
- package/export/artifacts/contracts/test/GoerliLightRelay.sol/GoerliLightRelay.json +1922 -1922
- package/export/artifacts/contracts/test/HeartbeatStub.sol/HeartbeatStub.json +130 -130
- package/export/artifacts/contracts/test/LightRelayStub.sol/LightRelayStub.json +2047 -2047
- package/export/artifacts/contracts/test/ReceiveApprovalStub.sol/ReceiveApprovalStub.json +354 -354
- package/export/artifacts/contracts/test/SystemTestRelay.sol/SystemTestRelay.json +568 -568
- package/export/artifacts/contracts/test/TestERC20.sol/TestERC20.json +2291 -2291
- package/export/artifacts/contracts/test/TestERC721.sol/TestERC721.json +1698 -1698
- package/export/artifacts/contracts/test/TestEcdsaLib.sol/TestEcdsaLib.json +189 -189
- package/export/artifacts/contracts/test/WormholeBridgeStub.sol/WormholeBridgeStub.json +1447 -1447
- package/export/artifacts/contracts/token/TBTC.sol/TBTC.json +2847 -2847
- package/export/artifacts/contracts/vault/DonationVault.sol/DonationVault.json +858 -858
- package/export/artifacts/contracts/vault/TBTCVault.sol/TBTCVault.json +3476 -3476
- package/export/typechain/WalletCoordinator.js +2 -0
- package/export/typechain/factories/WalletCoordinator__factory.js +806 -0
- package/export/typechain/index.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-only
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity 0.8.17;
|
|
17
|
+
|
|
18
|
+
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
|
|
19
|
+
import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
|
|
20
|
+
import {IWalletRegistry as EcdsaWalletRegistry} from "@keep-network/ecdsa/contracts/api/IWalletRegistry.sol";
|
|
21
|
+
import "@keep-network/random-beacon/contracts/Reimbursable.sol";
|
|
22
|
+
import "@keep-network/random-beacon/contracts/ReimbursementPool.sol";
|
|
23
|
+
|
|
24
|
+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
25
|
+
|
|
26
|
+
import "./BitcoinTx.sol";
|
|
27
|
+
import "./Bridge.sol";
|
|
28
|
+
import "./Deposit.sol";
|
|
29
|
+
import "./Wallets.sol";
|
|
30
|
+
|
|
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 {
|
|
45
|
+
using BTCUtils for bytes;
|
|
46
|
+
using BytesLib for bytes;
|
|
47
|
+
|
|
48
|
+
/// @notice Helper structure carrying data necessary to validate the wallet
|
|
49
|
+
/// membership of the caller.
|
|
50
|
+
struct WalletMemberContext {
|
|
51
|
+
uint32[] walletMembersIDs;
|
|
52
|
+
uint256 walletMemberIndex;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// @notice Helper structure representing a deposit sweep proposal.
|
|
56
|
+
struct DepositSweepProposal {
|
|
57
|
+
// 20-byte public key hash of the target wallet.
|
|
58
|
+
bytes20 walletPubKeyHash;
|
|
59
|
+
// Deposits that should be part of the sweep.
|
|
60
|
+
DepositKey[] depositsKeys;
|
|
61
|
+
// Proposed BTC fee for the entire transaction.
|
|
62
|
+
uint256 sweepTxFee;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// @notice Helper structure representing a plain-text deposit key.
|
|
66
|
+
/// Each deposit can be identified by their 32-byte funding
|
|
67
|
+
/// transaction hash (Bitcoin internal byte order) an the funding
|
|
68
|
+
/// output index (0-based).
|
|
69
|
+
/// @dev Do not confuse this structure with the deposit key used within the
|
|
70
|
+
/// Bridge contract to store deposits. Here we have the plain-text
|
|
71
|
+
/// components of the key while the Bridge uses a uint representation of
|
|
72
|
+
/// keccak256(fundingTxHash | fundingOutputIndex) for gas efficiency.
|
|
73
|
+
struct DepositKey {
|
|
74
|
+
bytes32 fundingTxHash;
|
|
75
|
+
uint32 fundingOutputIndex;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// @notice Helper structure holding deposit extra data required during
|
|
79
|
+
/// deposit sweep proposal validation. Basically, this structure
|
|
80
|
+
/// is a combination of BitcoinTx.Info and relevant parts of
|
|
81
|
+
/// Deposit.DepositRevealInfo.
|
|
82
|
+
/// @dev These data can be pulled from respective `DepositRevealed` events
|
|
83
|
+
/// emitted by the `Bridge.revealDeposit` function. The `fundingTx`
|
|
84
|
+
/// field must be taken directly from the Bitcoin chain, using the
|
|
85
|
+
/// `DepositRevealed.fundingTxHash` as transaction identifier.
|
|
86
|
+
struct DepositExtraInfo {
|
|
87
|
+
BitcoinTx.Info fundingTx;
|
|
88
|
+
bytes8 blindingFactor;
|
|
89
|
+
bytes20 walletPubKeyHash;
|
|
90
|
+
bytes20 refundPubKeyHash;
|
|
91
|
+
bytes4 refundLocktime;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// @notice Mapping that holds addresses allowed to submit proposals.
|
|
95
|
+
mapping(address => bool) public isProposalSubmitter;
|
|
96
|
+
|
|
97
|
+
/// @notice Mapping that holds wallet time locks. The key is a 20-byte
|
|
98
|
+
/// wallet public key hash. The value is a UNIX timestamp defining
|
|
99
|
+
/// the moment until which the wallet is locked and cannot receive
|
|
100
|
+
/// new proposals. The value of 0 means the wallet is not locked
|
|
101
|
+
/// and can receive a proposal at any time.
|
|
102
|
+
mapping(bytes20 => uint32) public walletLock;
|
|
103
|
+
|
|
104
|
+
/// @notice Handle to the Bridge contract.
|
|
105
|
+
Bridge public bridge;
|
|
106
|
+
|
|
107
|
+
/// @notice Handle to the WalletRegistry contract.
|
|
108
|
+
EcdsaWalletRegistry public walletRegistry;
|
|
109
|
+
|
|
110
|
+
/// @notice Determines the deposit sweep proposal validity time. In other
|
|
111
|
+
/// words, this is the worst-case time for a deposit sweep during
|
|
112
|
+
/// which the wallet is busy and cannot take another actions. This
|
|
113
|
+
/// is also the duration of the time lock applied to the wallet
|
|
114
|
+
/// once a new deposit sweep proposal is submitted.
|
|
115
|
+
///
|
|
116
|
+
/// For example, if a deposit sweep proposal was submitted at
|
|
117
|
+
/// 2 pm and depositSweepProposalValidity is 4 hours, the next
|
|
118
|
+
/// proposal (of any type) can be submitted after 6 pm.
|
|
119
|
+
uint32 public depositSweepProposalValidity;
|
|
120
|
+
|
|
121
|
+
/// @notice The minimum time that must elapse since the deposit reveal
|
|
122
|
+
/// before a deposit becomes eligible for a deposit sweep.
|
|
123
|
+
///
|
|
124
|
+
/// For example, if a deposit was revealed at 9 am and depositMinAge
|
|
125
|
+
/// is 2 hours, the deposit is eligible for sweep after 11 am.
|
|
126
|
+
///
|
|
127
|
+
/// @dev Forcing deposit minimum age ensures block finality for Ethereum.
|
|
128
|
+
/// In the happy path case, i.e. where the deposit is revealed immediately
|
|
129
|
+
/// after being broadcast on the Bitcoin network, the minimum age
|
|
130
|
+
/// check also ensures block finality for Bitcoin.
|
|
131
|
+
uint32 public depositMinAge;
|
|
132
|
+
|
|
133
|
+
/// @notice Each deposit can be technically swept until it reaches its
|
|
134
|
+
/// refund timestamp after which it can be taken back by the depositor.
|
|
135
|
+
/// However, allowing the wallet to sweep deposits that are close
|
|
136
|
+
/// to their refund timestamp may cause a race between the wallet
|
|
137
|
+
/// and the depositor. In result, the wallet may sign an invalid
|
|
138
|
+
/// sweep transaction that aims to sweep an already refunded deposit.
|
|
139
|
+
/// Such tx signature may be used to create an undefeatable fraud
|
|
140
|
+
/// challenge against the wallet. In order to mitigate that problem,
|
|
141
|
+
/// this parameter determines a safety margin that puts the latest
|
|
142
|
+
/// moment a deposit can be swept far before the point after which
|
|
143
|
+
/// the deposit becomes refundable.
|
|
144
|
+
///
|
|
145
|
+
/// For example, if a deposit becomes refundable after 8 pm and
|
|
146
|
+
/// depositRefundSafetyMargin is 6 hours, the deposit is valid for
|
|
147
|
+
/// for a sweep only before 2 pm.
|
|
148
|
+
uint32 public depositRefundSafetyMargin;
|
|
149
|
+
|
|
150
|
+
/// @notice The maximum count of deposits that can be swept within a
|
|
151
|
+
/// single sweep.
|
|
152
|
+
uint16 public depositSweepMaxSize;
|
|
153
|
+
|
|
154
|
+
/// @notice Gas that is meant to balance the deposit sweep proposal
|
|
155
|
+
/// submission overall cost. Can be updated by the owner based on
|
|
156
|
+
/// the current market conditions.
|
|
157
|
+
uint32 public depositSweepProposalSubmissionGasOffset;
|
|
158
|
+
|
|
159
|
+
event ProposalSubmitterAdded(address indexed proposalSubmitter);
|
|
160
|
+
|
|
161
|
+
event ProposalSubmitterRemoved(address indexed proposalSubmitter);
|
|
162
|
+
|
|
163
|
+
event WalletManuallyUnlocked(bytes20 indexed walletPubKeyHash);
|
|
164
|
+
|
|
165
|
+
event DepositSweepProposalValidityUpdated(
|
|
166
|
+
uint32 depositSweepProposalValidity
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
event DepositMinAgeUpdated(uint32 depositMinAge);
|
|
170
|
+
|
|
171
|
+
event DepositRefundSafetyMarginUpdated(uint32 depositRefundSafetyMargin);
|
|
172
|
+
|
|
173
|
+
event DepositSweepMaxSizeUpdated(uint16 depositSweepMaxSize);
|
|
174
|
+
|
|
175
|
+
event DepositSweepProposalSubmissionGasOffsetUpdated(
|
|
176
|
+
uint32 depositSweepProposalSubmissionGasOffset
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
event DepositSweepProposalSubmitted(
|
|
180
|
+
DepositSweepProposal proposal,
|
|
181
|
+
address indexed proposalSubmitter
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// TODO: Enhance this modifier by adding the coordinator role check. See:
|
|
185
|
+
// https://github.com/keep-network/tbtc-v2/pull/575#discussion_r1151564813
|
|
186
|
+
modifier onlyProposalSubmitterOrWalletMember(
|
|
187
|
+
bytes20 walletPubKeyHash,
|
|
188
|
+
WalletMemberContext calldata walletMemberContext
|
|
189
|
+
) {
|
|
190
|
+
bool proposalSubmitter = isProposalSubmitter[msg.sender];
|
|
191
|
+
bool walletMember = false;
|
|
192
|
+
|
|
193
|
+
if (!proposalSubmitter) {
|
|
194
|
+
bytes32 ecdsaWalletID = bridge
|
|
195
|
+
.wallets(walletPubKeyHash)
|
|
196
|
+
.ecdsaWalletID;
|
|
197
|
+
|
|
198
|
+
walletMember = walletRegistry.isWalletMember(
|
|
199
|
+
ecdsaWalletID,
|
|
200
|
+
walletMemberContext.walletMembersIDs,
|
|
201
|
+
msg.sender,
|
|
202
|
+
walletMemberContext.walletMemberIndex
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
require(
|
|
207
|
+
proposalSubmitter || walletMember,
|
|
208
|
+
"Caller is neither a proposal submitter nor a wallet member"
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
_;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
modifier onlyAfterWalletLock(bytes20 walletPubKeyHash) {
|
|
215
|
+
require(
|
|
216
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
217
|
+
block.timestamp > walletLock[walletPubKeyHash],
|
|
218
|
+
"Wallet locked"
|
|
219
|
+
);
|
|
220
|
+
_;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
modifier onlyReimbursableAdmin() override {
|
|
224
|
+
require(owner() == msg.sender, "Caller is not the owner");
|
|
225
|
+
_;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function initialize(Bridge _bridge) external initializer {
|
|
229
|
+
__Ownable_init();
|
|
230
|
+
|
|
231
|
+
bridge = _bridge;
|
|
232
|
+
// Pre-fetch wallet registry address to save gas on calls protected
|
|
233
|
+
// by the onlyProposalSubmitterOrWalletMember modifier.
|
|
234
|
+
(, , walletRegistry, reimbursementPool) = _bridge.contractReferences();
|
|
235
|
+
|
|
236
|
+
depositSweepProposalValidity = 4 hours;
|
|
237
|
+
depositMinAge = 2 hours;
|
|
238
|
+
depositRefundSafetyMargin = 24 hours;
|
|
239
|
+
depositSweepMaxSize = 5;
|
|
240
|
+
depositSweepProposalSubmissionGasOffset = 25000;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// @notice Adds the given address to the proposal submitters set.
|
|
244
|
+
/// @param proposalSubmitter Address of the new proposal submitter.
|
|
245
|
+
/// @dev Requirements:
|
|
246
|
+
/// - The caller must be the owner,
|
|
247
|
+
/// - The `proposalSubmitter` must not be an existing proposal submitter.
|
|
248
|
+
function addProposalSubmitter(address proposalSubmitter)
|
|
249
|
+
external
|
|
250
|
+
onlyOwner
|
|
251
|
+
{
|
|
252
|
+
require(
|
|
253
|
+
!isProposalSubmitter[proposalSubmitter],
|
|
254
|
+
"This address is already a proposal submitter"
|
|
255
|
+
);
|
|
256
|
+
isProposalSubmitter[proposalSubmitter] = true;
|
|
257
|
+
emit ProposalSubmitterAdded(proposalSubmitter);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// @notice Removes the given address from the proposal submitters set.
|
|
261
|
+
/// @param proposalSubmitter Address of the existing proposal submitter.
|
|
262
|
+
/// @dev Requirements:
|
|
263
|
+
/// - The caller must be the owner,
|
|
264
|
+
/// - The `proposalSubmitter` must be an existing proposal submitter.
|
|
265
|
+
function removeProposalSubmitter(address proposalSubmitter)
|
|
266
|
+
external
|
|
267
|
+
onlyOwner
|
|
268
|
+
{
|
|
269
|
+
require(
|
|
270
|
+
isProposalSubmitter[proposalSubmitter],
|
|
271
|
+
"This address is not a proposal submitter"
|
|
272
|
+
);
|
|
273
|
+
delete isProposalSubmitter[proposalSubmitter];
|
|
274
|
+
emit ProposalSubmitterRemoved(proposalSubmitter);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// @notice Allows to unlock the given wallet before their time lock expires.
|
|
278
|
+
/// This function should be used in exceptional cases where
|
|
279
|
+
/// something went wrong and there is a need to unlock the wallet
|
|
280
|
+
/// without waiting.
|
|
281
|
+
/// @param walletPubKeyHash 20-byte public key hash of the wallet
|
|
282
|
+
/// @dev Requirements:
|
|
283
|
+
/// - The caller must be the owner.
|
|
284
|
+
function unlockWallet(bytes20 walletPubKeyHash) external onlyOwner {
|
|
285
|
+
// Just in case, allow the owner to unlock the wallet earlier.
|
|
286
|
+
walletLock[walletPubKeyHash] = 0;
|
|
287
|
+
emit WalletManuallyUnlocked(walletPubKeyHash);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// @notice Updates the value of the depositSweepProposalValidity parameter.
|
|
291
|
+
/// @param _depositSweepProposalValidity New value.
|
|
292
|
+
/// @dev Requirements:
|
|
293
|
+
/// - The caller must be the owner.
|
|
294
|
+
function updateDepositSweepProposalValidity(
|
|
295
|
+
uint32 _depositSweepProposalValidity
|
|
296
|
+
) external onlyOwner {
|
|
297
|
+
depositSweepProposalValidity = _depositSweepProposalValidity;
|
|
298
|
+
emit DepositSweepProposalValidityUpdated(_depositSweepProposalValidity);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// @notice Updates the value of the depositMinAge parameter.
|
|
302
|
+
/// @param _depositMinAge New value.
|
|
303
|
+
/// @dev Requirements:
|
|
304
|
+
/// - The caller must be the owner.
|
|
305
|
+
function updateDepositMinAge(uint32 _depositMinAge) external onlyOwner {
|
|
306
|
+
depositMinAge = _depositMinAge;
|
|
307
|
+
emit DepositMinAgeUpdated(_depositMinAge);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// @notice Updates the value of the depositRefundSafetyMargin parameter.
|
|
311
|
+
/// @param _depositRefundSafetyMargin New value.
|
|
312
|
+
/// @dev Requirements:
|
|
313
|
+
/// - The caller must be the owner.
|
|
314
|
+
function updateDepositRefundSafetyMargin(uint32 _depositRefundSafetyMargin)
|
|
315
|
+
external
|
|
316
|
+
onlyOwner
|
|
317
|
+
{
|
|
318
|
+
depositRefundSafetyMargin = _depositRefundSafetyMargin;
|
|
319
|
+
emit DepositRefundSafetyMarginUpdated(_depositRefundSafetyMargin);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// @notice Updates the value of the depositSweepMaxSize parameter.
|
|
323
|
+
/// @param _depositSweepMaxSize New value.
|
|
324
|
+
/// @dev Requirements:
|
|
325
|
+
/// - The caller must be the owner.
|
|
326
|
+
function updateDepositSweepMaxSize(uint16 _depositSweepMaxSize)
|
|
327
|
+
external
|
|
328
|
+
onlyOwner
|
|
329
|
+
{
|
|
330
|
+
depositSweepMaxSize = _depositSweepMaxSize;
|
|
331
|
+
emit DepositSweepMaxSizeUpdated(_depositSweepMaxSize);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// @notice Updates the value of the depositSweepProposalSubmissionGasOffset
|
|
335
|
+
/// parameter.
|
|
336
|
+
/// @param _depositSweepProposalSubmissionGasOffset New value.
|
|
337
|
+
/// @dev Requirements:
|
|
338
|
+
/// - The caller must be the owner.
|
|
339
|
+
function updateDepositSweepProposalSubmissionGasOffset(
|
|
340
|
+
uint32 _depositSweepProposalSubmissionGasOffset
|
|
341
|
+
) external onlyOwner {
|
|
342
|
+
depositSweepProposalSubmissionGasOffset = _depositSweepProposalSubmissionGasOffset;
|
|
343
|
+
emit DepositSweepProposalSubmissionGasOffsetUpdated(
|
|
344
|
+
_depositSweepProposalSubmissionGasOffset
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// @notice Submits a deposit sweep proposal. Locks the target wallet
|
|
349
|
+
/// for a specific time, equal to the proposal validity period.
|
|
350
|
+
/// This function does not store the proposal in the state but
|
|
351
|
+
/// just emits an event that serves as a guiding light for wallet
|
|
352
|
+
/// off-chain members. Wallet members are supposed to validate
|
|
353
|
+
/// the proposal on their own, before taking any action.
|
|
354
|
+
/// @param proposal The deposit sweep proposal
|
|
355
|
+
/// @param walletMemberContext Optional parameter holding some data allowing
|
|
356
|
+
/// to confirm the wallet membership of the caller. This parameter is
|
|
357
|
+
/// relevant only when the caller is not a registered proposal
|
|
358
|
+
/// submitter but claims to be a member of the target wallet.
|
|
359
|
+
/// @dev Requirements:
|
|
360
|
+
/// - The caller is either a proposal submitter or a member of the
|
|
361
|
+
/// target wallet,
|
|
362
|
+
/// - The wallet is not time-locked.
|
|
363
|
+
function submitDepositSweepProposal(
|
|
364
|
+
DepositSweepProposal calldata proposal,
|
|
365
|
+
WalletMemberContext calldata walletMemberContext
|
|
366
|
+
)
|
|
367
|
+
public
|
|
368
|
+
onlyProposalSubmitterOrWalletMember(
|
|
369
|
+
proposal.walletPubKeyHash,
|
|
370
|
+
walletMemberContext
|
|
371
|
+
)
|
|
372
|
+
onlyAfterWalletLock(proposal.walletPubKeyHash)
|
|
373
|
+
{
|
|
374
|
+
walletLock[proposal.walletPubKeyHash] =
|
|
375
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
376
|
+
uint32(block.timestamp) +
|
|
377
|
+
depositSweepProposalValidity;
|
|
378
|
+
|
|
379
|
+
emit DepositSweepProposalSubmitted(proposal, msg.sender);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/// @notice Wraps `submitDepositSweepProposal` call and reimburses the
|
|
383
|
+
/// caller's transaction cost.
|
|
384
|
+
/// @dev See `submitDepositSweepProposal` function documentation.
|
|
385
|
+
function submitDepositSweepProposalWithReimbursement(
|
|
386
|
+
DepositSweepProposal calldata proposal,
|
|
387
|
+
WalletMemberContext calldata walletMemberContext
|
|
388
|
+
) external {
|
|
389
|
+
uint256 gasStart = gasleft();
|
|
390
|
+
|
|
391
|
+
submitDepositSweepProposal(proposal, walletMemberContext);
|
|
392
|
+
|
|
393
|
+
reimbursementPool.refund(
|
|
394
|
+
(gasStart - gasleft()) + depositSweepProposalSubmissionGasOffset,
|
|
395
|
+
msg.sender
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/// @notice View function encapsulating the main rules of a valid deposit
|
|
400
|
+
/// sweep proposal. This function is meant to facilitate the off-chain
|
|
401
|
+
/// validation of the incoming proposals. Thanks to it, most
|
|
402
|
+
/// of the work can be done using a single readonly contract call.
|
|
403
|
+
/// Worth noting, the validation done here is not exhaustive as some
|
|
404
|
+
/// conditions may not be verifiable within the on-chain function or
|
|
405
|
+
/// checking them may be easier on the off-chain side. For example,
|
|
406
|
+
/// this function does not check the SPV proofs and confirmations of
|
|
407
|
+
/// the deposit funding transactions as this would require an
|
|
408
|
+
/// integration with the difficulty relay that greatly increases
|
|
409
|
+
/// complexity. Instead of that, each off-chain wallet member is
|
|
410
|
+
/// supposed to do that check on their own.
|
|
411
|
+
/// @param proposal The sweeping proposal to validate.
|
|
412
|
+
/// @param depositsExtraInfo Deposits extra data required to perform the validation.
|
|
413
|
+
/// @dev Requirements:
|
|
414
|
+
/// - The target wallet must be in the Live state,
|
|
415
|
+
/// - The number of deposits included in the sweep must be in
|
|
416
|
+
/// the range [1, `depositSweepMaxSize`],
|
|
417
|
+
/// - The length of `depositsExtraInfo` array must be equal to the
|
|
418
|
+
/// length of `proposal.depositsKeys`, i.e. each deposit must
|
|
419
|
+
/// have exactly one set of corresponding extra data,
|
|
420
|
+
/// - The proposed sweep tx fee must be grater than zero,
|
|
421
|
+
/// - The proposed maximum per-deposit sweep tx fee must be lesser than
|
|
422
|
+
/// or equal the maximum fee allowed by the Bridge (`Bridge.depositTxMaxFee`),
|
|
423
|
+
/// - Each deposit must be revealed to the Bridge,
|
|
424
|
+
/// - Each deposit must be old enough, i.e. at least `depositMinAge`
|
|
425
|
+
/// elapsed since their reveal time,
|
|
426
|
+
/// - Each deposit must not be swept yet,
|
|
427
|
+
/// - Each deposit must have valid extra data (see `validateDepositExtraInfo`),
|
|
428
|
+
/// - Each deposit must have the refund safety margin preserved,
|
|
429
|
+
/// - Each deposit must be controlled by the same wallet,
|
|
430
|
+
/// - Each deposit must target the same vault.
|
|
431
|
+
///
|
|
432
|
+
/// The following off-chain validation must be performed as a bare minimum:
|
|
433
|
+
/// - Inputs used for the sweep transaction have enough Bitcoin confirmations,
|
|
434
|
+
/// - Deposits revealed to the Bridge have enough Ethereum confirmations.
|
|
435
|
+
function validateDepositSweepProposal(
|
|
436
|
+
DepositSweepProposal calldata proposal,
|
|
437
|
+
DepositExtraInfo[] calldata depositsExtraInfo
|
|
438
|
+
) external view {
|
|
439
|
+
require(
|
|
440
|
+
bridge.wallets(proposal.walletPubKeyHash).state ==
|
|
441
|
+
Wallets.WalletState.Live,
|
|
442
|
+
"Wallet is not in Live state"
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
require(proposal.depositsKeys.length > 0, "Sweep below the min size");
|
|
446
|
+
|
|
447
|
+
require(
|
|
448
|
+
proposal.depositsKeys.length <= depositSweepMaxSize,
|
|
449
|
+
"Sweep exceeds the max size"
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
require(
|
|
453
|
+
proposal.depositsKeys.length == depositsExtraInfo.length,
|
|
454
|
+
"Each deposit key must have matching extra data"
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
validateSweepTxFee(proposal.sweepTxFee, proposal.depositsKeys.length);
|
|
458
|
+
|
|
459
|
+
address proposalVault = address(0);
|
|
460
|
+
|
|
461
|
+
for (uint256 i = 0; i < proposal.depositsKeys.length; i++) {
|
|
462
|
+
DepositKey memory depositKey = proposal.depositsKeys[i];
|
|
463
|
+
DepositExtraInfo memory depositExtraInfo = depositsExtraInfo[i];
|
|
464
|
+
|
|
465
|
+
// slither-disable-next-line calls-loop
|
|
466
|
+
Deposit.DepositRequest memory depositRequest = bridge.deposits(
|
|
467
|
+
uint256(
|
|
468
|
+
keccak256(
|
|
469
|
+
abi.encodePacked(
|
|
470
|
+
depositKey.fundingTxHash,
|
|
471
|
+
depositKey.fundingOutputIndex
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
require(depositRequest.revealedAt != 0, "Deposit not revealed");
|
|
478
|
+
|
|
479
|
+
require(
|
|
480
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
481
|
+
block.timestamp > depositRequest.revealedAt + depositMinAge,
|
|
482
|
+
"Deposit min age not achieved yet"
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
require(depositRequest.sweptAt == 0, "Deposit already swept");
|
|
486
|
+
|
|
487
|
+
validateDepositExtraInfo(
|
|
488
|
+
depositKey,
|
|
489
|
+
depositRequest.depositor,
|
|
490
|
+
depositExtraInfo
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
uint32 depositRefundableTimestamp = BTCUtils.reverseUint32(
|
|
494
|
+
uint32(depositExtraInfo.refundLocktime)
|
|
495
|
+
);
|
|
496
|
+
require(
|
|
497
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
498
|
+
block.timestamp <
|
|
499
|
+
depositRefundableTimestamp - depositRefundSafetyMargin,
|
|
500
|
+
"Deposit refund safety margin is not preserved"
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
require(
|
|
504
|
+
depositExtraInfo.walletPubKeyHash == proposal.walletPubKeyHash,
|
|
505
|
+
"Deposit controlled by different wallet"
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
// Make sure all deposits target the same vault by using the
|
|
509
|
+
// vault of the first deposit as a reference.
|
|
510
|
+
if (i == 0) {
|
|
511
|
+
proposalVault = depositRequest.vault;
|
|
512
|
+
}
|
|
513
|
+
require(
|
|
514
|
+
depositRequest.vault == proposalVault,
|
|
515
|
+
"Deposit targets different vault"
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/// @notice Validates the sweep tx fee by checking if the part of the fee
|
|
521
|
+
/// incurred by each deposit does not exceed the maximum value
|
|
522
|
+
/// allowed by the Bridge. This function is heavily based on
|
|
523
|
+
/// `DepositSweep.depositSweepTxFeeDistribution` function.
|
|
524
|
+
/// @param sweepTxFee The sweep transaction fee.
|
|
525
|
+
/// @param depositsCount Count of the deposits swept by the sweep transaction.
|
|
526
|
+
/// @dev Requirements:
|
|
527
|
+
/// - The sweep tx fee must be grater than zero,
|
|
528
|
+
/// - The maximum per-deposit sweep tx fee must be lesser than or equal
|
|
529
|
+
/// the maximum fee allowed by the Bridge (`Bridge.depositTxMaxFee`).
|
|
530
|
+
function validateSweepTxFee(uint256 sweepTxFee, uint256 depositsCount)
|
|
531
|
+
internal
|
|
532
|
+
view
|
|
533
|
+
{
|
|
534
|
+
require(sweepTxFee > 0, "Proposed transaction fee cannot be zero");
|
|
535
|
+
|
|
536
|
+
// Compute the indivisible remainder that remains after dividing the
|
|
537
|
+
// sweep transaction fee over all deposits evenly.
|
|
538
|
+
uint256 depositTxFeeRemainder = sweepTxFee % depositsCount;
|
|
539
|
+
// Compute the transaction fee per deposit by dividing the sweep
|
|
540
|
+
// transaction fee (reduced by the remainder) by the number of deposits.
|
|
541
|
+
uint256 depositTxFee = (sweepTxFee - depositTxFeeRemainder) /
|
|
542
|
+
depositsCount;
|
|
543
|
+
|
|
544
|
+
(, , uint64 depositTxMaxFee, ) = bridge.depositParameters();
|
|
545
|
+
|
|
546
|
+
// The transaction fee is incurred by each deposit evenly except for the last
|
|
547
|
+
// deposit that has the indivisible remainder additionally incurred.
|
|
548
|
+
// See `DepositSweep.submitDepositSweepProof`.
|
|
549
|
+
// We must make sure the highest value of the deposit transaction fee does
|
|
550
|
+
// not exceed the maximum value limited by the governable parameter.
|
|
551
|
+
require(
|
|
552
|
+
depositTxFee + depositTxFeeRemainder <= depositTxMaxFee,
|
|
553
|
+
"Proposed transaction fee is too high"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/// @notice Validates the extra data for the given deposit. This function
|
|
558
|
+
/// is heavily based on `Deposit.revealDeposit` function.
|
|
559
|
+
/// @param depositKey Key of the given deposit.
|
|
560
|
+
/// @param depositor Depositor that revealed the deposit.
|
|
561
|
+
/// @param depositExtraInfo Extra data being subject of the validation.
|
|
562
|
+
/// @dev Requirements:
|
|
563
|
+
/// - The transaction hash computed using `depositExtraInfo.fundingTx`
|
|
564
|
+
/// must match the `depositKey.fundingTxHash`. This requirement
|
|
565
|
+
/// ensures the funding transaction data provided in the extra
|
|
566
|
+
/// data container actually represent the funding transaction of
|
|
567
|
+
/// the given deposit.
|
|
568
|
+
/// - The P2(W)SH script inferred from `depositExtraInfo` is actually
|
|
569
|
+
/// used to lock funds by the `depositKey.fundingOutputIndex` output
|
|
570
|
+
/// of the `depositExtraInfo.fundingTx` transaction. This requirement
|
|
571
|
+
/// ensures the reveal data provided in the extra data container
|
|
572
|
+
/// actually matches the given deposit.
|
|
573
|
+
function validateDepositExtraInfo(
|
|
574
|
+
DepositKey memory depositKey,
|
|
575
|
+
address depositor,
|
|
576
|
+
DepositExtraInfo memory depositExtraInfo
|
|
577
|
+
) internal view {
|
|
578
|
+
bytes32 depositExtraFundingTxHash = abi
|
|
579
|
+
.encodePacked(
|
|
580
|
+
depositExtraInfo.fundingTx.version,
|
|
581
|
+
depositExtraInfo.fundingTx.inputVector,
|
|
582
|
+
depositExtraInfo.fundingTx.outputVector,
|
|
583
|
+
depositExtraInfo.fundingTx.locktime
|
|
584
|
+
)
|
|
585
|
+
.hash256View();
|
|
586
|
+
|
|
587
|
+
// Make sure the funding tx provided as part of deposit extra data
|
|
588
|
+
// actually matches the deposit referred by the given deposit key.
|
|
589
|
+
if (depositKey.fundingTxHash != depositExtraFundingTxHash) {
|
|
590
|
+
revert("Extra info funding tx hash does not match");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
bytes memory expectedScript = abi.encodePacked(
|
|
594
|
+
hex"14", // Byte length of depositor Ethereum address.
|
|
595
|
+
depositor,
|
|
596
|
+
hex"75", // OP_DROP
|
|
597
|
+
hex"08", // Byte length of blinding factor value.
|
|
598
|
+
depositExtraInfo.blindingFactor,
|
|
599
|
+
hex"75", // OP_DROP
|
|
600
|
+
hex"76", // OP_DUP
|
|
601
|
+
hex"a9", // OP_HASH160
|
|
602
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
603
|
+
depositExtraInfo.walletPubKeyHash,
|
|
604
|
+
hex"87", // OP_EQUAL
|
|
605
|
+
hex"63", // OP_IF
|
|
606
|
+
hex"ac", // OP_CHECKSIG
|
|
607
|
+
hex"67", // OP_ELSE
|
|
608
|
+
hex"76", // OP_DUP
|
|
609
|
+
hex"a9", // OP_HASH160
|
|
610
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
611
|
+
depositExtraInfo.refundPubKeyHash,
|
|
612
|
+
hex"88", // OP_EQUALVERIFY
|
|
613
|
+
hex"04", // Byte length of refund locktime value.
|
|
614
|
+
depositExtraInfo.refundLocktime,
|
|
615
|
+
hex"b1", // OP_CHECKLOCKTIMEVERIFY
|
|
616
|
+
hex"75", // OP_DROP
|
|
617
|
+
hex"ac", // OP_CHECKSIG
|
|
618
|
+
hex"68" // OP_ENDIF
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
bytes memory fundingOutput = depositExtraInfo
|
|
622
|
+
.fundingTx
|
|
623
|
+
.outputVector
|
|
624
|
+
.extractOutputAtIndex(depositKey.fundingOutputIndex);
|
|
625
|
+
bytes memory fundingOutputHash = fundingOutput.extractHash();
|
|
626
|
+
|
|
627
|
+
// Path that checks the deposit extra data validity in case the
|
|
628
|
+
// referred deposit is a P2SH.
|
|
629
|
+
if (
|
|
630
|
+
// slither-disable-next-line calls-loop
|
|
631
|
+
fundingOutputHash.length == 20 &&
|
|
632
|
+
fundingOutputHash.slice20(0) == expectedScript.hash160View()
|
|
633
|
+
) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Path that checks the deposit extra data validity in case the
|
|
638
|
+
// referred deposit is a P2WSH.
|
|
639
|
+
if (
|
|
640
|
+
fundingOutputHash.length == 32 &&
|
|
641
|
+
fundingOutputHash.toBytes32() == sha256(expectedScript)
|
|
642
|
+
) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
revert("Extra info funding output script does not match");
|
|
647
|
+
}
|
|
648
|
+
}
|