@keep-network/tbtc-v2 0.1.1-dev.1 → 0.1.1-dev.10
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/TBTC.json +10 -10
- package/artifacts/TBTCToken.json +10 -10
- package/artifacts/VendingMachine.json +11 -11
- package/artifacts/solcInputs/524094faac10a04084fcc411e06dab84.json +128 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +4 -0
- package/build/contracts/bank/Bank.sol/Bank.json +537 -0
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +4 -0
- package/build/contracts/bridge/Bridge.sol/Bridge.json +335 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +4 -0
- package/build/contracts/vault/IVault.sol/IVault.json +47 -0
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +4 -0
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +181 -0
- package/contracts/bank/Bank.sol +389 -0
- package/contracts/bridge/BitcoinTx.sol +104 -0
- package/contracts/bridge/Bridge.sol +346 -0
- package/contracts/vault/IVault.sol +60 -0
- package/contracts/vault/TBTCVault.sol +146 -0
- package/package.json +4 -3
- package/artifacts/solcInputs/0c46d22cee2363c42c8bb0664dc1be66.json +0 -104
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity 0.8.4;
|
|
17
|
+
|
|
18
|
+
/// @title Bitcoin transaction
|
|
19
|
+
/// @notice Allows to reference Bitcoin raw transaction in Solidity.
|
|
20
|
+
/// @dev See https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
|
|
21
|
+
///
|
|
22
|
+
/// Raw Bitcon transaction data:
|
|
23
|
+
///
|
|
24
|
+
/// | Bytes | Name | BTC type | Description |
|
|
25
|
+
/// |--------|--------------|------------------------|---------------------------|
|
|
26
|
+
/// | 4 | version | int32_t (LE) | TX version number |
|
|
27
|
+
/// | varies | tx_in_count | compactSize uint (LE) | Number of TX inputs |
|
|
28
|
+
/// | varies | tx_in | txIn[] | TX inputs |
|
|
29
|
+
/// | varies | tx_out count | compactSize uint (LE) | Number of TX outputs |
|
|
30
|
+
/// | varies | tx_out | txOut[] | TX outputs |
|
|
31
|
+
/// | 4 | lock_time | uint32_t (LE) | Unix time or block number |
|
|
32
|
+
///
|
|
33
|
+
//
|
|
34
|
+
/// Non-coinbase transaction input (txIn):
|
|
35
|
+
///
|
|
36
|
+
/// | Bytes | Name | BTC type | Description |
|
|
37
|
+
/// |--------|------------------|------------------------|---------------------------------------------|
|
|
38
|
+
/// | 36 | previous_output | outpoint | The previous outpoint being spent |
|
|
39
|
+
/// | varies | script bytes | compactSize uint (LE) | The number of bytes in the signature script |
|
|
40
|
+
/// | varies | signature script | char[] | The signature script, empty for P2WSH |
|
|
41
|
+
/// | 4 | sequence | uint32_t (LE) | Sequence number |
|
|
42
|
+
///
|
|
43
|
+
///
|
|
44
|
+
/// The reference to transaction being spent (outpoint):
|
|
45
|
+
///
|
|
46
|
+
/// | Bytes | Name | BTC type | Description |
|
|
47
|
+
/// |-------|-------|---------------|------------------------------------------|
|
|
48
|
+
/// | 32 | hash | char[32] | Hash of the transaction to spend |
|
|
49
|
+
/// | 4 | index | uint32_t (LE) | Index of the specific output from the TX |
|
|
50
|
+
///
|
|
51
|
+
///
|
|
52
|
+
/// Transaction output (txOut):
|
|
53
|
+
///
|
|
54
|
+
/// | Bytes | Name | BTC type | Description |
|
|
55
|
+
/// |--------|-----------------|-----------------------|--------------------------------------|
|
|
56
|
+
/// | 8 | value | int64_t (LE) | Number of satoshis to spend |
|
|
57
|
+
/// | 1+ | pk_script_bytes | compactSize uint (LE) | Number of bytes in the pubkey script |
|
|
58
|
+
/// | varies | pk_script | char[] | Pubkey script |
|
|
59
|
+
///
|
|
60
|
+
/// compactSize uint format:
|
|
61
|
+
///
|
|
62
|
+
/// | Value | Bytes | Format |
|
|
63
|
+
/// |-----------------------------------------|-------|----------------------------------------------|
|
|
64
|
+
/// | >= 0 && <= 252 | 1 | uint8_t |
|
|
65
|
+
/// | >= 253 && <= 0xffff | 3 | 0xfd followed by the number as uint16_t (LE) |
|
|
66
|
+
/// | >= 0x10000 && <= 0xffffffff | 5 | 0xfe followed by the number as uint32_t (LE) |
|
|
67
|
+
/// | >= 0x100000000 && <= 0xffffffffffffffff | 9 | 0xff followed by the number as uint64_t (LE) |
|
|
68
|
+
///
|
|
69
|
+
/// (*) compactSize uint is often references as VarInt)
|
|
70
|
+
///
|
|
71
|
+
library BitcoinTx {
|
|
72
|
+
/// @notice Represents Bitcoin transaction data for funding BTC deposit
|
|
73
|
+
/// P2(W)SH transaction.
|
|
74
|
+
struct Info {
|
|
75
|
+
/// @notice Bitcoin transaction version
|
|
76
|
+
/// @dev `version` from raw Bitcon transaction data.
|
|
77
|
+
/// Encoded as 4-bytes signed integer, little endian.
|
|
78
|
+
bytes4 version;
|
|
79
|
+
/// @notice All Bitcoin transaction inputs, prepended by the number of
|
|
80
|
+
/// transaction inputs.
|
|
81
|
+
/// @dev `tx_in_count | tx_in` from raw Bitcon transaction data.
|
|
82
|
+
///
|
|
83
|
+
/// The number of transaction inputs encoded as compactSize
|
|
84
|
+
/// unsigned integer, little-endian.
|
|
85
|
+
///
|
|
86
|
+
/// Note that some popular block explorers reverse the order of
|
|
87
|
+
/// bytes from `outpoint`'s `hash` and display it as big-endian.
|
|
88
|
+
/// Solidity code of Bridge expects hashes in little-endian, just
|
|
89
|
+
/// like they are represented in a raw Bitcoin transaction.
|
|
90
|
+
bytes inputVector;
|
|
91
|
+
/// @notice All Bitcoin transaction outputs prepended by the number of
|
|
92
|
+
/// transaction outputs.
|
|
93
|
+
/// @dev `tx_out_count | tx_out` from raw Bitcoin transaction data.
|
|
94
|
+
///
|
|
95
|
+
/// The number of transaction outputs encoded as a compactSize
|
|
96
|
+
/// unsigned integer, little-endian.
|
|
97
|
+
bytes outputVector;
|
|
98
|
+
/// @notice Bitcoin transaction locktime.
|
|
99
|
+
///
|
|
100
|
+
/// @dev `lock_time` from raw Bitcoin transaction data.
|
|
101
|
+
/// Encoded as 4-bytes unsigned integer, little endian.
|
|
102
|
+
bytes4 locktime;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity 0.8.4;
|
|
17
|
+
|
|
18
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
19
|
+
|
|
20
|
+
import {BTCUtils} from "@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol";
|
|
21
|
+
import {BytesLib} from "@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol";
|
|
22
|
+
|
|
23
|
+
import "./BitcoinTx.sol";
|
|
24
|
+
|
|
25
|
+
/// @title Bitcoin Bridge
|
|
26
|
+
/// @notice Bridge manages BTC deposit and redemption flow and is increasing and
|
|
27
|
+
/// decreasing balances in the Bank as a result of BTC deposit and
|
|
28
|
+
/// redemption operations performed by depositors and redeemers.
|
|
29
|
+
///
|
|
30
|
+
/// Depositors send BTC funds to the most recently created off-chain
|
|
31
|
+
/// ECDSA wallet of the bridge using pay-to-script-hash (P2SH) or
|
|
32
|
+
/// pay-to-witness-script-hash (P2WSH) containing hashed information
|
|
33
|
+
/// about the depositor’s Ethereum address. Then, the depositor reveals
|
|
34
|
+
/// their Ethereum address along with their deposit blinding factor,
|
|
35
|
+
/// refund public key hash and refund locktime to the Bridge on Ethereum
|
|
36
|
+
/// chain. The off-chain ECDSA wallet listens for these sorts of
|
|
37
|
+
/// messages and when it gets one, it checks the Bitcoin network to make
|
|
38
|
+
/// sure the deposit lines up. If it does, the off-chain ECDSA wallet
|
|
39
|
+
/// may decide to pick the deposit transaction for sweeping, and when
|
|
40
|
+
/// the sweep operation is confirmed on the Bitcoin network, the ECDSA
|
|
41
|
+
/// wallet informs the Bridge about the sweep increasing appropriate
|
|
42
|
+
/// balances in the Bank.
|
|
43
|
+
/// @dev Bridge is an upgradeable component of the Bank.
|
|
44
|
+
contract Bridge is Ownable {
|
|
45
|
+
using BTCUtils for bytes;
|
|
46
|
+
using BytesLib for bytes;
|
|
47
|
+
|
|
48
|
+
/// @notice Represents data which must be revealed by the depositor during
|
|
49
|
+
/// deposit reveal.
|
|
50
|
+
struct RevealInfo {
|
|
51
|
+
// Index of the funding output belonging to the funding transaction.
|
|
52
|
+
uint8 fundingOutputIndex;
|
|
53
|
+
// Ethereum depositor address.
|
|
54
|
+
address depositor;
|
|
55
|
+
// The blinding factor as 8 bytes. Byte endianness doesn't matter
|
|
56
|
+
// as this factor is not interpreted as uint.
|
|
57
|
+
bytes8 blindingFactor;
|
|
58
|
+
// The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
|
|
59
|
+
// of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.
|
|
60
|
+
bytes20 walletPubKeyHash;
|
|
61
|
+
// The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)
|
|
62
|
+
// that can be used to make the deposit refund after the refund
|
|
63
|
+
// locktime passes. Hashed in the HASH160 Bitcoin opcode style.
|
|
64
|
+
bytes20 refundPubKeyHash;
|
|
65
|
+
// The refund locktime (4-byte LE). Interpreted according to locktime
|
|
66
|
+
// parsing rules described in:
|
|
67
|
+
// https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number
|
|
68
|
+
// and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:
|
|
69
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
|
|
70
|
+
bytes4 refundLocktime;
|
|
71
|
+
// Address of the Bank vault to which the deposit is routed to.
|
|
72
|
+
// Optional, can be 0x0. The vault must be trusted by the Bridge.
|
|
73
|
+
address vault;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// @notice Represents tBTC deposit data.
|
|
77
|
+
struct DepositInfo {
|
|
78
|
+
// Ethereum depositor address.
|
|
79
|
+
address depositor;
|
|
80
|
+
// Deposit amount in satoshi (8-byte LE). For example:
|
|
81
|
+
// 0.0001 BTC = 10000 satoshi = 0x1027000000000000
|
|
82
|
+
bytes8 amount;
|
|
83
|
+
// UNIX timestamp the deposit was revealed at.
|
|
84
|
+
uint32 revealedAt;
|
|
85
|
+
// Address of the Bank vault the deposit is routed to.
|
|
86
|
+
// Optional, can be 0x0.
|
|
87
|
+
address vault;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// @notice Indicates if the vault with the given address is trusted or not.
|
|
91
|
+
/// Depositors can route their revealed deposits only to trusted
|
|
92
|
+
/// vaults and have trusted vaults notified about new deposits as
|
|
93
|
+
/// soon as these deposits get swept. Vaults not trusted by the
|
|
94
|
+
/// Bridge can still be used by Bank balance owners on their own
|
|
95
|
+
/// responsibility - anyone can approve their Bank balance to any
|
|
96
|
+
/// address.
|
|
97
|
+
mapping(address => bool) public isVaultTrusted;
|
|
98
|
+
|
|
99
|
+
/// @notice Collection of all unswept deposits indexed by
|
|
100
|
+
/// keccak256(fundingTxHash | fundingOutputIndex).
|
|
101
|
+
/// The fundingTxHash is LE bytes32 and fundingOutputIndex an uint8.
|
|
102
|
+
/// This mapping may contain valid and invalid deposits and the
|
|
103
|
+
/// wallet is responsible for validating them before attempting to
|
|
104
|
+
/// execute a sweep.
|
|
105
|
+
///
|
|
106
|
+
/// TODO: Explore the possibility of storing just a hash of DepositInfo.
|
|
107
|
+
mapping(uint256 => DepositInfo) public unswept;
|
|
108
|
+
|
|
109
|
+
event DepositRevealed(
|
|
110
|
+
bytes32 fundingTxHash,
|
|
111
|
+
uint8 fundingOutputIndex,
|
|
112
|
+
address depositor,
|
|
113
|
+
bytes8 blindingFactor,
|
|
114
|
+
bytes20 walletPubKeyHash,
|
|
115
|
+
bytes20 refundPubKeyHash,
|
|
116
|
+
bytes4 refundLocktime,
|
|
117
|
+
address vault
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
event VaultStatusUpdated(address indexed vault, bool isTrusted);
|
|
121
|
+
|
|
122
|
+
/// @notice Allows the Governance to mark the given vault address as trusted
|
|
123
|
+
/// or no longer trusted. Vaults are not trusted by default.
|
|
124
|
+
/// Trusted vault must meet the following criteria:
|
|
125
|
+
/// - `IVault.receiveBalanceIncrease` must have a known, low gas
|
|
126
|
+
/// cost.
|
|
127
|
+
/// - `IVault.receiveBalanceIncrease` must never revert.
|
|
128
|
+
/// @dev Without restricting reveal only to trusted vaults, malicious
|
|
129
|
+
/// vaults not meeting the criteria would be able to nuke sweep proof
|
|
130
|
+
/// transactions executed by ECDSA wallet with deposits routed to
|
|
131
|
+
/// them.
|
|
132
|
+
/// @param vault The address of the vault
|
|
133
|
+
/// @param isTrusted flag indicating whether the vault is trusted or not
|
|
134
|
+
/// @dev Can only be called by the Governance.
|
|
135
|
+
function setVaultStatus(address vault, bool isTrusted) external onlyOwner {
|
|
136
|
+
isVaultTrusted[vault] = isTrusted;
|
|
137
|
+
emit VaultStatusUpdated(vault, isTrusted);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// @notice Used by the depositor to reveal information about their P2(W)SH
|
|
141
|
+
/// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain
|
|
142
|
+
/// wallet listens for revealed deposit events and may decide to
|
|
143
|
+
/// include the revealed deposit in the next executed sweep.
|
|
144
|
+
/// Information about the Bitcoin deposit can be revealed before or
|
|
145
|
+
/// after the Bitcoin transaction with P2(W)SH deposit is mined on
|
|
146
|
+
/// the Bitcoin chain. Worth noting, the gas cost of this function
|
|
147
|
+
/// scales with the number of P2(W)SH transaction inputs and
|
|
148
|
+
/// outputs. The deposit may be routed to one of the trusted vaults.
|
|
149
|
+
/// When a deposit is routed to a vault, vault gets notified when
|
|
150
|
+
/// the deposit gets swept and it may execute the appropriate action.
|
|
151
|
+
/// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`
|
|
152
|
+
/// @param reveal Deposit reveal data, see `RevealInfo struct
|
|
153
|
+
/// @dev Requirements:
|
|
154
|
+
/// - `reveal.vault` must be 0x0 or point to a trusted vault
|
|
155
|
+
/// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH
|
|
156
|
+
/// output of the BTC deposit transaction
|
|
157
|
+
/// - `reveal.depositor` must be the Ethereum address used in the
|
|
158
|
+
/// P2(W)SH BTC deposit transaction,
|
|
159
|
+
/// - `reveal.blindingFactor` must be the blinding factor used in the
|
|
160
|
+
/// P2(W)SH BTC deposit transaction,
|
|
161
|
+
/// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in
|
|
162
|
+
/// the P2(W)SH BTC deposit transaction,
|
|
163
|
+
/// - `reveal.refundPubKeyHash` must be the refund pub key hash used in
|
|
164
|
+
/// the P2(W)SH BTC deposit transaction,
|
|
165
|
+
/// - `reveal.refundLocktime` must be the refund locktime used in the
|
|
166
|
+
/// P2(W)SH BTC deposit transaction,
|
|
167
|
+
/// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`
|
|
168
|
+
/// can be revealed only one time.
|
|
169
|
+
///
|
|
170
|
+
/// If any of these requirements is not met, the wallet _must_ refuse
|
|
171
|
+
/// to sweep the deposit and the depositor has to wait until the
|
|
172
|
+
/// deposit script unlocks to receive their BTC back.
|
|
173
|
+
function revealDeposit(
|
|
174
|
+
BitcoinTx.Info calldata fundingTx,
|
|
175
|
+
RevealInfo calldata reveal
|
|
176
|
+
) external {
|
|
177
|
+
require(
|
|
178
|
+
reveal.vault == address(0) || isVaultTrusted[reveal.vault],
|
|
179
|
+
"Vault is not trusted"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
bytes memory expectedScript =
|
|
183
|
+
abi.encodePacked(
|
|
184
|
+
hex"14", // Byte length of depositor Ethereum address.
|
|
185
|
+
reveal.depositor,
|
|
186
|
+
hex"75", // OP_DROP
|
|
187
|
+
hex"08", // Byte length of blinding factor value.
|
|
188
|
+
reveal.blindingFactor,
|
|
189
|
+
hex"75", // OP_DROP
|
|
190
|
+
hex"76", // OP_DUP
|
|
191
|
+
hex"a9", // OP_HASH160
|
|
192
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
193
|
+
reveal.walletPubKeyHash,
|
|
194
|
+
hex"87", // OP_EQUAL
|
|
195
|
+
hex"63", // OP_IF
|
|
196
|
+
hex"ac", // OP_CHECKSIG
|
|
197
|
+
hex"67", // OP_ELSE
|
|
198
|
+
hex"76", // OP_DUP
|
|
199
|
+
hex"a9", // OP_HASH160
|
|
200
|
+
hex"14", // Byte length of a compressed Bitcoin public key hash.
|
|
201
|
+
reveal.refundPubKeyHash,
|
|
202
|
+
hex"88", // OP_EQUALVERIFY
|
|
203
|
+
hex"04", // Byte length of refund locktime value.
|
|
204
|
+
reveal.refundLocktime,
|
|
205
|
+
hex"b1", // OP_CHECKLOCKTIMEVERIFY
|
|
206
|
+
hex"75", // OP_DROP
|
|
207
|
+
hex"ac", // OP_CHECKSIG
|
|
208
|
+
hex"68" // OP_ENDIF
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
bytes memory fundingOutput =
|
|
212
|
+
fundingTx.outputVector.extractOutputAtIndex(
|
|
213
|
+
reveal.fundingOutputIndex
|
|
214
|
+
);
|
|
215
|
+
bytes memory fundingOutputHash = fundingOutput.extractHash();
|
|
216
|
+
|
|
217
|
+
if (fundingOutputHash.length == 20) {
|
|
218
|
+
// A 20-byte output hash is used by P2SH. That hash is constructed
|
|
219
|
+
// by applying OP_HASH160 on the locking script. A 20-byte output
|
|
220
|
+
// hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the
|
|
221
|
+
// public key). However, since we compare the actual output hash
|
|
222
|
+
// with an expected locking script hash, this check will succeed only
|
|
223
|
+
// for P2SH transaction type with expected script hash value. For
|
|
224
|
+
// P2PKH and P2WPKH, it will fail on the output hash comparison with
|
|
225
|
+
// the expected locking script hash.
|
|
226
|
+
require(
|
|
227
|
+
keccak256(fundingOutputHash) ==
|
|
228
|
+
keccak256(expectedScript.hash160()),
|
|
229
|
+
"Wrong 20-byte script hash"
|
|
230
|
+
);
|
|
231
|
+
} else if (fundingOutputHash.length == 32) {
|
|
232
|
+
// A 32-byte output hash is used by P2WSH. That hash is constructed
|
|
233
|
+
// by applying OP_SHA256 on the locking script.
|
|
234
|
+
require(
|
|
235
|
+
fundingOutputHash.toBytes32() == sha256(expectedScript),
|
|
236
|
+
"Wrong 32-byte script hash"
|
|
237
|
+
);
|
|
238
|
+
} else {
|
|
239
|
+
revert("Wrong script hash length");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Resulting TX hash is in native Bitcoin little-endian format.
|
|
243
|
+
bytes32 fundingTxHash =
|
|
244
|
+
abi
|
|
245
|
+
.encodePacked(
|
|
246
|
+
fundingTx
|
|
247
|
+
.version,
|
|
248
|
+
fundingTx
|
|
249
|
+
.inputVector,
|
|
250
|
+
fundingTx
|
|
251
|
+
.outputVector,
|
|
252
|
+
fundingTx
|
|
253
|
+
.locktime
|
|
254
|
+
)
|
|
255
|
+
.hash256();
|
|
256
|
+
|
|
257
|
+
DepositInfo storage deposit =
|
|
258
|
+
unswept[
|
|
259
|
+
uint256(
|
|
260
|
+
keccak256(
|
|
261
|
+
abi.encodePacked(
|
|
262
|
+
fundingTxHash,
|
|
263
|
+
reveal.fundingOutputIndex
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
];
|
|
268
|
+
require(deposit.revealedAt == 0, "Deposit already revealed");
|
|
269
|
+
|
|
270
|
+
bytes8 fundingOutputAmount;
|
|
271
|
+
/* solhint-disable-next-line no-inline-assembly */
|
|
272
|
+
assembly {
|
|
273
|
+
// First 8 bytes (little-endian) of the funding output represents
|
|
274
|
+
// its value. To take the value, we need to jump over the first
|
|
275
|
+
// word determining the array length, load the array, and trim it
|
|
276
|
+
// by putting it to a bytes8.
|
|
277
|
+
fundingOutputAmount := mload(add(fundingOutput, 32))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
deposit.amount = fundingOutputAmount;
|
|
281
|
+
deposit.depositor = reveal.depositor;
|
|
282
|
+
/* solhint-disable-next-line not-rely-on-time */
|
|
283
|
+
deposit.revealedAt = uint32(block.timestamp);
|
|
284
|
+
deposit.vault = reveal.vault;
|
|
285
|
+
|
|
286
|
+
emit DepositRevealed(
|
|
287
|
+
fundingTxHash,
|
|
288
|
+
reveal.fundingOutputIndex,
|
|
289
|
+
reveal.depositor,
|
|
290
|
+
reveal.blindingFactor,
|
|
291
|
+
reveal.walletPubKeyHash,
|
|
292
|
+
reveal.refundPubKeyHash,
|
|
293
|
+
reveal.refundLocktime,
|
|
294
|
+
reveal.vault
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// @notice Used by the wallet to prove the BTC deposit sweep transaction
|
|
299
|
+
/// and to update Bank balances accordingly. Sweep is only accepted
|
|
300
|
+
/// if it satisfies SPV proof.
|
|
301
|
+
///
|
|
302
|
+
/// The function is performing Bank balance updates by first
|
|
303
|
+
/// computing the Bitcoin fee for the sweep transaction. The fee is
|
|
304
|
+
/// divided evenly between all swept deposits. Each depositor
|
|
305
|
+
/// receives a balance in the bank equal to the amount inferred
|
|
306
|
+
/// during the reveal transaction, minus their fee share.
|
|
307
|
+
///
|
|
308
|
+
/// It is possible to prove the given sweep only one time.
|
|
309
|
+
/// @param sweepTx Bitcoin sweep transaction data.
|
|
310
|
+
/// @param merkleProof The merkle proof of transaction inclusion in a block.
|
|
311
|
+
/// @param txIndexInBlock Transaction index in the block (0-indexed).
|
|
312
|
+
/// @param bitcoinHeaders Single bytestring of 80-byte bitcoin headers,
|
|
313
|
+
/// lowest height first.
|
|
314
|
+
function sweep(
|
|
315
|
+
BitcoinTx.Info calldata sweepTx,
|
|
316
|
+
bytes memory merkleProof,
|
|
317
|
+
uint256 txIndexInBlock,
|
|
318
|
+
bytes memory bitcoinHeaders
|
|
319
|
+
) external {
|
|
320
|
+
// TODO We need to read `fundingTxHash`, `fundingOutputIndex` from
|
|
321
|
+
// `sweepTx.inputVector`. We then hash them to obtain deposit
|
|
322
|
+
// identifier and read DepositInfo. From DepositInfo we know what
|
|
323
|
+
// amount was inferred during deposit reveal transaction and we
|
|
324
|
+
// use that amount to update their Bank balance, minus fee.
|
|
325
|
+
//
|
|
326
|
+
// TODO We need to validate if the sum in the output minus the
|
|
327
|
+
// amount from the previous wallet balance input minus fees is
|
|
328
|
+
// equal to the amount by which Bank balances were increased.
|
|
329
|
+
//
|
|
330
|
+
// TODO We need to validate `sweepTx.outputVector` to see if the balance
|
|
331
|
+
// was not transferred away from the wallet before increasing
|
|
332
|
+
// balances in the bank.
|
|
333
|
+
//
|
|
334
|
+
// TODO Delete deposit from unswept mapping or mark it as swept
|
|
335
|
+
// depending on the gas costs. Alternatively, do not allow to
|
|
336
|
+
// use the same TX input vector twice. Sweep should be provable
|
|
337
|
+
// only one time.
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// TODO It is possible a malicious wallet can sweep deposits that can not
|
|
341
|
+
// be later proved on Ethereum. For example, a deposit with
|
|
342
|
+
// an incorrect amount revealed. We need to provide a function for honest
|
|
343
|
+
// depositors, next to sweep, to prove their swept balances on Ethereum
|
|
344
|
+
// selectively, based on deposits they have earlier received.
|
|
345
|
+
// (UPDATE PR #90: Is it still the case since amounts are inferred?)
|
|
346
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity 0.8.4;
|
|
17
|
+
|
|
18
|
+
/// @title Bank Vault interface
|
|
19
|
+
/// @notice `IVault` is an interface for a smart contract consuming Bank
|
|
20
|
+
/// balances of other contracts or externally owned accounts (EOA).
|
|
21
|
+
interface IVault {
|
|
22
|
+
/// @notice Called by the Bank in `approveBalanceAndCall` function after
|
|
23
|
+
/// the balance `owner` approved `amount` of their balance in the
|
|
24
|
+
/// Bank for the vault. This way, the depositor can approve balance
|
|
25
|
+
/// and call the vault to use the approved balance in a single
|
|
26
|
+
/// transaction.
|
|
27
|
+
/// @param owner Address of the Bank balance owner who approved their
|
|
28
|
+
/// balance to be used by the vault
|
|
29
|
+
/// @param amount The amount of the Bank balance approved by the owner
|
|
30
|
+
/// to be used by the vault
|
|
31
|
+
// @dev The implementation must ensure this function can only be called
|
|
32
|
+
/// by the Bank. The Bank does _not_ guarantee that the `amount`
|
|
33
|
+
/// approved by the `owner` currently exists on their balance. That is,
|
|
34
|
+
/// the `owner` could approve more balance than they currently have.
|
|
35
|
+
/// This works the same as `Bank.approve` function. The vault must
|
|
36
|
+
/// ensure the actual balance is checked before performing any action
|
|
37
|
+
/// based on it.
|
|
38
|
+
function receiveBalanceApproval(address owner, uint256 amount) external;
|
|
39
|
+
|
|
40
|
+
/// @notice Called by the Bank in `increaseBalanceAndCall` function after
|
|
41
|
+
/// increasing the balance in the Bank for the vault. It happens in
|
|
42
|
+
/// the same transaction in which deposits were swept by the Bridge.
|
|
43
|
+
/// This allows the depositor to route their deposit revealed to the
|
|
44
|
+
/// Bridge to the particular smart contract (vault) in the same
|
|
45
|
+
/// transaction in which the deposit is revealed. This way, the
|
|
46
|
+
/// depositor does not have to execute additional transaction after
|
|
47
|
+
/// the deposit gets swept by the Bridge to approve and transfer
|
|
48
|
+
/// their balance to the vault.
|
|
49
|
+
/// @param depositors Addresses of depositors whose deposits have been swept
|
|
50
|
+
/// @param depositedAmounts Amounts deposited by individual depositors and
|
|
51
|
+
/// swept
|
|
52
|
+
/// @dev The implementation must ensure this function can only be called
|
|
53
|
+
/// by the Bank. The Bank guarantees that the vault's balance was
|
|
54
|
+
/// increased by the sum of all deposited amounts before this function
|
|
55
|
+
/// is called, in the same transaction.
|
|
56
|
+
function receiveBalanceIncrease(
|
|
57
|
+
address[] calldata depositors,
|
|
58
|
+
uint256[] calldata depositedAmounts
|
|
59
|
+
) external;
|
|
60
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
// ██████████████ ▐████▌ ██████████████
|
|
4
|
+
// ██████████████ ▐████▌ ██████████████
|
|
5
|
+
// ▐████▌ ▐████▌
|
|
6
|
+
// ▐████▌ ▐████▌
|
|
7
|
+
// ██████████████ ▐████▌ ██████████████
|
|
8
|
+
// ██████████████ ▐████▌ ██████████████
|
|
9
|
+
// ▐████▌ ▐████▌
|
|
10
|
+
// ▐████▌ ▐████▌
|
|
11
|
+
// ▐████▌ ▐████▌
|
|
12
|
+
// ▐████▌ ▐████▌
|
|
13
|
+
// ▐████▌ ▐████▌
|
|
14
|
+
// ▐████▌ ▐████▌
|
|
15
|
+
|
|
16
|
+
pragma solidity 0.8.4;
|
|
17
|
+
|
|
18
|
+
import "./IVault.sol";
|
|
19
|
+
import "../bank/Bank.sol";
|
|
20
|
+
import "../token/TBTC.sol";
|
|
21
|
+
|
|
22
|
+
/// @title TBTC application vault
|
|
23
|
+
/// @notice TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of
|
|
24
|
+
/// Bitcoin. It facilitates Bitcoin holders to act on the Ethereum
|
|
25
|
+
/// blockchain and access the decentralized finance (DeFi) ecosystem.
|
|
26
|
+
/// TBTC Vault mints and redeems TBTC based on Bitcoin balances in the
|
|
27
|
+
/// Bank.
|
|
28
|
+
/// @dev TBTC Vault is the owner of TBTC token contract and is the only contract
|
|
29
|
+
/// minting the token.
|
|
30
|
+
contract TBTCVault is IVault {
|
|
31
|
+
Bank public bank;
|
|
32
|
+
TBTC public tbtcToken;
|
|
33
|
+
|
|
34
|
+
event Minted(address indexed to, uint256 amount);
|
|
35
|
+
|
|
36
|
+
event Redeemed(address indexed from, uint256 amount);
|
|
37
|
+
|
|
38
|
+
modifier onlyBank() {
|
|
39
|
+
require(msg.sender == address(bank), "Caller is not the Bank");
|
|
40
|
+
_;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
constructor(Bank _bank, TBTC _tbtcToken) {
|
|
44
|
+
require(
|
|
45
|
+
address(_bank) != address(0),
|
|
46
|
+
"Bank can not be the zero address"
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
require(
|
|
50
|
+
address(_tbtcToken) != address(0),
|
|
51
|
+
"TBTC token can not be the zero address"
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
bank = _bank;
|
|
55
|
+
tbtcToken = _tbtcToken;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// @notice Transfers the given `amount` of the Bank balance from caller
|
|
59
|
+
/// to TBTC Vault, and mints `amount` of TBTC to the caller.
|
|
60
|
+
/// @dev TBTC Vault must have an allowance for caller's balance in the Bank
|
|
61
|
+
/// for at least `amount`.
|
|
62
|
+
/// @param amount Amount of TBTC to mint
|
|
63
|
+
function mint(uint256 amount) external {
|
|
64
|
+
address minter = msg.sender;
|
|
65
|
+
require(
|
|
66
|
+
bank.balanceOf(minter) >= amount,
|
|
67
|
+
"Amount exceeds balance in the bank"
|
|
68
|
+
);
|
|
69
|
+
_mint(minter, amount);
|
|
70
|
+
bank.transferBalanceFrom(minter, address(this), amount);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// @notice Transfers the given `amount` of the Bank balance from the caller
|
|
74
|
+
/// to TBTC Vault and mints `amount` of TBTC to the caller.
|
|
75
|
+
/// @dev Can only be called by the Bank via `approveBalanceAndCall`.
|
|
76
|
+
/// @param owner The owner who approved their Bank balance
|
|
77
|
+
/// @param amount Amount of TBTC to mint
|
|
78
|
+
function receiveBalanceApproval(address owner, uint256 amount)
|
|
79
|
+
external
|
|
80
|
+
override
|
|
81
|
+
onlyBank
|
|
82
|
+
{
|
|
83
|
+
require(
|
|
84
|
+
bank.balanceOf(owner) >= amount,
|
|
85
|
+
"Amount exceeds balance in the bank"
|
|
86
|
+
);
|
|
87
|
+
_mint(owner, amount);
|
|
88
|
+
bank.transferBalanceFrom(owner, address(this), amount);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// @notice Mints the same amount of TBTC as the deposited amount for each
|
|
92
|
+
/// depositor in the array. Can only be called by the Bank after the
|
|
93
|
+
/// Bridge swept deposits and Bank increased balance for the
|
|
94
|
+
/// vault.
|
|
95
|
+
/// @dev Fails if `depositors` array is empty. Expects the length of
|
|
96
|
+
/// `depositors` and `depositedAmounts` is the same.
|
|
97
|
+
function receiveBalanceIncrease(
|
|
98
|
+
address[] calldata depositors,
|
|
99
|
+
uint256[] calldata depositedAmounts
|
|
100
|
+
) external override onlyBank {
|
|
101
|
+
require(depositors.length != 0, "No depositors specified");
|
|
102
|
+
for (uint256 i = 0; i < depositors.length; i++) {
|
|
103
|
+
_mint(depositors[i], depositedAmounts[i]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// @notice Burns `amount` of TBTC from the caller's account and transfers
|
|
108
|
+
/// `amount` back to the caller's balance in the Bank.
|
|
109
|
+
/// @dev Caller must have at least `amount` of TBTC approved to
|
|
110
|
+
/// TBTC Vault.
|
|
111
|
+
/// @param amount Amount of TBTC to redeem
|
|
112
|
+
function redeem(uint256 amount) external {
|
|
113
|
+
_redeem(msg.sender, amount);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// @notice Burns `amount` of TBTC from the caller's account and transfers
|
|
117
|
+
/// `amount` back to the caller's balance in the Bank.
|
|
118
|
+
/// @dev This function is doing the same as `redeem` but it allows to
|
|
119
|
+
/// execute redemption without an additional approval transaction.
|
|
120
|
+
/// The function can be called only via `approveAndCall` of TBTC token.
|
|
121
|
+
/// @param from TBTC token holder executing redemption
|
|
122
|
+
/// @param amount Amount of TBTC to redeem
|
|
123
|
+
/// @param token TBTC token address
|
|
124
|
+
function receiveApproval(
|
|
125
|
+
address from,
|
|
126
|
+
uint256 amount,
|
|
127
|
+
address token,
|
|
128
|
+
bytes calldata
|
|
129
|
+
) external {
|
|
130
|
+
require(token == address(tbtcToken), "Token is not TBTC");
|
|
131
|
+
require(msg.sender == token, "Only TBTC caller allowed");
|
|
132
|
+
_redeem(from, amount);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// slither-disable-next-line calls-loop
|
|
136
|
+
function _mint(address minter, uint256 amount) internal {
|
|
137
|
+
emit Minted(minter, amount);
|
|
138
|
+
tbtcToken.mint(minter, amount);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function _redeem(address redeemer, uint256 amount) internal {
|
|
142
|
+
emit Redeemed(redeemer, amount);
|
|
143
|
+
tbtcToken.burnFrom(redeemer, amount);
|
|
144
|
+
bank.transferBalance(redeemer, amount);
|
|
145
|
+
}
|
|
146
|
+
}
|