@keep-network/tbtc-v2 0.1.0 → 0.1.1-dev

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 (296) hide show
  1. package/README.adoc +12 -0
  2. package/artifacts/.chainId +1 -1
  3. package/artifacts/Bank.json +807 -0
  4. package/artifacts/Bridge.json +2300 -0
  5. package/artifacts/Deposit.json +117 -0
  6. package/artifacts/DepositSweep.json +77 -0
  7. package/artifacts/EcdsaDkgValidator.json +532 -0
  8. package/artifacts/EcdsaInactivity.json +156 -0
  9. package/artifacts/EcdsaSortitionPool.json +1004 -0
  10. package/artifacts/Fraud.json +164 -0
  11. package/artifacts/KeepRegistry.json +99 -0
  12. package/artifacts/KeepStake.json +286 -0
  13. package/artifacts/KeepToken.json +711 -0
  14. package/artifacts/KeepTokenStaking.json +483 -0
  15. package/artifacts/MovingFunds.json +249 -0
  16. package/artifacts/NuCypherStakingEscrow.json +256 -0
  17. package/artifacts/NuCypherToken.json +711 -0
  18. package/artifacts/RandomBeaconStub.json +141 -0
  19. package/artifacts/Redemption.json +174 -0
  20. package/artifacts/ReimbursementPool.json +509 -0
  21. package/artifacts/Relay.json +123 -0
  22. package/artifacts/T.json +1148 -0
  23. package/artifacts/TBTC.json +36 -35
  24. package/artifacts/TBTCToken.json +738 -0
  25. package/artifacts/TBTCVault.json +691 -0
  26. package/artifacts/TokenStaking.json +2288 -0
  27. package/artifacts/TokenholderGovernor.json +1795 -0
  28. package/artifacts/TokenholderTimelock.json +1058 -0
  29. package/artifacts/VendingMachine.json +34 -33
  30. package/artifacts/VendingMachineKeep.json +400 -0
  31. package/artifacts/VendingMachineNuCypher.json +400 -0
  32. package/artifacts/WalletRegistry.json +1843 -0
  33. package/artifacts/WalletRegistryGovernance.json +2754 -0
  34. package/artifacts/Wallets.json +186 -0
  35. package/artifacts/solcInputs/5e62cff1ead0900b07facca4b559e818.json +314 -0
  36. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  37. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
  38. package/build/contracts/bank/Bank.sol/Bank.dbg.json +4 -0
  39. package/build/contracts/bank/Bank.sol/Bank.json +542 -0
  40. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +4 -0
  41. package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.json +34 -0
  42. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
  43. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
  44. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +4 -0
  45. package/build/contracts/bridge/Bridge.sol/Bridge.json +2686 -0
  46. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
  47. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +226 -0
  48. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
  49. package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
  50. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
  51. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
  52. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
  53. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
  54. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
  55. package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
  56. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
  57. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
  58. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
  59. package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
  60. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
  61. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +138 -0
  62. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
  63. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
  64. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
  65. package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
  66. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  67. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
  68. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
  69. package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
  70. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  71. package/build/contracts/token/TBTC.sol/TBTC.json +4 -4
  72. package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
  73. package/build/contracts/vault/DonationVault.sol/DonationVault.json +108 -0
  74. package/build/contracts/vault/IVault.sol/IVault.dbg.json +4 -0
  75. package/build/contracts/vault/IVault.sol/IVault.json +52 -0
  76. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +4 -0
  77. package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +449 -0
  78. package/contracts/GovernanceUtils.sol +4 -4
  79. package/contracts/bank/Bank.sol +436 -0
  80. package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
  81. package/contracts/bridge/BitcoinTx.sol +326 -0
  82. package/contracts/bridge/Bridge.sol +1793 -0
  83. package/contracts/bridge/BridgeState.sol +739 -0
  84. package/contracts/bridge/Deposit.sol +269 -0
  85. package/contracts/bridge/DepositSweep.sol +574 -0
  86. package/contracts/bridge/EcdsaLib.sol +45 -0
  87. package/contracts/bridge/Fraud.sol +579 -0
  88. package/contracts/bridge/Heartbeat.sol +112 -0
  89. package/contracts/bridge/IRelay.sol +28 -0
  90. package/contracts/bridge/MovingFunds.sol +1077 -0
  91. package/contracts/bridge/Redemption.sol +1020 -0
  92. package/contracts/bridge/VendingMachine.sol +2 -2
  93. package/contracts/bridge/Wallets.sol +719 -0
  94. package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
  95. package/contracts/hardhat-dependency-compiler/@keep-network/ecdsa/contracts/WalletRegistry.sol +3 -0
  96. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
  97. package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
  98. package/contracts/token/TBTC.sol +1 -1
  99. package/contracts/vault/DonationVault.sol +125 -0
  100. package/contracts/vault/IVault.sol +44 -0
  101. package/contracts/vault/TBTCVault.sol +305 -0
  102. package/deploy/00_resolve_relay.ts +28 -0
  103. package/deploy/00_resolve_tbtc_v1_token.ts +1 -1
  104. package/deploy/01_deploy_tbtc_v2_token.ts +8 -1
  105. package/deploy/02_deploy_vending_machine.ts +7 -0
  106. package/deploy/{03_transfer_roles.ts → 03_transfer_vending_machine_roles.ts} +1 -1
  107. package/deploy/04_deploy_bank.ts +27 -0
  108. package/deploy/05_deploy_bridge.ts +80 -0
  109. package/deploy/06_deploy_tbtc_vault.ts +30 -0
  110. package/deploy/07_bank_update_bridge.ts +19 -0
  111. package/deploy/08_transfer_bank_ownership.ts +15 -0
  112. package/deploy/09_transfer_tbtc_vault_ownership.ts +15 -0
  113. package/deploy/10_transfer_bridge_governance.ts +20 -0
  114. package/deploy/11_initialize_wallet_owner.ts +18 -0
  115. package/deploy/11_transfer_proxy_admin_ownership.ts +30 -0
  116. package/deploy/12_deploy_proxy_admin_with_deputy.ts +33 -0
  117. package/export/deploy/00_resolve_relay.js +24 -0
  118. package/export/deploy/00_resolve_tbtc_v1_token.js +24 -0
  119. package/export/deploy/01_deploy_tbtc_v2_token.js +19 -0
  120. package/export/deploy/02_deploy_vending_machine.js +25 -0
  121. package/export/deploy/03_transfer_vending_machine_roles.js +19 -0
  122. package/export/deploy/04_deploy_bank.js +21 -0
  123. package/export/deploy/05_deploy_bridge.js +69 -0
  124. package/export/deploy/06_deploy_tbtc_vault.js +24 -0
  125. package/export/deploy/07_bank_update_bridge.js +13 -0
  126. package/export/deploy/08_transfer_bank_ownership.js +11 -0
  127. package/export/deploy/09_transfer_tbtc_vault_ownership.js +11 -0
  128. package/export/deploy/10_transfer_bridge_governance.js +11 -0
  129. package/export/deploy/11_initialize_wallet_owner.js +14 -0
  130. package/export/deploy/11_transfer_proxy_admin_ownership.js +23 -0
  131. package/export/deploy/12_deploy_proxy_admin_with_deputy.js +22 -0
  132. package/export/hardhat.config.js +169 -0
  133. package/export/test/bank/Bank.test.js +1012 -0
  134. package/export/test/bridge/Bridge.Deployment.test.js +76 -0
  135. package/export/test/bridge/Bridge.Deposit.test.js +1834 -0
  136. package/export/test/bridge/Bridge.Frauds.test.js +1349 -0
  137. package/export/test/bridge/Bridge.MovingFunds.test.js +2437 -0
  138. package/export/test/bridge/Bridge.Parameters.test.js +400 -0
  139. package/export/test/bridge/Bridge.Redemption.test.js +2523 -0
  140. package/export/test/bridge/Bridge.Vaults.test.js +74 -0
  141. package/export/test/bridge/Bridge.Wallets.test.js +1017 -0
  142. package/export/test/bridge/EcdsaLib.test.js +46 -0
  143. package/export/test/bridge/Heartbeat.test.js +77 -0
  144. package/export/test/bridge/VendingMachine.Upgrade.test.js +160 -0
  145. package/export/test/bridge/VendingMachine.test.js +762 -0
  146. package/export/test/data/deposit-sweep.js +655 -0
  147. package/export/test/data/ecdsa.js +18 -0
  148. package/export/test/data/fraud.js +158 -0
  149. package/export/test/data/moving-funds.js +815 -0
  150. package/export/test/data/redemption.js +1011 -0
  151. package/export/test/fixtures/bridge.js +54 -0
  152. package/export/test/fixtures/index.js +57 -0
  153. package/export/test/helpers/contract-test-helpers.js +18 -0
  154. package/export/test/integration/Slashing.test.js +279 -0
  155. package/export/test/integration/WalleCreation.test.js +66 -0
  156. package/export/test/integration/utils/ecdsa-wallet-registry.js +137 -0
  157. package/export/test/integration/utils/fixture.js +77 -0
  158. package/export/test/integration/utils/gas.js +36 -0
  159. package/export/test/integration/utils/random-beacon.js +26 -0
  160. package/export/test/integration/utils/staking.js +19 -0
  161. package/export/test/vault/DonationVault.test.js +202 -0
  162. package/export/test/vault/TBTCVault.Redemption.test.js +357 -0
  163. package/export/test/vault/TBTCVault.test.js +768 -0
  164. package/export/typechain/BTCUtils.js +2 -0
  165. package/export/typechain/Bank.js +2 -0
  166. package/export/typechain/BankStub.js +2 -0
  167. package/export/typechain/Bridge.js +2 -0
  168. package/export/typechain/BridgeState.js +2 -0
  169. package/export/typechain/BridgeStub.js +2 -0
  170. package/export/typechain/Deposit.js +2 -0
  171. package/export/typechain/DepositSweep.js +2 -0
  172. package/export/typechain/DonationVault.js +2 -0
  173. package/export/typechain/ERC165.js +2 -0
  174. package/export/typechain/ERC1967Proxy.js +2 -0
  175. package/export/typechain/ERC1967Upgrade.js +2 -0
  176. package/export/typechain/ERC20WithPermit.js +2 -0
  177. package/export/typechain/ERC721.js +2 -0
  178. package/export/typechain/EcdsaAuthorization.js +2 -0
  179. package/export/typechain/EcdsaDkg.js +2 -0
  180. package/export/typechain/EcdsaDkgValidator.js +2 -0
  181. package/export/typechain/EcdsaInactivity.js +2 -0
  182. package/export/typechain/Fraud.js +2 -0
  183. package/export/typechain/Governable.js +2 -0
  184. package/export/typechain/HeartbeatStub.js +2 -0
  185. package/export/typechain/IApplication.js +2 -0
  186. package/export/typechain/IApproveAndCall.js +2 -0
  187. package/export/typechain/IBeacon.js +2 -0
  188. package/export/typechain/IERC165.js +2 -0
  189. package/export/typechain/IERC1822Proxiable.js +2 -0
  190. package/export/typechain/IERC20.js +2 -0
  191. package/export/typechain/IERC20Metadata.js +2 -0
  192. package/export/typechain/IERC20WithPermit.js +2 -0
  193. package/export/typechain/IERC721.js +2 -0
  194. package/export/typechain/IERC721Metadata.js +2 -0
  195. package/export/typechain/IERC721Receiver.js +2 -0
  196. package/export/typechain/IRandomBeacon.js +2 -0
  197. package/export/typechain/IRandomBeaconConsumer.js +2 -0
  198. package/export/typechain/IReceiveApproval.js +2 -0
  199. package/export/typechain/IReceiveBalanceApproval.js +2 -0
  200. package/export/typechain/IRelay.js +2 -0
  201. package/export/typechain/IStaking.js +2 -0
  202. package/export/typechain/IVault.js +2 -0
  203. package/export/typechain/IWalletOwner.js +2 -0
  204. package/export/typechain/IWalletRegistry.js +2 -0
  205. package/export/typechain/Initializable.js +2 -0
  206. package/export/typechain/MisfundRecovery.js +2 -0
  207. package/export/typechain/MovingFunds.js +2 -0
  208. package/export/typechain/Ownable.js +2 -0
  209. package/export/typechain/Proxy.js +2 -0
  210. package/export/typechain/ProxyAdmin.js +2 -0
  211. package/export/typechain/ReceiveApprovalStub.js +2 -0
  212. package/export/typechain/Redemption.js +2 -0
  213. package/export/typechain/Reimbursable.js +2 -0
  214. package/export/typechain/ReimbursementPool.js +2 -0
  215. package/export/typechain/Rewards.js +2 -0
  216. package/export/typechain/SortitionPool.js +2 -0
  217. package/export/typechain/SortitionTree.js +2 -0
  218. package/export/typechain/TBTC.js +2 -0
  219. package/export/typechain/TBTCVault.js +2 -0
  220. package/export/typechain/TestERC20.js +2 -0
  221. package/export/typechain/TestERC721.js +2 -0
  222. package/export/typechain/TestEcdsaLib.js +2 -0
  223. package/export/typechain/TestRelay.js +2 -0
  224. package/export/typechain/TransparentUpgradeableProxy.js +2 -0
  225. package/export/typechain/VendingMachine.js +2 -0
  226. package/export/typechain/WalletRegistry.js +2 -0
  227. package/export/typechain/Wallets.js +2 -0
  228. package/export/typechain/common.js +2 -0
  229. package/export/typechain/factories/BTCUtils__factory.js +94 -0
  230. package/export/typechain/factories/BankStub__factory.js +586 -0
  231. package/export/typechain/factories/Bank__factory.js +573 -0
  232. package/export/typechain/factories/BridgeState__factory.js +257 -0
  233. package/export/typechain/factories/BridgeStub__factory.js +2912 -0
  234. package/export/typechain/factories/Bridge__factory.js +2526 -0
  235. package/export/typechain/factories/DepositSweep__factory.js +61 -0
  236. package/export/typechain/factories/Deposit__factory.js +103 -0
  237. package/export/typechain/factories/DonationVault__factory.js +139 -0
  238. package/export/typechain/factories/ERC165__factory.js +38 -0
  239. package/export/typechain/factories/ERC1967Proxy__factory.js +111 -0
  240. package/export/typechain/factories/ERC1967Upgrade__factory.js +64 -0
  241. package/export/typechain/factories/ERC20WithPermit__factory.js +524 -0
  242. package/export/typechain/factories/ERC721__factory.js +388 -0
  243. package/export/typechain/factories/EcdsaAuthorization__factory.js +211 -0
  244. package/export/typechain/factories/EcdsaDkgValidator__factory.js +441 -0
  245. package/export/typechain/factories/EcdsaDkg__factory.js +192 -0
  246. package/export/typechain/factories/EcdsaInactivity__factory.js +134 -0
  247. package/export/typechain/factories/Fraud__factory.js +117 -0
  248. package/export/typechain/factories/Governable__factory.js +64 -0
  249. package/export/typechain/factories/HeartbeatStub__factory.js +61 -0
  250. package/export/typechain/factories/IApplication__factory.js +152 -0
  251. package/export/typechain/factories/IApproveAndCall__factory.js +48 -0
  252. package/export/typechain/factories/IBeacon__factory.js +32 -0
  253. package/export/typechain/factories/IERC165__factory.js +38 -0
  254. package/export/typechain/factories/IERC1822Proxiable__factory.js +32 -0
  255. package/export/typechain/factories/IERC20Metadata__factory.js +241 -0
  256. package/export/typechain/factories/IERC20WithPermit__factory.js +389 -0
  257. package/export/typechain/factories/IERC20__factory.js +202 -0
  258. package/export/typechain/factories/IERC721Metadata__factory.js +349 -0
  259. package/export/typechain/factories/IERC721Receiver__factory.js +53 -0
  260. package/export/typechain/factories/IERC721__factory.js +304 -0
  261. package/export/typechain/factories/IRandomBeaconConsumer__factory.js +37 -0
  262. package/export/typechain/factories/IRandomBeacon__factory.js +32 -0
  263. package/export/typechain/factories/IReceiveApproval__factory.js +47 -0
  264. package/export/typechain/factories/IReceiveBalanceApproval__factory.js +42 -0
  265. package/export/typechain/factories/IRelay__factory.js +45 -0
  266. package/export/typechain/factories/IStaking__factory.js +722 -0
  267. package/export/typechain/factories/IVault__factory.js +60 -0
  268. package/export/typechain/factories/IWalletOwner__factory.js +65 -0
  269. package/export/typechain/factories/IWalletRegistry__factory.js +138 -0
  270. package/export/typechain/factories/Initializable__factory.js +32 -0
  271. package/export/typechain/factories/MisfundRecovery__factory.js +145 -0
  272. package/export/typechain/factories/MovingFunds__factory.js +169 -0
  273. package/export/typechain/factories/Ownable__factory.js +71 -0
  274. package/export/typechain/factories/ProxyAdmin__factory.js +191 -0
  275. package/export/typechain/factories/Proxy__factory.js +27 -0
  276. package/export/typechain/factories/ReceiveApprovalStub__factory.js +127 -0
  277. package/export/typechain/factories/Redemption__factory.js +123 -0
  278. package/export/typechain/factories/Reimbursable__factory.js +58 -0
  279. package/export/typechain/factories/ReimbursementPool__factory.js +350 -0
  280. package/export/typechain/factories/Rewards__factory.js +117 -0
  281. package/export/typechain/factories/SortitionPool__factory.js +610 -0
  282. package/export/typechain/factories/SortitionTree__factory.js +149 -0
  283. package/export/typechain/factories/TBTCVault__factory.js +480 -0
  284. package/export/typechain/factories/TBTC__factory.js +564 -0
  285. package/export/typechain/factories/TestERC20__factory.js +539 -0
  286. package/export/typechain/factories/TestERC721__factory.js +421 -0
  287. package/export/typechain/factories/TestEcdsaLib__factory.js +66 -0
  288. package/export/typechain/factories/TestRelay__factory.js +94 -0
  289. package/export/typechain/factories/TransparentUpgradeableProxy__factory.js +186 -0
  290. package/export/typechain/factories/VendingMachine__factory.js +549 -0
  291. package/export/typechain/factories/WalletRegistry__factory.js +1919 -0
  292. package/export/typechain/factories/Wallets__factory.js +143 -0
  293. package/export/typechain/index.js +132 -0
  294. package/export.json +15932 -503
  295. package/package.json +47 -26
  296. package/artifacts/solcInputs/7cc3eda3cb3ff2522d18b5e7b31ea228.json +0 -104
@@ -0,0 +1,1834 @@
1
+ "use strict";
2
+ /* eslint-disable no-underscore-dangle */
3
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ var __importDefault = (this && this.__importDefault) || function (mod) {
28
+ return (mod && mod.__esModule) ? mod : { "default": mod };
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ const hardhat_1 = require("hardhat");
32
+ const chai_1 = __importStar(require("chai"));
33
+ const smock_1 = require("@defi-wonderland/smock");
34
+ const bridge_1 = __importDefault(require("../fixtures/bridge"));
35
+ const fixtures_1 = require("../fixtures");
36
+ const deposit_sweep_1 = require("../data/deposit-sweep");
37
+ chai_1.default.use(smock_1.smock.matchers);
38
+ const { createSnapshot, restoreSnapshot } = hardhat_1.helpers.snapshot;
39
+ const { lastBlockTime } = hardhat_1.helpers.time;
40
+ const ZERO_ADDRESS = hardhat_1.ethers.constants.AddressZero;
41
+ describe("Bridge - Deposit", () => {
42
+ let governance;
43
+ let treasury;
44
+ let bank;
45
+ let relay;
46
+ let BridgeFactory;
47
+ let bridge;
48
+ before(async () => {
49
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
50
+ ;
51
+ ({ governance, treasury, bank, relay, BridgeFactory, bridge } =
52
+ await hardhat_1.waffle.loadFixture(bridge_1.default));
53
+ // Set the deposit dust threshold to 0.0001 BTC, i.e. 100x smaller than
54
+ // the initial value in the Bridge in order to save test Bitcoins.
55
+ await bridge.setDepositDustThreshold(10000);
56
+ });
57
+ describe("revealDeposit", () => {
58
+ // Data of a proper P2SH deposit funding transaction. Little-endian hash is:
59
+ // 0x17350f81cdb61cd8d7014ad1507d4af8d032b75812cf88d2c636c1c022991af2 and
60
+ // this is the same as `expectedP2SHDeposit.transaction` mentioned in
61
+ // tbtc-ts/test/deposit.test.ts file.
62
+ const P2SHFundingTx = {
63
+ version: "0x01000000",
64
+ inputVector: "0x018348cdeb551134fe1f19d378a8adec9b146671cb67b945b71bf56b20d" +
65
+ "c2b952f0100000000ffffffff",
66
+ outputVector: "0x02102700000000000017a9142c1444d23936c57bdd8b3e67e5938a5440c" +
67
+ "da455877ed73b00000000001600147ac2d9378a1c47e589dfb8095ca95ed2" +
68
+ "140d2726",
69
+ locktime: "0x00000000",
70
+ };
71
+ // Data of a proper P2WSH deposit funding transaction. Little-endian hash is:
72
+ // 0x6a81de17ce3da1eadc833c5fd9d85dac307d3b78235f57afbcd9f068fc01b99e and
73
+ // this is the same as `expectedP2WSHDeposit.transaction` mentioned in
74
+ // tbtc-ts/test/deposit.test.ts file.
75
+ const P2WSHFundingTx = {
76
+ version: "0x01000000",
77
+ inputVector: "0x018348cdeb551134fe1f19d378a8adec9b146671cb67b945b71bf56b20d" +
78
+ "c2b952f0100000000ffffffff",
79
+ outputVector: "0x021027000000000000220020df74a2e385542c87acfafa564ea4bc4fc4e" +
80
+ "b87d2b6a37d6c3b64722be83c636f10d73b00000000001600147ac2d9378a" +
81
+ "1c47e589dfb8095ca95ed2140d2726",
82
+ locktime: "0x00000000",
83
+ };
84
+ // Data matching the redeem script locking the funding output of
85
+ // P2SHFundingTx and P2WSHFundingTx.
86
+ const reveal = {
87
+ fundingOutputIndex: 0,
88
+ depositor: "0x934B98637cA318a4D6E7CA6ffd1690b8e77df637",
89
+ blindingFactor: "0xf9f0c90d00039523",
90
+ // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9.
91
+ walletPubKeyHash: "0x8db50eb52063ea9d98b3eac91489a90f738986f6",
92
+ // HASH160 of 0300d6f28a2f6bf9836f57fcda5d284c9a8f849316119779f0d6090830d97763a9.
93
+ refundPubKeyHash: "0x28e081f285138ccbe389c1eb8985716230129f89",
94
+ refundLocktime: "0x60bcea61",
95
+ vault: "0x594cfd89700040163727828AE20B52099C58F02C",
96
+ };
97
+ context("when wallet is in Live state", () => {
98
+ before(async () => {
99
+ await createSnapshot();
100
+ await bridge.connect(governance).setVaultStatus(reveal.vault, true);
101
+ // Simulate the wallet is a Live one and is known in the system.
102
+ await bridge.setWallet(reveal.walletPubKeyHash, {
103
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
104
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
105
+ pendingRedemptionsValue: 0,
106
+ createdAt: await lastBlockTime(),
107
+ movingFundsRequestedAt: 0,
108
+ closingStartedAt: 0,
109
+ pendingMovedFundsSweepRequestsCount: 0,
110
+ state: fixtures_1.walletState.Live,
111
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
112
+ });
113
+ });
114
+ after(async () => {
115
+ await restoreSnapshot();
116
+ });
117
+ context("when funding transaction is P2SH", () => {
118
+ context("when funding output script hash is correct", () => {
119
+ context("when deposit was not revealed yet", () => {
120
+ context("when amount is not below the dust threshold", () => {
121
+ context("when deposit is routed to a trusted vault", () => {
122
+ let tx;
123
+ before(async () => {
124
+ await createSnapshot();
125
+ tx = await bridge.revealDeposit(P2SHFundingTx, reveal);
126
+ });
127
+ after(async () => {
128
+ await restoreSnapshot();
129
+ });
130
+ it("should store proper deposit data", async () => {
131
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
132
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
133
+ "0x17350f81cdb61cd8d7014ad1507d4af8d032b75812cf88d2c636c1c022991af2",
134
+ reveal.fundingOutputIndex,
135
+ ]);
136
+ const deposit = await bridge.deposits(depositKey);
137
+ // Depositor address, same as in `reveal.depositor`.
138
+ (0, chai_1.expect)(deposit.depositor).to.be.equal("0x934B98637cA318a4D6E7CA6ffd1690b8e77df637");
139
+ // Deposit amount in satoshi. In this case it's 10000 satoshi
140
+ // because the P2SH deposit transaction set this value for the
141
+ // funding output.
142
+ (0, chai_1.expect)(deposit.amount).to.be.equal(10000);
143
+ // Revealed time should be set.
144
+ (0, chai_1.expect)(deposit.revealedAt).to.be.equal(await lastBlockTime());
145
+ // Deposit vault, same as in `reveal.vault`.
146
+ (0, chai_1.expect)(deposit.vault).to.be.equal("0x594cfd89700040163727828AE20B52099C58F02C");
147
+ // Treasury fee should be computed according to the current
148
+ // value of the `depositTreasuryFeeDivisor`.
149
+ (0, chai_1.expect)(deposit.treasuryFee).to.be.equal(5);
150
+ // Swept time should be unset.
151
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(0);
152
+ });
153
+ it("should emit DepositRevealed event", async () => {
154
+ await (0, chai_1.expect)(tx)
155
+ .to.emit(bridge, "DepositRevealed")
156
+ .withArgs("0x17350f81cdb61cd8d7014ad1507d4af8d032b75812cf88d2c636c1c022991af2", reveal.fundingOutputIndex, "0x934B98637cA318a4D6E7CA6ffd1690b8e77df637", 10000, "0xf9f0c90d00039523", "0x8db50eb52063ea9d98b3eac91489a90f738986f6", "0x28e081f285138ccbe389c1eb8985716230129f89", "0x60bcea61", reveal.vault);
157
+ });
158
+ });
159
+ context("when deposit is not routed to a vault", () => {
160
+ let tx;
161
+ let nonRoutedReveal;
162
+ before(async () => {
163
+ await createSnapshot();
164
+ nonRoutedReveal = { ...reveal };
165
+ nonRoutedReveal.vault = ZERO_ADDRESS;
166
+ tx = await bridge.revealDeposit(P2SHFundingTx, nonRoutedReveal);
167
+ });
168
+ after(async () => {
169
+ await restoreSnapshot();
170
+ });
171
+ it("should accept the deposit", async () => {
172
+ await (0, chai_1.expect)(tx)
173
+ .to.emit(bridge, "DepositRevealed")
174
+ .withArgs("0x17350f81cdb61cd8d7014ad1507d4af8d032b75812cf88d2c636c1c022991af2", reveal.fundingOutputIndex, "0x934B98637cA318a4D6E7CA6ffd1690b8e77df637", 10000, "0xf9f0c90d00039523", "0x8db50eb52063ea9d98b3eac91489a90f738986f6", "0x28e081f285138ccbe389c1eb8985716230129f89", "0x60bcea61", ZERO_ADDRESS);
175
+ });
176
+ });
177
+ context("when deposit is routed to a non-trusted vault", () => {
178
+ let nonTrustedVaultReveal;
179
+ before(async () => {
180
+ await createSnapshot();
181
+ nonTrustedVaultReveal = { ...reveal };
182
+ nonTrustedVaultReveal.vault =
183
+ "0x92499afEAD6c41f757Ec3558D0f84bf7ec5aD967";
184
+ });
185
+ after(async () => {
186
+ await restoreSnapshot();
187
+ });
188
+ it("should revert", async () => {
189
+ await (0, chai_1.expect)(bridge.revealDeposit(P2SHFundingTx, nonTrustedVaultReveal)).to.be.revertedWith("Vault is not trusted");
190
+ });
191
+ });
192
+ });
193
+ context("when amount is below the dust threshold", () => {
194
+ before(async () => {
195
+ await createSnapshot();
196
+ // The `P2SHFundingTx` used within this scenario has an output
197
+ // whose value is 10000 satoshi. To make the scenario happen, it
198
+ // is enough that the contract's deposit dust threshold is
199
+ // bigger by 1 satoshi.
200
+ await bridge.setDepositDustThreshold(10001);
201
+ });
202
+ after(async () => {
203
+ await restoreSnapshot();
204
+ });
205
+ it("should revert", async () => {
206
+ await (0, chai_1.expect)(bridge.revealDeposit(P2SHFundingTx, reveal)).to.be.revertedWith("Deposit amount too small");
207
+ });
208
+ });
209
+ });
210
+ context("when deposit was already revealed", () => {
211
+ before(async () => {
212
+ await createSnapshot();
213
+ await bridge.revealDeposit(P2SHFundingTx, reveal);
214
+ });
215
+ after(async () => {
216
+ await restoreSnapshot();
217
+ });
218
+ it("should revert", async () => {
219
+ await (0, chai_1.expect)(bridge.revealDeposit(P2SHFundingTx, reveal)).to.be.revertedWith("Deposit already revealed");
220
+ });
221
+ });
222
+ });
223
+ context("when funding output script hash is wrong", () => {
224
+ it("should revert", async () => {
225
+ // Corrupt reveal data by setting a wrong depositor address.
226
+ const corruptedReveal = { ...reveal };
227
+ corruptedReveal.depositor =
228
+ "0x24CbaB95C69e5bcbE328252F957A39d906eE75f3";
229
+ await (0, chai_1.expect)(bridge.revealDeposit(P2SHFundingTx, corruptedReveal)).to.be.revertedWith("Wrong 20-byte script hash");
230
+ });
231
+ });
232
+ });
233
+ context("when funding transaction is P2WSH", () => {
234
+ context("when funding output script hash is correct", () => {
235
+ context("when deposit was not revealed yet", () => {
236
+ context("when deposit is routed to a trusted vault", () => {
237
+ let tx;
238
+ before(async () => {
239
+ await createSnapshot();
240
+ tx = await bridge.revealDeposit(P2WSHFundingTx, reveal);
241
+ });
242
+ after(async () => {
243
+ await restoreSnapshot();
244
+ });
245
+ it("should store proper deposit data", async () => {
246
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
247
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
248
+ "0x6a81de17ce3da1eadc833c5fd9d85dac307d3b78235f57afbcd9f068fc01b99e",
249
+ reveal.fundingOutputIndex,
250
+ ]);
251
+ const deposit = await bridge.deposits(depositKey);
252
+ // Depositor address, same as in `reveal.depositor`.
253
+ (0, chai_1.expect)(deposit.depositor).to.be.equal("0x934B98637cA318a4D6E7CA6ffd1690b8e77df637");
254
+ // Deposit amount in satoshi. In this case it's 10000 satoshi
255
+ // because the P2SH deposit transaction set this value for the
256
+ // funding output.
257
+ (0, chai_1.expect)(deposit.amount).to.be.equal(10000);
258
+ // Revealed time should be set.
259
+ (0, chai_1.expect)(deposit.revealedAt).to.be.equal(await lastBlockTime());
260
+ // Deposit vault, same as in `reveal.vault`.
261
+ (0, chai_1.expect)(deposit.vault).to.be.equal("0x594cfd89700040163727828AE20B52099C58F02C");
262
+ // Treasury fee should be computed according to the current
263
+ // value of the `depositTreasuryFeeDivisor`.
264
+ (0, chai_1.expect)(deposit.treasuryFee).to.be.equal(5);
265
+ // Swept time should be unset.
266
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(0);
267
+ });
268
+ it("should emit DepositRevealed event", async () => {
269
+ await (0, chai_1.expect)(tx)
270
+ .to.emit(bridge, "DepositRevealed")
271
+ .withArgs("0x6a81de17ce3da1eadc833c5fd9d85dac307d3b78235f57afbcd9f068fc01b99e", reveal.fundingOutputIndex, "0x934B98637cA318a4D6E7CA6ffd1690b8e77df637", 10000, "0xf9f0c90d00039523", "0x8db50eb52063ea9d98b3eac91489a90f738986f6", "0x28e081f285138ccbe389c1eb8985716230129f89", "0x60bcea61", reveal.vault);
272
+ });
273
+ });
274
+ context("when deposit is not routed to a vault", () => {
275
+ let tx;
276
+ let nonRoutedReveal;
277
+ before(async () => {
278
+ await createSnapshot();
279
+ nonRoutedReveal = { ...reveal };
280
+ nonRoutedReveal.vault = ZERO_ADDRESS;
281
+ tx = await bridge.revealDeposit(P2WSHFundingTx, nonRoutedReveal);
282
+ });
283
+ after(async () => {
284
+ await restoreSnapshot();
285
+ });
286
+ it("should accept the deposit", async () => {
287
+ await (0, chai_1.expect)(tx)
288
+ .to.emit(bridge, "DepositRevealed")
289
+ .withArgs("0x6a81de17ce3da1eadc833c5fd9d85dac307d3b78235f57afbcd9f068fc01b99e", reveal.fundingOutputIndex, "0x934B98637cA318a4D6E7CA6ffd1690b8e77df637", 10000, "0xf9f0c90d00039523", "0x8db50eb52063ea9d98b3eac91489a90f738986f6", "0x28e081f285138ccbe389c1eb8985716230129f89", "0x60bcea61", ZERO_ADDRESS);
290
+ });
291
+ });
292
+ context("when deposit is routed to a non-trusted vault", () => {
293
+ let nonTrustedVaultReveal;
294
+ before(async () => {
295
+ await createSnapshot();
296
+ nonTrustedVaultReveal = { ...reveal };
297
+ nonTrustedVaultReveal.vault =
298
+ "0x92499afEAD6c41f757Ec3558D0f84bf7ec5aD967";
299
+ });
300
+ after(async () => {
301
+ await restoreSnapshot();
302
+ });
303
+ it("should revert", async () => {
304
+ await (0, chai_1.expect)(bridge.revealDeposit(P2WSHFundingTx, nonTrustedVaultReveal)).to.be.revertedWith("Vault is not trusted");
305
+ });
306
+ });
307
+ });
308
+ context("when deposit was already revealed", () => {
309
+ before(async () => {
310
+ await createSnapshot();
311
+ await bridge.revealDeposit(P2WSHFundingTx, reveal);
312
+ });
313
+ after(async () => {
314
+ await restoreSnapshot();
315
+ });
316
+ it("should revert", async () => {
317
+ await (0, chai_1.expect)(bridge.revealDeposit(P2WSHFundingTx, reveal)).to.be.revertedWith("Deposit already revealed");
318
+ });
319
+ });
320
+ });
321
+ context("when funding output script hash is wrong", () => {
322
+ it("should revert", async () => {
323
+ // Corrupt reveal data by setting a wrong depositor address.
324
+ const corruptedReveal = { ...reveal };
325
+ corruptedReveal.depositor =
326
+ "0x24CbaB95C69e5bcbE328252F957A39d906eE75f3";
327
+ await (0, chai_1.expect)(bridge.revealDeposit(P2WSHFundingTx, corruptedReveal)).to.be.revertedWith("Wrong 32-byte script hash");
328
+ });
329
+ });
330
+ });
331
+ context("when funding transaction is neither P2SH nor P2WSH", () => {
332
+ it("should revert", async () => {
333
+ // Corrupt transaction output data by making a 21-byte script hash.
334
+ const corruptedP2SHFundingTx = { ...P2SHFundingTx };
335
+ corruptedP2SHFundingTx.outputVector =
336
+ "0x02102700000000000017a9156a6ade1c799a3e5a59678e776f21be14d66dc" +
337
+ "15ed8877ed73b00000000001600147ac2d9378a1c47e589dfb8095ca95ed2" +
338
+ "140d2726";
339
+ await (0, chai_1.expect)(bridge.revealDeposit(corruptedP2SHFundingTx, reveal)).to.be.revertedWith("Wrong script hash length");
340
+ });
341
+ });
342
+ });
343
+ context("when wallet is not in Live state", () => {
344
+ const testData = [
345
+ {
346
+ testName: "when wallet state is Unknown",
347
+ walletState: fixtures_1.walletState.Unknown,
348
+ },
349
+ {
350
+ testName: "when wallet state is MovingFunds",
351
+ walletState: fixtures_1.walletState.MovingFunds,
352
+ },
353
+ {
354
+ testName: "when the source wallet is in the Closing state",
355
+ walletState: fixtures_1.walletState.Closing,
356
+ },
357
+ {
358
+ testName: "when wallet state is Closed",
359
+ walletState: fixtures_1.walletState.Closed,
360
+ },
361
+ {
362
+ testName: "when wallet state is Terminated",
363
+ walletState: fixtures_1.walletState.Terminated,
364
+ },
365
+ ];
366
+ testData.forEach((test) => {
367
+ context(test.testName, () => {
368
+ before(async () => {
369
+ await createSnapshot();
370
+ await bridge.setWallet(reveal.walletPubKeyHash, {
371
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
372
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
373
+ pendingRedemptionsValue: 0,
374
+ createdAt: await lastBlockTime(),
375
+ movingFundsRequestedAt: 0,
376
+ closingStartedAt: 0,
377
+ pendingMovedFundsSweepRequestsCount: 0,
378
+ state: test.walletState,
379
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
380
+ });
381
+ });
382
+ after(async () => {
383
+ await restoreSnapshot();
384
+ });
385
+ it("should revert", async () => {
386
+ await (0, chai_1.expect)(bridge.revealDeposit(P2SHFundingTx, reveal)).to.be.revertedWith("Wallet must be in Live state");
387
+ });
388
+ });
389
+ });
390
+ });
391
+ });
392
+ describe("submitDepositSweepProof", () => {
393
+ const walletDraft = {
394
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
395
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
396
+ pendingRedemptionsValue: 0,
397
+ createdAt: 0,
398
+ movingFundsRequestedAt: 0,
399
+ closingStartedAt: 0,
400
+ pendingMovedFundsSweepRequestsCount: 0,
401
+ state: fixtures_1.walletState.Unknown,
402
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
403
+ };
404
+ context("when the wallet state is Live", () => {
405
+ context("when transaction proof is valid", () => {
406
+ context("when there is only one output", () => {
407
+ context("when the single output is 20-byte", () => {
408
+ context("when single output is either P2PKH or P2WPKH", () => {
409
+ context("when main UTXO data are valid", () => {
410
+ context("when transaction fee does not exceed the deposit transaction maximum fee", () => {
411
+ context("when there is only one input", () => {
412
+ context("when the single input is a revealed unswept P2SH deposit", () => {
413
+ let tx;
414
+ const data = deposit_sweep_1.SingleP2SHDeposit;
415
+ // Take wallet public key hash from first deposit. All
416
+ // deposits in same sweep batch should have the same value
417
+ // of that field.
418
+ const { walletPubKeyHash } = data.deposits[0].reveal;
419
+ before(async () => {
420
+ await createSnapshot();
421
+ // Simulate the wallet is a Live one and is known in
422
+ // the system.
423
+ await bridge.setWallet(walletPubKeyHash, {
424
+ ...walletDraft,
425
+ state: fixtures_1.walletState.Live,
426
+ });
427
+ tx = await runDepositSweepScenario(data);
428
+ });
429
+ after(async () => {
430
+ await restoreSnapshot();
431
+ });
432
+ it("should mark deposit as swept", async () => {
433
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
434
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
435
+ data.deposits[0].fundingTx.hash,
436
+ data.deposits[0].reveal.fundingOutputIndex,
437
+ ]);
438
+ const deposit = await bridge.deposits(depositKey);
439
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(await lastBlockTime());
440
+ });
441
+ it("should update main UTXO for the given wallet", async () => {
442
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
443
+ // Amount can be checked by opening the sweep tx in a Bitcoin
444
+ // testnet explorer. In this case, the sum of inputs is
445
+ // 20000 satoshi (from the single deposit) and there is a
446
+ // fee of 1500 so the output value is 18500.
447
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 18500]);
448
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
449
+ });
450
+ it("should update the depositor's balance", async () => {
451
+ // The sum of sweep tx inputs is 20000 satoshi. The output
452
+ // value is 18500 so the transaction fee is 1500. There is
453
+ // only one deposit so it incurs the entire transaction fee.
454
+ // The deposit should also incur the treasury fee whose
455
+ // initial value is 0.05% of the deposited amount so the
456
+ // final depositor balance should be cut by 10 satoshi.
457
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(18490);
458
+ });
459
+ it("should transfer collected treasury fee", async () => {
460
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(10);
461
+ });
462
+ it("should emit DepositsSwept event", async () => {
463
+ await (0, chai_1.expect)(tx)
464
+ .to.emit(bridge, "DepositsSwept")
465
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
466
+ });
467
+ });
468
+ context("when the single input is a revealed unswept P2WSH deposit", () => {
469
+ let tx;
470
+ const data = deposit_sweep_1.SingleP2WSHDeposit;
471
+ // Take wallet public key hash from first deposit. All
472
+ // deposits in same sweep batch should have the same value
473
+ // of that field.
474
+ const { walletPubKeyHash } = data.deposits[0].reveal;
475
+ before(async () => {
476
+ await createSnapshot();
477
+ // Simulate the wallet is a Live one and is known in
478
+ // the system.
479
+ await bridge.setWallet(walletPubKeyHash, {
480
+ ...walletDraft,
481
+ state: fixtures_1.walletState.Live,
482
+ });
483
+ tx = await runDepositSweepScenario(data);
484
+ });
485
+ after(async () => {
486
+ await restoreSnapshot();
487
+ });
488
+ it("should mark deposit as swept", async () => {
489
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
490
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
491
+ data.deposits[0].fundingTx.hash,
492
+ data.deposits[0].reveal.fundingOutputIndex,
493
+ ]);
494
+ const deposit = await bridge.deposits(depositKey);
495
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(await lastBlockTime());
496
+ });
497
+ it("should update main UTXO for the given wallet", async () => {
498
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
499
+ // Amount can be checked by opening the sweep tx in a Bitcoin
500
+ // testnet explorer. In this case, the sum of inputs is
501
+ // 80000 satoshi (from the single deposit) and there is a
502
+ // fee of 2000 so the output value is 78000.
503
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 78000]);
504
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
505
+ });
506
+ it("should update the depositor's balance", async () => {
507
+ // The sum of sweep tx inputs is 80000 satoshi. The output
508
+ // value is 78000 so the fee is 2000. There is only one
509
+ // deposit so it incurs the entire fee. The deposit should
510
+ // also incur the treasury fee whose initial value is 0.05%
511
+ // of the deposited amount so the final depositor balance
512
+ // should be cut by 40 satoshi.
513
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(77960);
514
+ });
515
+ it("should transfer collected treasury fee", async () => {
516
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(40);
517
+ });
518
+ it("should emit DepositsSwept event", async () => {
519
+ await (0, chai_1.expect)(tx)
520
+ .to.emit(bridge, "DepositsSwept")
521
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
522
+ });
523
+ });
524
+ context("when the single input is a revealed unswept deposit with a trusted vault", async () => {
525
+ let vault;
526
+ let tx;
527
+ const data = deposit_sweep_1.SingleP2WSHDeposit;
528
+ // Take wallet public key hash from first deposit. All
529
+ // deposits in same sweep batch should have the same value
530
+ // of that field.
531
+ const { walletPubKeyHash } = data.deposits[0].reveal;
532
+ before(async () => {
533
+ await createSnapshot();
534
+ // Simulate the wallet is a Live one and is known in
535
+ // the system.
536
+ await bridge.setWallet(walletPubKeyHash, {
537
+ ...walletDraft,
538
+ state: fixtures_1.walletState.Live,
539
+ });
540
+ // Deploy a fake vault and mark it as trusted.
541
+ vault = await smock_1.smock.fake("IVault");
542
+ await bridge
543
+ .connect(governance)
544
+ .setVaultStatus(vault.address, true);
545
+ // Enrich the test data with the vault parameter.
546
+ const dataWithVault = JSON.parse(JSON.stringify(data));
547
+ dataWithVault.vault = vault.address;
548
+ dataWithVault.deposits[0].reveal.vault =
549
+ vault.address;
550
+ tx = await runDepositSweepScenario(dataWithVault);
551
+ });
552
+ after(async () => {
553
+ vault.receiveBalanceIncrease.reset();
554
+ await restoreSnapshot();
555
+ });
556
+ it("should mark deposit as swept", async () => {
557
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
558
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
559
+ data.deposits[0].fundingTx.hash,
560
+ data.deposits[0].reveal.fundingOutputIndex,
561
+ ]);
562
+ const deposit = await bridge.deposits(depositKey);
563
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(await lastBlockTime());
564
+ });
565
+ it("should update main UTXO for the given wallet", async () => {
566
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
567
+ // Amount can be checked by opening the sweep tx in a Bitcoin
568
+ // testnet explorer. In this case, the sum of inputs is
569
+ // 80000 satoshi (from the single deposit) and there is a
570
+ // fee of 2000 so the output value is 78000.
571
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 78000]);
572
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
573
+ });
574
+ it("should not update the depositor's balance", async () => {
575
+ // The depositor balance should not be increased.
576
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(0);
577
+ });
578
+ it("should update the vault's balance", async () => {
579
+ // The vault's balance should be increased by the
580
+ // sum of all swept deposits counted as follows:
581
+ //
582
+ // The sum of sweep tx inputs is 80000 satoshi. The output
583
+ // value is 78000 so the fee is 2000. There is only one
584
+ // deposit so it incurs the entire fee. The deposit should
585
+ // also incur the treasury fee whose initial value is 0.05%
586
+ // of the deposited amount so the final depositor balance
587
+ // should be cut by 40 satoshi. The final sum
588
+ // is 77960.
589
+ (0, chai_1.expect)(await bank.balanceOf(vault.address)).to.be.equal(77960);
590
+ });
591
+ it("should call the vault's receiveBalanceIncrease function", async () => {
592
+ (0, chai_1.expect)(vault.receiveBalanceIncrease).to.have.been.calledOnceWith([data.deposits[0].reveal.depositor], [77960]);
593
+ });
594
+ it("should transfer collected treasury fee", async () => {
595
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(40);
596
+ });
597
+ it("should emit DepositsSwept event", async () => {
598
+ await (0, chai_1.expect)(tx)
599
+ .to.emit(bridge, "DepositsSwept")
600
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
601
+ });
602
+ });
603
+ context("when the single input is a revealed unswept deposit with a non-trusted vault", async () => {
604
+ let vault;
605
+ let tx;
606
+ const data = deposit_sweep_1.SingleP2WSHDeposit;
607
+ // Take wallet public key hash from first deposit. All
608
+ // deposits in same sweep batch should have the same value
609
+ // of that field.
610
+ const { walletPubKeyHash } = data.deposits[0].reveal;
611
+ before(async () => {
612
+ await createSnapshot();
613
+ // Simulate the wallet is a Live one and is known in
614
+ // the system.
615
+ await bridge.setWallet(walletPubKeyHash, {
616
+ ...walletDraft,
617
+ state: fixtures_1.walletState.Live,
618
+ });
619
+ // Deploy a fake vault and mark it as trusted.
620
+ vault = await smock_1.smock.fake("IVault");
621
+ await bridge
622
+ .connect(governance)
623
+ .setVaultStatus(vault.address, true);
624
+ // Enrich the test data with the vault parameter.
625
+ const dataWithVault = JSON.parse(JSON.stringify(data));
626
+ dataWithVault.vault = vault.address;
627
+ dataWithVault.deposits[0].reveal.vault =
628
+ vault.address;
629
+ // Mark the vault as non-trusted just before
630
+ // proof submission.
631
+ const beforeProofActions = async () => {
632
+ await bridge
633
+ .connect(governance)
634
+ .setVaultStatus(vault.address, false);
635
+ };
636
+ tx = await runDepositSweepScenario(dataWithVault, beforeProofActions);
637
+ });
638
+ after(async () => {
639
+ await restoreSnapshot();
640
+ });
641
+ it("should mark deposit as swept", async () => {
642
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
643
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
644
+ data.deposits[0].fundingTx.hash,
645
+ data.deposits[0].reveal.fundingOutputIndex,
646
+ ]);
647
+ const deposit = await bridge.deposits(depositKey);
648
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(await lastBlockTime());
649
+ });
650
+ it("should update main UTXO for the given wallet", async () => {
651
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
652
+ // Amount can be checked by opening the sweep tx in a Bitcoin
653
+ // testnet explorer. In this case, the sum of inputs is
654
+ // 80000 satoshi (from the single deposit) and there is a
655
+ // fee of 2000 so the output value is 78000.
656
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 78000]);
657
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
658
+ });
659
+ it("should update the depositor's balance", async () => {
660
+ // The sum of sweep tx inputs is 80000 satoshi. The output
661
+ // value is 78000 so the fee is 2000. There is only one
662
+ // deposit so it incurs the entire fee. The deposit should
663
+ // also incur the treasury fee whose initial value is 0.05%
664
+ // of the deposited amount so the final depositor balance
665
+ // should be cut by 40 satoshi.
666
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(77960);
667
+ });
668
+ it("should transfer collected treasury fee", async () => {
669
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(40);
670
+ });
671
+ it("should emit DepositsSwept event", async () => {
672
+ await (0, chai_1.expect)(tx)
673
+ .to.emit(bridge, "DepositsSwept")
674
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
675
+ });
676
+ });
677
+ context("when the single input is a revealed unswept deposit with a trusted vault but non-equal to the vault passed via function parameter", async () => {
678
+ let vault;
679
+ let tx;
680
+ const data = deposit_sweep_1.SingleP2WSHDeposit;
681
+ // Take wallet public key hash from first deposit. All
682
+ // deposits in same sweep batch should have the same value
683
+ // of that field.
684
+ const { walletPubKeyHash } = data.deposits[0].reveal;
685
+ before(async () => {
686
+ await createSnapshot();
687
+ // Simulate the wallet is a Live one and is known in
688
+ // the system.
689
+ await bridge.setWallet(walletPubKeyHash, {
690
+ ...walletDraft,
691
+ state: fixtures_1.walletState.Live,
692
+ });
693
+ // Deploy a fake vault and mark it as trusted.
694
+ vault = await smock_1.smock.fake("IVault");
695
+ await bridge
696
+ .connect(governance)
697
+ .setVaultStatus(vault.address, true);
698
+ // Enrich the test data with the vault parameter.
699
+ // However, deliberately set the `vault` parameter
700
+ // passed to `submitDepositSweepProof` to another
701
+ // value than in the deposit.
702
+ const dataWithVault = JSON.parse(JSON.stringify(data));
703
+ dataWithVault.vault = hardhat_1.ethers.constants.AddressZero;
704
+ dataWithVault.deposits[0].reveal.vault =
705
+ vault.address;
706
+ tx = runDepositSweepScenario(dataWithVault);
707
+ });
708
+ after(async () => {
709
+ await restoreSnapshot();
710
+ });
711
+ it("should revert", async () => {
712
+ await (0, chai_1.expect)(tx).to.be.revertedWith("Deposit should be routed to another vault");
713
+ });
714
+ });
715
+ context("when the single input is the expected main UTXO", () => {
716
+ const previousData = deposit_sweep_1.SingleP2SHDeposit;
717
+ const data = deposit_sweep_1.SingleMainUtxo;
718
+ // Take wallet public key hash from first deposit. All
719
+ // deposits in same sweep batch should have the same value
720
+ // of that field.
721
+ const { walletPubKeyHash } = previousData.deposits[0].reveal;
722
+ before(async () => {
723
+ await createSnapshot();
724
+ // Simulate the wallet is a Live one and is known in
725
+ // the system.
726
+ await bridge.setWallet(walletPubKeyHash, {
727
+ ...walletDraft,
728
+ state: fixtures_1.walletState.Live,
729
+ });
730
+ // Make the first sweep which is actually the predecessor
731
+ // of the sweep tested within this scenario.
732
+ await runDepositSweepScenario(previousData);
733
+ });
734
+ after(async () => {
735
+ await restoreSnapshot();
736
+ });
737
+ it("should revert", async () => {
738
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Sweep transaction must process at least one deposit");
739
+ });
740
+ });
741
+ context("when the single input is a revealed but already swept deposit", () => {
742
+ const data = deposit_sweep_1.SingleP2SHDeposit;
743
+ // Take wallet public key hash from first deposit. All
744
+ // deposits in same sweep batch should have the same value
745
+ // of that field.
746
+ const { walletPubKeyHash } = data.deposits[0].reveal;
747
+ before(async () => {
748
+ await createSnapshot();
749
+ // Simulate the wallet is a Live one and is known in
750
+ // the system.
751
+ await bridge.setWallet(walletPubKeyHash, {
752
+ ...walletDraft,
753
+ state: fixtures_1.walletState.Live,
754
+ });
755
+ // Make a proper sweep to turn the tested deposit into
756
+ // the swept state.
757
+ await runDepositSweepScenario(data);
758
+ });
759
+ after(async () => {
760
+ await restoreSnapshot();
761
+ });
762
+ it("should revert", async () => {
763
+ // Main UTXO parameter must point to the properly
764
+ // made sweep to avoid revert at validation stage.
765
+ const mainUtxo = {
766
+ txHash: data.sweepTx.hash,
767
+ txOutputIndex: 0,
768
+ txOutputValue: 18500,
769
+ };
770
+ // Try replaying the already done sweep.
771
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, mainUtxo, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Deposit already swept");
772
+ });
773
+ });
774
+ context("when the single input is an unknown", () => {
775
+ const data = deposit_sweep_1.SingleP2SHDeposit;
776
+ // Take wallet public key hash from first deposit. All
777
+ // deposits in same sweep batch should have the same value
778
+ // of that field.
779
+ const { walletPubKeyHash } = data.deposits[0].reveal;
780
+ before(async () => {
781
+ await createSnapshot();
782
+ // Simulate the wallet is a Live one and is known in the system.
783
+ await bridge.setWallet(walletPubKeyHash, {
784
+ ...walletDraft,
785
+ state: fixtures_1.walletState.Live,
786
+ });
787
+ // Necessary to pass the proof validation.
788
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
789
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
790
+ });
791
+ after(async () => {
792
+ await restoreSnapshot();
793
+ });
794
+ it("should revert", async () => {
795
+ // Try to sweep a deposit which was not revealed before and
796
+ // is unknown from system's point of view.
797
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, deposit_sweep_1.NO_MAIN_UTXO, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Unknown input type");
798
+ });
799
+ });
800
+ });
801
+ // Since P2SH vs P2WSH path has been already checked in the scenario
802
+ // "when there is only one input", we no longer differentiate deposits
803
+ // using that criterion during "when there are multiple inputs" scenario.
804
+ context("when there are multiple inputs", () => {
805
+ context("when input vector consists only of revealed unswept deposits and the expected main UTXO", () => {
806
+ let tx;
807
+ const previousData = deposit_sweep_1.MultipleDepositsNoMainUtxo;
808
+ const data = deposit_sweep_1.MultipleDepositsWithMainUtxo;
809
+ // Take wallet public key hash from first deposit. All
810
+ // deposits in same sweep batch should have the same value
811
+ // of that field.
812
+ const { walletPubKeyHash } = data.deposits[0].reveal;
813
+ before(async () => {
814
+ await createSnapshot();
815
+ // Simulate the wallet is a Live one and is known in
816
+ // the system.
817
+ await bridge.setWallet(walletPubKeyHash, {
818
+ ...walletDraft,
819
+ state: fixtures_1.walletState.Live,
820
+ });
821
+ // Make the first sweep which is actually the predecessor
822
+ // of the sweep tested within this scenario.
823
+ await runDepositSweepScenario(previousData);
824
+ tx = await runDepositSweepScenario(data);
825
+ });
826
+ after(async () => {
827
+ await restoreSnapshot();
828
+ });
829
+ it("should mark deposits as swept", async () => {
830
+ for (let i = 0; i < data.deposits.length; i++) {
831
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
832
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
833
+ data.deposits[i].fundingTx.hash,
834
+ data.deposits[i].reveal.fundingOutputIndex,
835
+ ]);
836
+ // eslint-disable-next-line no-await-in-loop
837
+ const deposit = await bridge.deposits(depositKey);
838
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(
839
+ // eslint-disable-next-line no-await-in-loop
840
+ await lastBlockTime(), `Deposit with index ${i} has an unexpected swept time`);
841
+ }
842
+ });
843
+ it("should update main UTXO for the given wallet", async () => {
844
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
845
+ // Amount can be checked by opening the sweep tx in a Bitcoin
846
+ // testnet explorer. In this case, the sum of inputs is
847
+ // 4148000 satoshi and there is a fee of 2999 so the output
848
+ // value is 4145001.
849
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 4145001]);
850
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
851
+ });
852
+ it("should update the depositors balances", async () => {
853
+ // The sum of sweep tx inputs is 4148000 satoshi. The output
854
+ // value is 4145001 so the sweep transaction fee is 2999.
855
+ // There are 5 deposits so the fee per deposit is 599
856
+ // and the indivisible remainder is 4 which means the
857
+ // last deposit should incur 603 satoshi. Worth noting
858
+ // the order of deposits used by this test scenario
859
+ // data does not correspond to the order of sweep
860
+ // transaction inputs. Each deposit should also incur
861
+ // the treasury fee whose initial value is 0.05% of the
862
+ // deposited amount.
863
+ // Deposit with index 0 used as input with index 5
864
+ // in the sweep transaction. This is the last deposit
865
+ // (according to inputs order) and it should incur the
866
+ // remainder of the transaction fee.
867
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(219287);
868
+ // Deposit with index 1 used as input with index 3
869
+ // in the sweep transaction.
870
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[1].reveal.depositor)).to.be.equal(759021);
871
+ // Deposit with index 2 used as input with index 1
872
+ // in the sweep transaction.
873
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[2].reveal.depositor)).to.be.equal(938931);
874
+ // Deposit with index 3 used as input with index 2
875
+ // in the sweep transaction.
876
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[3].reveal.depositor)).to.be.equal(878961);
877
+ // Deposit with index 4 used as input with index 4
878
+ // in the sweep transaction.
879
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[4].reveal.depositor)).to.be.equal(289256);
880
+ });
881
+ it("should transfer collected treasury fee", async () => {
882
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(2075);
883
+ });
884
+ it("should mark the previous main UTXO as spent", async () => {
885
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
886
+ data.mainUtxo.txHash,
887
+ data.mainUtxo.txOutputIndex,
888
+ ]);
889
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to
890
+ .be.true;
891
+ });
892
+ it("should emit DepositsSwept event", async () => {
893
+ await (0, chai_1.expect)(tx)
894
+ .to.emit(bridge, "DepositsSwept")
895
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
896
+ });
897
+ });
898
+ context("when input vector consists only of revealed unswept deposits with a trusted vault and the expected main UTXO", () => {
899
+ let vault;
900
+ let tx;
901
+ const previousData = deposit_sweep_1.MultipleDepositsNoMainUtxo;
902
+ const data = deposit_sweep_1.MultipleDepositsWithMainUtxo;
903
+ // Take wallet public key hash from first deposit. All
904
+ // deposits in same sweep batch should have the same value
905
+ // of that field.
906
+ const { walletPubKeyHash } = data.deposits[0].reveal;
907
+ before(async () => {
908
+ await createSnapshot();
909
+ // Simulate the wallet is a Live one and is known in
910
+ // the system.
911
+ await bridge.setWallet(walletPubKeyHash, {
912
+ ...walletDraft,
913
+ state: fixtures_1.walletState.Live,
914
+ });
915
+ // Make the first sweep which is actually the predecessor
916
+ // of the sweep tested within this scenario.
917
+ await runDepositSweepScenario(previousData);
918
+ // Deploy a fake vault and mark it as trusted.
919
+ vault = await smock_1.smock.fake("IVault");
920
+ await bridge
921
+ .connect(governance)
922
+ .setVaultStatus(vault.address, true);
923
+ // Enrich the test data with the vault parameter.
924
+ const dataWithVault = JSON.parse(JSON.stringify(data));
925
+ dataWithVault.vault = vault.address;
926
+ dataWithVault.deposits[0].reveal.vault =
927
+ vault.address;
928
+ dataWithVault.deposits[1].reveal.vault =
929
+ vault.address;
930
+ dataWithVault.deposits[2].reveal.vault =
931
+ vault.address;
932
+ dataWithVault.deposits[3].reveal.vault =
933
+ vault.address;
934
+ dataWithVault.deposits[4].reveal.vault =
935
+ vault.address;
936
+ tx = await runDepositSweepScenario(dataWithVault);
937
+ });
938
+ after(async () => {
939
+ vault.receiveBalanceIncrease.reset();
940
+ await restoreSnapshot();
941
+ });
942
+ it("should mark deposits as swept", async () => {
943
+ for (let i = 0; i < data.deposits.length; i++) {
944
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
945
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
946
+ data.deposits[i].fundingTx.hash,
947
+ data.deposits[i].reveal.fundingOutputIndex,
948
+ ]);
949
+ // eslint-disable-next-line no-await-in-loop
950
+ const deposit = await bridge.deposits(depositKey);
951
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(
952
+ // eslint-disable-next-line no-await-in-loop
953
+ await lastBlockTime(), `Deposit with index ${i} has an unexpected swept time`);
954
+ }
955
+ });
956
+ it("should update main UTXO for the given wallet", async () => {
957
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
958
+ // Amount can be checked by opening the sweep tx in a Bitcoin
959
+ // testnet explorer. In this case, the sum of inputs is
960
+ // 4148000 satoshi and there is a fee of 2999 so the output
961
+ // value is 4145001.
962
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 4145001]);
963
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
964
+ });
965
+ it("should not update the depositors balances", async () => {
966
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(0);
967
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[1].reveal.depositor)).to.be.equal(0);
968
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[2].reveal.depositor)).to.be.equal(0);
969
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[3].reveal.depositor)).to.be.equal(0);
970
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[4].reveal.depositor)).to.be.equal(0);
971
+ });
972
+ it("should update the vault's balance", async () => {
973
+ // The vault's balance should be increased by the
974
+ // sum of all swept deposits counted as follows:
975
+ //
976
+ // The sum of sweep tx inputs is 4148000 satoshi
977
+ // (including the main UTXO value). The output value
978
+ // is 4145001 so the sweep transaction fee is 2999.
979
+ // There are 5 deposits so the fee per deposit is 599
980
+ // and the indivisible remainder is 4 which means the
981
+ // last deposit should incur 603 satoshi. Each deposit
982
+ // should also incur the treasury fee whose initial
983
+ // value is 0.05% of the deposited amount. The
984
+ // final sum of all deposits is 3085456.
985
+ (0, chai_1.expect)(await bank.balanceOf(vault.address)).to.be.equal(3085456);
986
+ });
987
+ it("should call the vault's receiveBalanceIncrease function", async () => {
988
+ // The order of deposits is different that
989
+ // the order of inputs that refers them in
990
+ // the transaction.
991
+ (0, chai_1.expect)(vault.receiveBalanceIncrease).to.have.been.calledOnceWith([
992
+ data.deposits[2].reveal.depositor,
993
+ data.deposits[3].reveal.depositor,
994
+ data.deposits[1].reveal.depositor,
995
+ data.deposits[4].reveal.depositor,
996
+ data.deposits[0].reveal.depositor,
997
+ ], [938931, 878961, 759021, 289256, 219287]);
998
+ });
999
+ it("should transfer collected treasury fee", async () => {
1000
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(2075);
1001
+ });
1002
+ it("should mark the previous main UTXO as spent", async () => {
1003
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
1004
+ data.mainUtxo.txHash,
1005
+ data.mainUtxo.txOutputIndex,
1006
+ ]);
1007
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to
1008
+ .be.true;
1009
+ });
1010
+ it("should emit DepositsSwept event", async () => {
1011
+ await (0, chai_1.expect)(tx)
1012
+ .to.emit(bridge, "DepositsSwept")
1013
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
1014
+ });
1015
+ });
1016
+ context("when input vector consists only of revealed unswept deposits with a non-trusted vault and the expected main UTXO", () => {
1017
+ let vault;
1018
+ let tx;
1019
+ const previousData = deposit_sweep_1.MultipleDepositsNoMainUtxo;
1020
+ const data = deposit_sweep_1.MultipleDepositsWithMainUtxo;
1021
+ // Take wallet public key hash from first deposit. All
1022
+ // deposits in same sweep batch should have the same value
1023
+ // of that field.
1024
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1025
+ before(async () => {
1026
+ await createSnapshot();
1027
+ // Simulate the wallet is a Live one and is known in
1028
+ // the system.
1029
+ await bridge.setWallet(walletPubKeyHash, {
1030
+ ...walletDraft,
1031
+ state: fixtures_1.walletState.Live,
1032
+ });
1033
+ // Make the first sweep which is actually the predecessor
1034
+ // of the sweep tested within this scenario.
1035
+ await runDepositSweepScenario(previousData);
1036
+ // Deploy a fake vault and mark it as trusted.
1037
+ vault = await smock_1.smock.fake("IVault");
1038
+ await bridge
1039
+ .connect(governance)
1040
+ .setVaultStatus(vault.address, true);
1041
+ // Enrich the test data with the vault parameter.
1042
+ const dataWithVault = JSON.parse(JSON.stringify(data));
1043
+ dataWithVault.vault = vault.address;
1044
+ dataWithVault.deposits[0].reveal.vault =
1045
+ vault.address;
1046
+ dataWithVault.deposits[1].reveal.vault =
1047
+ vault.address;
1048
+ dataWithVault.deposits[2].reveal.vault =
1049
+ vault.address;
1050
+ dataWithVault.deposits[3].reveal.vault =
1051
+ vault.address;
1052
+ dataWithVault.deposits[4].reveal.vault =
1053
+ vault.address;
1054
+ // Mark the vault as non-trusted just before
1055
+ // proof submission.
1056
+ const beforeProofActions = async () => {
1057
+ await bridge
1058
+ .connect(governance)
1059
+ .setVaultStatus(vault.address, false);
1060
+ };
1061
+ tx = await runDepositSweepScenario(dataWithVault, beforeProofActions);
1062
+ });
1063
+ after(async () => {
1064
+ await restoreSnapshot();
1065
+ });
1066
+ it("should mark deposits as swept", async () => {
1067
+ for (let i = 0; i < data.deposits.length; i++) {
1068
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
1069
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
1070
+ data.deposits[i].fundingTx.hash,
1071
+ data.deposits[i].reveal.fundingOutputIndex,
1072
+ ]);
1073
+ // eslint-disable-next-line no-await-in-loop
1074
+ const deposit = await bridge.deposits(depositKey);
1075
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(
1076
+ // eslint-disable-next-line no-await-in-loop
1077
+ await lastBlockTime(), `Deposit with index ${i} has an unexpected swept time`);
1078
+ }
1079
+ });
1080
+ it("should update main UTXO for the given wallet", async () => {
1081
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
1082
+ // Amount can be checked by opening the sweep tx in a Bitcoin
1083
+ // testnet explorer. In this case, the sum of inputs is
1084
+ // 4148000 satoshi and there is a fee of 2999 so the output
1085
+ // value is 4145001.
1086
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 4145001]);
1087
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
1088
+ });
1089
+ it("should update the depositors balances", async () => {
1090
+ // The sum of sweep tx inputs is 4148000 satoshi. The output
1091
+ // value is 4145001 so the sweep transaction fee is 2999.
1092
+ // There are 5 deposits so the fee per deposit is 599
1093
+ // and the indivisible remainder is 4 which means the
1094
+ // last deposit should incur 603 satoshi. Worth noting
1095
+ // the order of deposits used by this test scenario
1096
+ // data does not correspond to the order of sweep
1097
+ // transaction inputs. Each deposit should also incur
1098
+ // the treasury fee whose initial value is 0.05% of the
1099
+ // deposited amount.
1100
+ // Deposit with index 0 used as input with index 5
1101
+ // in the sweep transaction. This is the last deposit
1102
+ // (according to inputs order) and it should incur the
1103
+ // remainder of the transaction fee.
1104
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(219287);
1105
+ // Deposit with index 1 used as input with index 3
1106
+ // in the sweep transaction.
1107
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[1].reveal.depositor)).to.be.equal(759021);
1108
+ // Deposit with index 2 used as input with index 1
1109
+ // in the sweep transaction.
1110
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[2].reveal.depositor)).to.be.equal(938931);
1111
+ // Deposit with index 3 used as input with index 2
1112
+ // in the sweep transaction.
1113
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[3].reveal.depositor)).to.be.equal(878961);
1114
+ // Deposit with index 4 used as input with index 4
1115
+ // in the sweep transaction.
1116
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[4].reveal.depositor)).to.be.equal(289256);
1117
+ });
1118
+ it("should transfer collected treasury fee", async () => {
1119
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(2075);
1120
+ });
1121
+ it("should mark the previous main UTXO as spent", async () => {
1122
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
1123
+ data.mainUtxo.txHash,
1124
+ data.mainUtxo.txOutputIndex,
1125
+ ]);
1126
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to
1127
+ .be.true;
1128
+ });
1129
+ it("should emit DepositsSwept event", async () => {
1130
+ await (0, chai_1.expect)(tx)
1131
+ .to.emit(bridge, "DepositsSwept")
1132
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
1133
+ });
1134
+ });
1135
+ context("when input vector consists only of revealed unswept deposits with different trusted vaults and the expected main UTXO", () => {
1136
+ let vaultA;
1137
+ let vaultB;
1138
+ let tx;
1139
+ const previousData = deposit_sweep_1.MultipleDepositsNoMainUtxo;
1140
+ const data = deposit_sweep_1.MultipleDepositsWithMainUtxo;
1141
+ // Take wallet public key hash from first deposit. All
1142
+ // deposits in same sweep batch should have the same value
1143
+ // of that field.
1144
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1145
+ before(async () => {
1146
+ await createSnapshot();
1147
+ // Simulate the wallet is a Live one and is known in
1148
+ // the system.
1149
+ await bridge.setWallet(walletPubKeyHash, {
1150
+ ...walletDraft,
1151
+ state: fixtures_1.walletState.Live,
1152
+ });
1153
+ // Make the first sweep which is actually the predecessor
1154
+ // of the sweep tested within this scenario.
1155
+ await runDepositSweepScenario(previousData);
1156
+ // Deploy two fake vaults and mark them as trusted.
1157
+ vaultA = await smock_1.smock.fake("IVault");
1158
+ vaultB = await smock_1.smock.fake("IVault");
1159
+ await bridge
1160
+ .connect(governance)
1161
+ .setVaultStatus(vaultA.address, true);
1162
+ await bridge
1163
+ .connect(governance)
1164
+ .setVaultStatus(vaultB.address, true);
1165
+ // Enrich the test data with the vault parameter.
1166
+ const dataWithVault = JSON.parse(JSON.stringify(data));
1167
+ dataWithVault.vault = vaultA.address;
1168
+ dataWithVault.deposits[0].reveal.vault =
1169
+ vaultA.address;
1170
+ dataWithVault.deposits[1].reveal.vault =
1171
+ vaultA.address;
1172
+ dataWithVault.deposits[2].reveal.vault =
1173
+ vaultB.address;
1174
+ dataWithVault.deposits[3].reveal.vault =
1175
+ vaultB.address;
1176
+ dataWithVault.deposits[4].reveal.vault =
1177
+ vaultA.address;
1178
+ tx = runDepositSweepScenario(dataWithVault);
1179
+ });
1180
+ after(async () => {
1181
+ await restoreSnapshot();
1182
+ });
1183
+ it("should revert", async () => {
1184
+ await (0, chai_1.expect)(tx).to.be.revertedWith("Deposit should be routed to another vault");
1185
+ });
1186
+ });
1187
+ context("when input vector consists only of revealed unswept deposits but there is no main UTXO since it is not expected", () => {
1188
+ let tx;
1189
+ const data = deposit_sweep_1.MultipleDepositsNoMainUtxo;
1190
+ // Take wallet public key hash from first deposit. All
1191
+ // deposits in same sweep batch should have the same value
1192
+ // of that field.
1193
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1194
+ before(async () => {
1195
+ await createSnapshot();
1196
+ // Simulate the wallet is a Live one and is known in
1197
+ // the system.
1198
+ await bridge.setWallet(walletPubKeyHash, {
1199
+ ...walletDraft,
1200
+ state: fixtures_1.walletState.Live,
1201
+ });
1202
+ tx = await runDepositSweepScenario(data);
1203
+ });
1204
+ after(async () => {
1205
+ await restoreSnapshot();
1206
+ });
1207
+ it("should mark deposits as swept", async () => {
1208
+ for (let i = 0; i < data.deposits.length; i++) {
1209
+ // Deposit key is keccak256(fundingTxHash | fundingOutputIndex).
1210
+ const depositKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
1211
+ data.deposits[i].fundingTx.hash,
1212
+ data.deposits[i].reveal.fundingOutputIndex,
1213
+ ]);
1214
+ // eslint-disable-next-line no-await-in-loop
1215
+ const deposit = await bridge.deposits(depositKey);
1216
+ (0, chai_1.expect)(deposit.sweptAt).to.be.equal(
1217
+ // eslint-disable-next-line no-await-in-loop
1218
+ await lastBlockTime(), `Deposit with index ${i} has an unexpected swept time`);
1219
+ }
1220
+ });
1221
+ it("should update main UTXO for the given wallet", async () => {
1222
+ const { mainUtxoHash } = await bridge.wallets(walletPubKeyHash);
1223
+ // Amount can be checked by opening the sweep tx in a Bitcoin
1224
+ // testnet explorer. In this case, the sum of inputs is
1225
+ // 1060000 satoshi and there is a fee of 2000 so the output
1226
+ // value is 1058000.
1227
+ const expectedMainUtxo = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 1058000]);
1228
+ (0, chai_1.expect)(mainUtxoHash).to.be.equal(expectedMainUtxo);
1229
+ });
1230
+ it("should update the depositors balances", async () => {
1231
+ // The sum of sweep tx inputs is 1060000 satoshi. The output
1232
+ // value is 1058000 so the sweep transaction fee is 2000.
1233
+ // There are 5 deposits so the fee per deposit is 400
1234
+ // and there is no indivisible remainder. Each deposit
1235
+ // should also incur the treasury fee whose initial
1236
+ // value is 0.05% of the deposited amount.
1237
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[0].reveal.depositor)).to.be.equal(29585);
1238
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[1].reveal.depositor)).to.be.equal(9595);
1239
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[2].reveal.depositor)).to.be.equal(209495);
1240
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[3].reveal.depositor)).to.be.equal(369415);
1241
+ (0, chai_1.expect)(await bank.balanceOf(data.deposits[4].reveal.depositor)).to.be.equal(439380);
1242
+ });
1243
+ it("should transfer collected treasury fee", async () => {
1244
+ (0, chai_1.expect)(await bank.balanceOf(treasury.address)).to.be.equal(530);
1245
+ });
1246
+ it("should emit DepositsSwept event", async () => {
1247
+ await (0, chai_1.expect)(tx)
1248
+ .to.emit(bridge, "DepositsSwept")
1249
+ .withArgs(walletPubKeyHash, data.sweepTx.hash);
1250
+ });
1251
+ });
1252
+ context("when input vector consists only of revealed unswept deposits but there is no main UTXO despite it is expected", () => {
1253
+ const previousData = deposit_sweep_1.SingleP2WSHDeposit;
1254
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.MultipleDepositsNoMainUtxo));
1255
+ // Take wallet public key hash from first deposit. All
1256
+ // deposits in same sweep batch should have the same value
1257
+ // of that field.
1258
+ const { walletPubKeyHash } = previousData.deposits[0].reveal;
1259
+ before(async () => {
1260
+ await createSnapshot();
1261
+ // Simulate the wallet is a Live one and is known in
1262
+ // the system.
1263
+ await bridge.setWallet(walletPubKeyHash, {
1264
+ ...walletDraft,
1265
+ state: fixtures_1.walletState.Live,
1266
+ });
1267
+ // Make the first sweep to create an on-chain expectation
1268
+ // that the tested sweep will contain the main UTXO
1269
+ // input.
1270
+ await runDepositSweepScenario(previousData);
1271
+ });
1272
+ after(async () => {
1273
+ await restoreSnapshot();
1274
+ });
1275
+ it("should revert", async () => {
1276
+ // Use sweep data which doesn't reference the main UTXO.
1277
+ // However, pass a correct main UTXO parameter in order
1278
+ // to pass main UTXO validation in the contract.
1279
+ data.mainUtxo = {
1280
+ txHash: previousData.sweepTx.hash,
1281
+ txOutputIndex: 0,
1282
+ txOutputValue: 78000,
1283
+ };
1284
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Expected main UTXO not present in sweep transaction inputs");
1285
+ });
1286
+ });
1287
+ context("when input vector contains a revealed but already swept deposit", () => {
1288
+ const data = deposit_sweep_1.MultipleDepositsNoMainUtxo;
1289
+ // Take wallet public key hash from first deposit. All
1290
+ // deposits in same sweep batch should have the same value
1291
+ // of that field.
1292
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1293
+ before(async () => {
1294
+ await createSnapshot();
1295
+ // Simulate the wallet is a Live one and is known in
1296
+ // the system.
1297
+ await bridge.setWallet(walletPubKeyHash, {
1298
+ ...walletDraft,
1299
+ state: fixtures_1.walletState.Live,
1300
+ });
1301
+ // Make a proper sweep to turn the tested deposits into
1302
+ // the swept state.
1303
+ await runDepositSweepScenario(data);
1304
+ });
1305
+ after(async () => {
1306
+ await restoreSnapshot();
1307
+ });
1308
+ it("should revert", async () => {
1309
+ // Main UTXO parameter must point to the properly
1310
+ // made sweep to avoid revert at validation stage.
1311
+ const mainUtxo = {
1312
+ txHash: data.sweepTx.hash,
1313
+ txOutputIndex: 0,
1314
+ txOutputValue: 1058000,
1315
+ };
1316
+ // Try replaying the already done sweep.
1317
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, mainUtxo, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Deposit already swept");
1318
+ });
1319
+ });
1320
+ context("when input vector contains an unknown input", () => {
1321
+ const data = deposit_sweep_1.MultipleDepositsWithMainUtxo;
1322
+ // Take wallet public key hash from first deposit. All
1323
+ // deposits in same sweep batch should have the same value
1324
+ // of that field.
1325
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1326
+ before(async () => {
1327
+ await createSnapshot();
1328
+ // Simulate the wallet is a Live one and is known in
1329
+ // the system.
1330
+ await bridge.setWallet(walletPubKeyHash, {
1331
+ ...walletDraft,
1332
+ state: fixtures_1.walletState.Live,
1333
+ });
1334
+ });
1335
+ after(async () => {
1336
+ await restoreSnapshot();
1337
+ });
1338
+ it("should revert", async () => {
1339
+ // Used test data contains an actual main UTXO input
1340
+ // but the previous action proof was not submitted on-chain
1341
+ // so input is unknown from contract's perspective.
1342
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Unknown input type");
1343
+ });
1344
+ });
1345
+ });
1346
+ });
1347
+ context("when transaction fee exceeds the deposit transaction maximum fee", () => {
1348
+ const data = deposit_sweep_1.SingleP2SHDeposit;
1349
+ // Take wallet public key hash from first deposit. All
1350
+ // deposits in same sweep batch should have the same value
1351
+ // of that field.
1352
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1353
+ before(async () => {
1354
+ await createSnapshot();
1355
+ // Simulate the wallet is a Live one and is known in
1356
+ // the system.
1357
+ await bridge.setWallet(walletPubKeyHash, {
1358
+ ...walletDraft,
1359
+ state: fixtures_1.walletState.Live,
1360
+ });
1361
+ // Set the deposit transaction maximum fee to a value much
1362
+ // lower than the fee used by the test data transaction.
1363
+ await bridge.setDepositTxMaxFee(100);
1364
+ });
1365
+ after(async () => {
1366
+ await restoreSnapshot();
1367
+ });
1368
+ it("should revert", async () => {
1369
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("'Transaction fee is too high");
1370
+ });
1371
+ });
1372
+ });
1373
+ context("when main UTXO data are invalid", () => {
1374
+ const previousData = deposit_sweep_1.MultipleDepositsNoMainUtxo;
1375
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.MultipleDepositsWithMainUtxo));
1376
+ // Take wallet public key hash from first deposit. All
1377
+ // deposits in same sweep batch should have the same value
1378
+ // of that field.
1379
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1380
+ before(async () => {
1381
+ await createSnapshot();
1382
+ // Simulate the wallet is a Live one and is known in
1383
+ // the system.
1384
+ await bridge.setWallet(walletPubKeyHash, {
1385
+ ...walletDraft,
1386
+ state: fixtures_1.walletState.Live,
1387
+ });
1388
+ // Make the first sweep which is actually the predecessor
1389
+ // of the sweep tested within this scenario.
1390
+ await runDepositSweepScenario(previousData);
1391
+ });
1392
+ after(async () => {
1393
+ await restoreSnapshot();
1394
+ });
1395
+ it("should revert", async () => {
1396
+ // Forge the main UTXO parameter to force validation crash.
1397
+ data.mainUtxo = deposit_sweep_1.NO_MAIN_UTXO;
1398
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Invalid main UTXO data");
1399
+ });
1400
+ });
1401
+ });
1402
+ context("when single output is neither P2PKH nor P2WPKH", () => {
1403
+ const data = deposit_sweep_1.SingleMainUtxoP2SHOutput;
1404
+ before(async () => {
1405
+ await createSnapshot();
1406
+ });
1407
+ after(async () => {
1408
+ await restoreSnapshot();
1409
+ });
1410
+ it("should revert", async () => {
1411
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Output must be P2PKH or P2WPKH");
1412
+ });
1413
+ });
1414
+ });
1415
+ context("when the single output is not 20-byte", () => {
1416
+ before(async () => {
1417
+ await createSnapshot();
1418
+ // Necessary to pass the proof validation.
1419
+ relay.getPrevEpochDifficulty.returns(20870012);
1420
+ relay.getCurrentEpochDifficulty.returns(20870012);
1421
+ });
1422
+ after(async () => {
1423
+ await restoreSnapshot();
1424
+ });
1425
+ it("should revert", async () => {
1426
+ // To test this case, an arbitrary transaction with single
1427
+ // P2WSH output is used. In that case, the wallet public key
1428
+ // hash will have a wrong length of 32 bytes. Used transaction:
1429
+ // https://live.blockcypher.com/btc-testnet/tx/af56cae479215c5e44a6a4db0eeb10a1abdd98020a6c01b9c26ea7b829aa2809
1430
+ const sweepTx = {
1431
+ version: "0x01000000",
1432
+ inputVector: "0x01d32586237f6a832c3aa324bb83151e43e6cca2e4312d676f14" +
1433
+ "dbbd6b1f04f4680100000000ffffffff",
1434
+ outputVector: "0x012ea3090000000000220020af802a76c10b6a646fff8d358241" +
1435
+ "c121c9be1c53628adb26bd6554631bfc7d8b",
1436
+ locktime: "0x00000000",
1437
+ };
1438
+ const sweepProof = {
1439
+ merkleProof: "0xf09955dcfb05b1c369eb9f58b6e583e49f47b9b8d6e63537dcac" +
1440
+ "10bf0cc5407d06e76ee2d75b5be5ec365a4c1272067b786d79a64d" +
1441
+ "c015eb40dedd3c813f4dee40c149ee21036bba713d14b3c22454ef" +
1442
+ "44c958293a015e9e186983f20c46d74a29ca5f705913e210229078" +
1443
+ "af993e89d90bb731dab3c8cf8907d683ab60faca1866036118737e" +
1444
+ "07aaa74d489e80f773b4d9ff2887a4855b805aaf1b5a7a1b0bf382" +
1445
+ "be8dab2401ec758a705b648724f93d14c3b72ce4fb3cd7d414e8a1" +
1446
+ "75ef173e",
1447
+ txIndexInBlock: 20,
1448
+ bitcoinHeaders: "0x0000e020fbeb3a876746438f1fd793add061b0b7af2f88a387ee" +
1449
+ "f5b38600000000000000933a0cec98a028727df04dafbbe691c8ad" +
1450
+ "442351db7321c9f7cc169aa9f64a9a7af6f361cbcd001a65073028",
1451
+ };
1452
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(sweepTx, sweepProof, deposit_sweep_1.NO_MAIN_UTXO, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Output's public key hash must have 20 bytes");
1453
+ });
1454
+ });
1455
+ });
1456
+ context("when output count is other than one", () => {
1457
+ before(async () => {
1458
+ await createSnapshot();
1459
+ // Necessary to pass the proof validation.
1460
+ relay.getCurrentEpochDifficulty.returns(1);
1461
+ relay.getPrevEpochDifficulty.returns(1);
1462
+ });
1463
+ after(async () => {
1464
+ await restoreSnapshot();
1465
+ });
1466
+ it("should revert", async () => {
1467
+ // To test this case, an arbitrary transaction with two
1468
+ // outputs is used. Used transaction:
1469
+ // https://live.blockcypher.com/btc-testnet/tx/af56cae479215c5e44a6a4db0eeb10a1abdd98020a6c01b9c26ea7b829aa2809
1470
+ const sweepTx = {
1471
+ version: "0x01000000",
1472
+ inputVector: "0x011d9b71144a3ddbb56dd099ee94e6dd8646d7d1eb37fe1195367e6f" +
1473
+ "a844a388e7010000006a47304402206f8553c07bcdc0c3b90631188810" +
1474
+ "3d623ca9096ca0b28b7d04650a029a01fcf9022064cda02e39e65ace71" +
1475
+ "2029845cfcf58d1b59617d753c3fd3556f3551b609bbb00121039d61d6" +
1476
+ "2dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa" +
1477
+ "ffffffff",
1478
+ outputVector: "0x02204e00000000000017a9143ec459d0f3c29286ae5df5fcc421e278" +
1479
+ "6024277e87a6c2140000000000160014e257eccafbc07c381642ce6e7e" +
1480
+ "55120fb077fbed",
1481
+ locktime: "0x00000000",
1482
+ };
1483
+ const sweepProof = {
1484
+ merkleProof: "0x161d24e53fc61db783f0271d45ef43b76e69fc975cf38decbba654ae" +
1485
+ "3d09f5d1a060c3448c0c06ededa9749e559ffa65e2d5f3abac749b278e" +
1486
+ "1189aa5b49a499b032963ea3fad337c4a9c8df4e748865503b5aea083f" +
1487
+ "b32efe4dca057a741a020790cde5b50acc2cdbd231e43594036388f1e5" +
1488
+ "d20ebba319465c56e85bf4e4b4f8b7276402b6c114000c59149494f852" +
1489
+ "84507c253bbc505fec7ea50f370aa150",
1490
+ txIndexInBlock: 8,
1491
+ bitcoinHeaders: "0x00000020fbee5222c9fc99c8071cee3fed39b4c0d39f41075469ce9f" +
1492
+ "52000000000000003fd9c72d0611b373ce2b1996e0ebb8bc36dc12d431" +
1493
+ "cae5b9371f343111f3d7519015da61ffff001dbddfb528000040208a9f" +
1494
+ "e49585b4cd8a94daeeb926c6f1e96151c74ae1ae0b18c6a6d564000000" +
1495
+ "0065c05d9ea40cace1b6b0ad0b8a9a18646096b54484fbdd96b1596560" +
1496
+ "f6999194a815da612ac0001a2e4c6405",
1497
+ };
1498
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(sweepTx, sweepProof, deposit_sweep_1.NO_MAIN_UTXO, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Sweep transaction must have a single output");
1499
+ });
1500
+ });
1501
+ });
1502
+ context("when transaction proof is not valid", () => {
1503
+ context("when input vector is not valid", () => {
1504
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1505
+ // Take wallet public key hash from first deposit. All
1506
+ // deposits in same sweep batch should have the same value
1507
+ // of that field.
1508
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1509
+ before(async () => {
1510
+ await createSnapshot();
1511
+ // Simulate the wallet is a Live one and is known in
1512
+ // the system.
1513
+ await bridge.setWallet(walletPubKeyHash, {
1514
+ ...walletDraft,
1515
+ state: fixtures_1.walletState.Live,
1516
+ });
1517
+ });
1518
+ after(async () => {
1519
+ await restoreSnapshot();
1520
+ });
1521
+ it("should revert", async () => {
1522
+ // Corrupt the input vector by setting a compactSize uint claiming
1523
+ // there is no inputs at all.
1524
+ data.sweepTx.inputVector =
1525
+ "0x0079544f374199c68869ce7df906eeb0ee5c0506a512d903e3900d5752" +
1526
+ "e3e080c500000000c847304402205eff3ae003a5903eb33f32737e3442b6" +
1527
+ "516685a1addb19339c2d02d400cf67ce0220707435fc2a0577373c63c99d" +
1528
+ "242c30bea5959ec180169978d43ece50618fe0ff012103989d253b17a6a0" +
1529
+ "f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d94c5c14934b" +
1530
+ "98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d000395237576" +
1531
+ "a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776a914e2" +
1532
+ "57eccafbc07c381642ce6e7e55120fb077fbed8804e0250162b175ac68ff" +
1533
+ "ffffff";
1534
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Invalid input vector provided");
1535
+ });
1536
+ });
1537
+ context("when output vector is not valid", () => {
1538
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1539
+ // Take wallet public key hash from first deposit. All
1540
+ // deposits in same sweep batch should have the same value
1541
+ // of that field.
1542
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1543
+ before(async () => {
1544
+ await createSnapshot();
1545
+ // Simulate the wallet is a Live one and is known in
1546
+ // the system.
1547
+ await bridge.setWallet(walletPubKeyHash, {
1548
+ ...walletDraft,
1549
+ state: fixtures_1.walletState.Live,
1550
+ });
1551
+ });
1552
+ after(async () => {
1553
+ await restoreSnapshot();
1554
+ });
1555
+ it("should revert", async () => {
1556
+ // Corrupt the output vector by setting a compactSize uint claiming
1557
+ // there is no outputs at all.
1558
+ data.sweepTx.outputVector =
1559
+ "0x0044480000000000001600148db50eb52063ea9d98b3eac91489a90f73" +
1560
+ "8986f6";
1561
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Invalid output vector provided");
1562
+ });
1563
+ });
1564
+ context("when merkle proof is not valid", () => {
1565
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1566
+ // Take wallet public key hash from first deposit. All
1567
+ // deposits in same sweep batch should have the same value
1568
+ // of that field.
1569
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1570
+ before(async () => {
1571
+ await createSnapshot();
1572
+ // Simulate the wallet is a Live one and is known in
1573
+ // the system.
1574
+ await bridge.setWallet(walletPubKeyHash, {
1575
+ ...walletDraft,
1576
+ state: fixtures_1.walletState.Live,
1577
+ });
1578
+ });
1579
+ after(async () => {
1580
+ await restoreSnapshot();
1581
+ });
1582
+ it("should revert", async () => {
1583
+ // Corrupt the merkle proof by changing tx index in block to an
1584
+ // invalid one. The proper one is 36 so any other will do the trick.
1585
+ data.sweepProof.txIndexInBlock = 30;
1586
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Tx merkle proof is not valid for provided header and tx hash");
1587
+ });
1588
+ });
1589
+ context("when proof difficulty is not current nor previous", () => {
1590
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1591
+ // Take wallet public key hash from first deposit. All
1592
+ // deposits in same sweep batch should have the same value
1593
+ // of that field.
1594
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1595
+ before(async () => {
1596
+ await createSnapshot();
1597
+ // Simulate the wallet is a Live one and is known in
1598
+ // the system.
1599
+ await bridge.setWallet(walletPubKeyHash, {
1600
+ ...walletDraft,
1601
+ state: fixtures_1.walletState.Live,
1602
+ });
1603
+ });
1604
+ after(async () => {
1605
+ await restoreSnapshot();
1606
+ });
1607
+ it("should revert", async () => {
1608
+ // To pass the proof validation, the difficulty returned by the relay
1609
+ // must be 22350181 for test data used in this scenario. Setting
1610
+ // a different value will cause difficulty comparison failure.
1611
+ data.chainDifficulty = 1;
1612
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Not at current or previous difficulty");
1613
+ });
1614
+ });
1615
+ context("when headers chain length is not valid", () => {
1616
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1617
+ // Take wallet public key hash from first deposit. All
1618
+ // deposits in same sweep batch should have the same value
1619
+ // of that field.
1620
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1621
+ before(async () => {
1622
+ await createSnapshot();
1623
+ // Simulate the wallet is a Live one and is known in
1624
+ // the system.
1625
+ await bridge.setWallet(walletPubKeyHash, {
1626
+ ...walletDraft,
1627
+ state: fixtures_1.walletState.Live,
1628
+ });
1629
+ });
1630
+ after(async () => {
1631
+ await restoreSnapshot();
1632
+ });
1633
+ it("should revert", async () => {
1634
+ // Corrupt the bitcoin headers length in the sweep proof. The proper
1635
+ // value is length divisible by 80 so any length violating this
1636
+ // rule will cause failure. In this case, we just remove the last
1637
+ // byte from proper headers chain.
1638
+ const properHeaders = data.sweepProof.bitcoinHeaders.toString();
1639
+ data.sweepProof.bitcoinHeaders = properHeaders.substring(0, properHeaders.length - 2);
1640
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Invalid length of the headers chain");
1641
+ });
1642
+ });
1643
+ context("when headers chain is not valid", () => {
1644
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1645
+ // Take wallet public key hash from first deposit. All
1646
+ // deposits in same sweep batch should have the same value
1647
+ // of that field.
1648
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1649
+ before(async () => {
1650
+ await createSnapshot();
1651
+ // Simulate the wallet is a Live one and is known in
1652
+ // the system.
1653
+ await bridge.setWallet(walletPubKeyHash, {
1654
+ ...walletDraft,
1655
+ state: fixtures_1.walletState.Live,
1656
+ });
1657
+ });
1658
+ after(async () => {
1659
+ await restoreSnapshot();
1660
+ });
1661
+ it("should revert", async () => {
1662
+ // Bitcoin headers must form a chain to pass the proof validation.
1663
+ // That means the `previous block hash` encoded in the given block
1664
+ // header must match the actual previous header's hash. To test
1665
+ // that scenario, we corrupt the `previous block hash` of the
1666
+ // second header. Each header is 80 bytes length. First 4 bytes
1667
+ // of each header is `version` and 32 subsequent bytes is
1668
+ // `previous block hash`. Changing byte 85 of the whole chain will
1669
+ // do the work.
1670
+ const properHeaders = data.sweepProof.bitcoinHeaders.toString();
1671
+ data.sweepProof.bitcoinHeaders = `${properHeaders.substring(0, 170)}ff${properHeaders.substring(172)}`;
1672
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Invalid headers chain");
1673
+ });
1674
+ });
1675
+ context("when the work in the header is insufficient", () => {
1676
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1677
+ // Take wallet public key hash from first deposit. All
1678
+ // deposits in same sweep batch should have the same value
1679
+ // of that field.
1680
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1681
+ before(async () => {
1682
+ await createSnapshot();
1683
+ // Simulate the wallet is a Live one and is known in
1684
+ // the system.
1685
+ await bridge.setWallet(walletPubKeyHash, {
1686
+ ...walletDraft,
1687
+ state: fixtures_1.walletState.Live,
1688
+ });
1689
+ });
1690
+ after(async () => {
1691
+ await restoreSnapshot();
1692
+ });
1693
+ it("should revert", async () => {
1694
+ // Each header encodes a `difficulty target` field in bytes 72-76.
1695
+ // The given header's hash (interpreted as uint) must be bigger than
1696
+ // the `difficulty target`. To test this scenario, we change the
1697
+ // last byte of the last header in such a way their hash becomes
1698
+ // lower than their `difficulty target`.
1699
+ const properHeaders = data.sweepProof.bitcoinHeaders.toString();
1700
+ data.sweepProof.bitcoinHeaders = `${properHeaders.substring(0, properHeaders.length - 2)}ff`;
1701
+ await (0, chai_1.expect)(runDepositSweepScenario(data)).to.be.revertedWith("Insufficient work in a header");
1702
+ });
1703
+ });
1704
+ context("when accumulated difficulty in headers chain is insufficient", () => {
1705
+ let otherBridge;
1706
+ const data = JSON.parse(JSON.stringify(deposit_sweep_1.SingleP2SHDeposit));
1707
+ // Take wallet public key hash from first deposit. All
1708
+ // deposits in same sweep batch should have the same value
1709
+ // of that field.
1710
+ const { walletPubKeyHash } = data.deposits[0].reveal;
1711
+ before(async () => {
1712
+ await createSnapshot();
1713
+ // Simulate the wallet is a Live one and is known in
1714
+ // the system.
1715
+ await bridge.setWallet(walletPubKeyHash, {
1716
+ ...walletDraft,
1717
+ state: fixtures_1.walletState.Live,
1718
+ });
1719
+ // Necessary to pass the first part of proof validation.
1720
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1721
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1722
+ // Deploy another bridge which has higher `txProofDifficultyFactor`
1723
+ // than the original bridge. That means it will need 12 confirmations
1724
+ // to deem transaction proof validity. This scenario uses test
1725
+ // data which has only 6 confirmations. That should force the
1726
+ // failure we expect within this scenario.
1727
+ otherBridge = await BridgeFactory.deploy();
1728
+ await otherBridge.initialize(bank.address, relay.address, treasury.address, hardhat_1.ethers.utils.hexZeroPad("0x01", 20), 12);
1729
+ await otherBridge.deployed();
1730
+ });
1731
+ after(async () => {
1732
+ await restoreSnapshot();
1733
+ });
1734
+ it("should revert", async () => {
1735
+ await (0, chai_1.expect)(otherBridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Insufficient accumulated difficulty in header chain");
1736
+ });
1737
+ });
1738
+ });
1739
+ });
1740
+ context("when the wallet state is MovingFunds", () => {
1741
+ // The execution of `submitDepositSweepProof` is the same for wallets in
1742
+ // `MovingFunds` state as for the ones in `Live` state. Therefore the
1743
+ // testing of `MovingFunds` state is limited to just one simple test case
1744
+ // (sweeping single P2SH deposit).
1745
+ const data = deposit_sweep_1.SingleP2SHDeposit;
1746
+ const { fundingTx, reveal } = data.deposits[0];
1747
+ before(async () => {
1748
+ await createSnapshot();
1749
+ // Initially set the state to Live, so that the deposit can be revealed
1750
+ await bridge.setWallet(reveal.walletPubKeyHash, {
1751
+ ...walletDraft,
1752
+ state: fixtures_1.walletState.Live,
1753
+ });
1754
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1755
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1756
+ await bridge.revealDeposit(fundingTx, reveal);
1757
+ // Simulate the wallet's state has changed to MovingFunds
1758
+ const wallet = await bridge.wallets(reveal.walletPubKeyHash);
1759
+ await bridge.setWallet(reveal.walletPubKeyHash, {
1760
+ ...wallet,
1761
+ state: fixtures_1.walletState.MovingFunds,
1762
+ });
1763
+ });
1764
+ after(async () => {
1765
+ await restoreSnapshot();
1766
+ });
1767
+ it("should succeed", async () => {
1768
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo, hardhat_1.ethers.constants.AddressZero)).not.to.be.reverted;
1769
+ });
1770
+ });
1771
+ context("when the wallet state is neither Live or MovingFunds", () => {
1772
+ const data = deposit_sweep_1.SingleP2SHDeposit;
1773
+ const { fundingTx, reveal } = data.deposits[0];
1774
+ const testData = [
1775
+ {
1776
+ testName: "when wallet state is Unknown",
1777
+ walletState: fixtures_1.walletState.Unknown,
1778
+ },
1779
+ {
1780
+ testName: "when wallet state is Closing",
1781
+ walletState: fixtures_1.walletState.Closing,
1782
+ },
1783
+ {
1784
+ testName: "when wallet state is Closed",
1785
+ walletState: fixtures_1.walletState.Closed,
1786
+ },
1787
+ {
1788
+ testName: "when wallet state is Terminated",
1789
+ walletState: fixtures_1.walletState.Terminated,
1790
+ },
1791
+ ];
1792
+ testData.forEach((test) => {
1793
+ context(test.testName, () => {
1794
+ before(async () => {
1795
+ await createSnapshot();
1796
+ // Initially set the state to Live, so that the deposit can be revealed
1797
+ await bridge.setWallet(reveal.walletPubKeyHash, {
1798
+ ...walletDraft,
1799
+ state: fixtures_1.walletState.Live,
1800
+ });
1801
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1802
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1803
+ await bridge.revealDeposit(fundingTx, reveal);
1804
+ // Simulate the wallet's state has changed
1805
+ const wallet = await bridge.wallets(reveal.walletPubKeyHash);
1806
+ await bridge.setWallet(reveal.walletPubKeyHash, {
1807
+ ...wallet,
1808
+ state: test.walletState,
1809
+ });
1810
+ });
1811
+ after(async () => {
1812
+ await restoreSnapshot();
1813
+ });
1814
+ it("should revert", async () => {
1815
+ await (0, chai_1.expect)(bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo, hardhat_1.ethers.constants.AddressZero)).to.be.revertedWith("Wallet must be in Live or MovingFunds state");
1816
+ });
1817
+ });
1818
+ });
1819
+ });
1820
+ });
1821
+ async function runDepositSweepScenario(data, beforeProofActions) {
1822
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1823
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1824
+ for (let i = 0; i < data.deposits.length; i++) {
1825
+ const { fundingTx, reveal } = data.deposits[i];
1826
+ // eslint-disable-next-line no-await-in-loop
1827
+ await bridge.revealDeposit(fundingTx, reveal);
1828
+ }
1829
+ if (beforeProofActions) {
1830
+ await beforeProofActions();
1831
+ }
1832
+ return bridge.submitDepositSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo, data.vault);
1833
+ }
1834
+ });