@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,2523 @@
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 ethers_1 = require("ethers");
34
+ const smock_1 = require("@defi-wonderland/smock");
35
+ const deposit_sweep_1 = require("../data/deposit-sweep");
36
+ const redemption_1 = require("../data/redemption");
37
+ const fixtures_1 = require("../fixtures");
38
+ const bridge_1 = __importDefault(require("../fixtures/bridge"));
39
+ chai_1.default.use(smock_1.smock.matchers);
40
+ const { createSnapshot, restoreSnapshot } = hardhat_1.helpers.snapshot;
41
+ const { lastBlockTime, increaseTime } = hardhat_1.helpers.time;
42
+ const { impersonateAccount } = hardhat_1.helpers.account;
43
+ describe("Bridge - Redemption", () => {
44
+ let governance;
45
+ let thirdParty;
46
+ let treasury;
47
+ let bank;
48
+ let relay;
49
+ let BridgeFactory;
50
+ let bridge;
51
+ let walletRegistry;
52
+ let redemptionTimeout;
53
+ let redemptionTimeoutSlashingAmount;
54
+ let redemptionTimeoutNotifierRewardMultiplier;
55
+ before(async () => {
56
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi
57
+ ;
58
+ ({
59
+ governance,
60
+ thirdParty,
61
+ treasury,
62
+ bank,
63
+ relay,
64
+ walletRegistry,
65
+ bridge,
66
+ BridgeFactory,
67
+ } = await hardhat_1.waffle.loadFixture(bridge_1.default));
68
+ ({
69
+ redemptionTimeout,
70
+ redemptionTimeoutSlashingAmount,
71
+ redemptionTimeoutNotifierRewardMultiplier,
72
+ } = await bridge.redemptionParameters());
73
+ // Set the deposit dust threshold to 0.0001 BTC, i.e. 100x smaller than
74
+ // the initial value in the Bridge in order to save test Bitcoins.
75
+ await bridge.setDepositDustThreshold(10000);
76
+ // Set the redemption dust threshold to 0.001 BTC, i.e. 10x smaller than
77
+ // the initial value in the Bridge in order to save test Bitcoins.
78
+ await bridge.setRedemptionDustThreshold(100000);
79
+ redemptionTimeout = (await bridge.redemptionParameters()).redemptionTimeout;
80
+ });
81
+ describe("requestRedemption", () => {
82
+ const walletPubKeyHash = "0x8db50eb52063ea9d98b3eac91489a90f738986f6";
83
+ context("when wallet state is Live", () => {
84
+ before(async () => {
85
+ await createSnapshot();
86
+ // Simulate the wallet is an Live one and is known to the system.
87
+ await bridge.setWallet(walletPubKeyHash, {
88
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
89
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
90
+ pendingRedemptionsValue: 0,
91
+ createdAt: await lastBlockTime(),
92
+ movingFundsRequestedAt: 0,
93
+ closingStartedAt: 0,
94
+ pendingMovedFundsSweepRequestsCount: 0,
95
+ state: fixtures_1.walletState.Live,
96
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
97
+ });
98
+ });
99
+ after(async () => {
100
+ await restoreSnapshot();
101
+ });
102
+ context("when there is a main UTXO for the given wallet", () => {
103
+ // Prepare a dumb main UTXO with 10M satoshi as value. This will
104
+ // be the wallet BTC balance.
105
+ const mainUtxo = {
106
+ txHash: "0x3835ecdee2daa83c9a19b5012104ace55ecab197b5e16489c26d372e475f5d2a",
107
+ txOutputIndex: 0,
108
+ txOutputValue: 10000000,
109
+ };
110
+ before(async () => {
111
+ await createSnapshot();
112
+ // Simulate the prepared main UTXO belongs to the wallet.
113
+ await bridge.setWalletMainUtxo(walletPubKeyHash, mainUtxo);
114
+ });
115
+ after(async () => {
116
+ await restoreSnapshot();
117
+ });
118
+ context("when main UTXO data are valid", () => {
119
+ context("when redeemer output script is standard type", () => {
120
+ // Arbitrary standard output scripts.
121
+ const redeemerOutputScriptP2WPKH = "0x160014f4eedc8f40d4b8e30771f792b065ebec0abaddef";
122
+ const redeemerOutputScriptP2WSH = "0x220020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c";
123
+ const redeemerOutputScriptP2PKH = "0x1976a914f4eedc8f40d4b8e30771f792b065ebec0abaddef88ac";
124
+ const redeemerOutputScriptP2SH = "0x17a914f4eedc8f40d4b8e30771f792b065ebec0abaddef87";
125
+ context("when redeemer output script does not point to the wallet public key hash", () => {
126
+ context("when amount is not below the dust threshold", () => {
127
+ // Requested amount is 1901000 satoshi.
128
+ const requestedAmount = ethers_1.BigNumber.from(1901000);
129
+ // Treasury fee is `requestedAmount / redemptionTreasuryFeeDivisor`
130
+ // where the divisor is `2000` initially. So, we
131
+ // have 1901000 / 2000 = 950.5 though Solidity
132
+ // loses the decimal part.
133
+ const treasuryFee = 950;
134
+ context("when there is no pending request for the given redemption key", () => {
135
+ context("when wallet has sufficient funds", () => {
136
+ context("when redeemer made a sufficient allowance in Bank", () => {
137
+ let redeemer;
138
+ before(async () => {
139
+ await createSnapshot();
140
+ // Use an arbitrary ETH account as redeemer.
141
+ redeemer = thirdParty;
142
+ await makeRedemptionAllowance(redeemer, requestedAmount);
143
+ });
144
+ after(async () => {
145
+ await restoreSnapshot();
146
+ });
147
+ context("when redeemer output script is P2WPKH", () => {
148
+ const redeemerOutputScript = redeemerOutputScriptP2WPKH;
149
+ let initialBridgeBalance;
150
+ let initialRedeemerBalance;
151
+ let initialWalletPendingRedemptionValue;
152
+ let tx;
153
+ let redemptionTxMaxFee;
154
+ before(async () => {
155
+ await createSnapshot();
156
+ redemptionTxMaxFee = (await bridge.redemptionParameters()).redemptionTxMaxFee;
157
+ // Capture initial balance of Bridge and
158
+ // redeemer.
159
+ initialBridgeBalance = await bank.balanceOf(bridge.address);
160
+ initialRedeemerBalance = await bank.balanceOf(redeemer.address);
161
+ // Capture the initial pending redemptions value
162
+ // for the given wallet.
163
+ initialWalletPendingRedemptionValue = (await bridge.wallets(walletPubKeyHash)).pendingRedemptionsValue;
164
+ // Perform the redemption request.
165
+ tx = await bridge
166
+ .connect(redeemer)
167
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScript, requestedAmount);
168
+ });
169
+ after(async () => {
170
+ await restoreSnapshot();
171
+ });
172
+ it("should increase the wallet's pending redemptions value", async () => {
173
+ const walletPendingRedemptionValue = (await bridge.wallets(walletPubKeyHash)).pendingRedemptionsValue;
174
+ (0, chai_1.expect)(walletPendingRedemptionValue.sub(initialWalletPendingRedemptionValue)).to.be.equal(requestedAmount.sub(treasuryFee));
175
+ });
176
+ it("should store the redemption request", async () => {
177
+ const redemptionKey = buildRedemptionKey(walletPubKeyHash, redeemerOutputScript);
178
+ const redemptionRequest = await bridge.pendingRedemptions(redemptionKey);
179
+ (0, chai_1.expect)(redemptionRequest.redeemer).to.be.equal(redeemer.address);
180
+ (0, chai_1.expect)(redemptionRequest.requestedAmount).to.be.equal(requestedAmount);
181
+ (0, chai_1.expect)(redemptionRequest.treasuryFee).to.be.equal(treasuryFee);
182
+ (0, chai_1.expect)(redemptionRequest.txMaxFee).to.be.equal(redemptionTxMaxFee);
183
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(await lastBlockTime());
184
+ });
185
+ it("should emit RedemptionRequested event", async () => {
186
+ await (0, chai_1.expect)(tx)
187
+ .to.emit(bridge, "RedemptionRequested")
188
+ .withArgs(walletPubKeyHash, redeemerOutputScript, redeemer.address, requestedAmount, treasuryFee, redemptionTxMaxFee);
189
+ });
190
+ it("should take the right balance from Bank", async () => {
191
+ const bridgeBalance = await bank.balanceOf(bridge.address);
192
+ (0, chai_1.expect)(bridgeBalance.sub(initialBridgeBalance)).to.equal(requestedAmount);
193
+ const redeemerBalance = await bank.balanceOf(redeemer.address);
194
+ (0, chai_1.expect)(redeemerBalance.sub(initialRedeemerBalance)).to.equal(requestedAmount.mul(-1));
195
+ });
196
+ });
197
+ context("when redeemer output script is P2WSH", () => {
198
+ before(async () => {
199
+ await createSnapshot();
200
+ });
201
+ after(async () => {
202
+ await restoreSnapshot();
203
+ });
204
+ // Do not repeat all check made in the
205
+ // "when redeemer output script is P2WPKH"
206
+ // scenario but just assert the call succeeds
207
+ // for an P2WSH output script.
208
+ it("should succeed", async () => {
209
+ await (0, chai_1.expect)(bridge
210
+ .connect(redeemer)
211
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WSH, requestedAmount)).to.not.be.reverted;
212
+ });
213
+ });
214
+ context("when redeemer output script is P2PKH", () => {
215
+ before(async () => {
216
+ await createSnapshot();
217
+ });
218
+ after(async () => {
219
+ await restoreSnapshot();
220
+ });
221
+ // Do not repeat all check made in the
222
+ // "when redeemer output script is P2WPKH"
223
+ // scenario but just assert the call succeeds
224
+ // for an P2PKH output script.
225
+ it("should succeed", async () => {
226
+ await (0, chai_1.expect)(bridge
227
+ .connect(redeemer)
228
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2PKH, requestedAmount)).to.not.be.reverted;
229
+ });
230
+ });
231
+ context("when redeemer output script is P2SH", () => {
232
+ before(async () => {
233
+ await createSnapshot();
234
+ });
235
+ after(async () => {
236
+ await restoreSnapshot();
237
+ });
238
+ // Do not repeat all check made in the
239
+ // "when redeemer output script is P2WPKH"
240
+ // scenario but just assert the call succeeds
241
+ // for an P2SH output script.
242
+ it("should succeed", async () => {
243
+ await (0, chai_1.expect)(bridge
244
+ .connect(redeemer)
245
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2SH, requestedAmount)).to.not.be.reverted;
246
+ });
247
+ });
248
+ });
249
+ context("when redeemer has not made a sufficient allowance in Bank", () => {
250
+ it("should revert", async () => {
251
+ await (0, chai_1.expect)(bridge
252
+ .connect(thirdParty)
253
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WPKH, requestedAmount)).to.be.revertedWith("Transfer amount exceeds allowance");
254
+ });
255
+ });
256
+ });
257
+ context("when wallet has insufficient funds", () => {
258
+ before(async () => {
259
+ await createSnapshot();
260
+ // Simulate a situation when the wallet has so many
261
+ // pending redemptions that a new request will
262
+ // exceed its Bitcoin balance. This is done by making
263
+ // a redemption request that will request the entire
264
+ // wallet's balance right before the tested request.
265
+ await makeRedemptionAllowance(thirdParty, mainUtxo.txOutputValue);
266
+ await bridge
267
+ .connect(thirdParty)
268
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WPKH, mainUtxo.txOutputValue);
269
+ });
270
+ after(async () => {
271
+ await restoreSnapshot();
272
+ });
273
+ it("should revert", async () => {
274
+ await (0, chai_1.expect)(bridge
275
+ .connect(thirdParty)
276
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WSH, requestedAmount)).to.be.revertedWith("Insufficient wallet funds");
277
+ });
278
+ });
279
+ });
280
+ context("when there is a pending request for the given redemption key", () => {
281
+ before(async () => {
282
+ await createSnapshot();
283
+ // Make a request targeting the given wallet and
284
+ // redeemer output script. Tested request will use
285
+ // the same parameters.
286
+ await makeRedemptionAllowance(thirdParty, mainUtxo.txOutputValue);
287
+ await bridge
288
+ .connect(thirdParty)
289
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WPKH, mainUtxo.txOutputValue);
290
+ });
291
+ after(async () => {
292
+ await restoreSnapshot();
293
+ });
294
+ it("should revert", async () => {
295
+ await (0, chai_1.expect)(bridge
296
+ .connect(thirdParty)
297
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WPKH, requestedAmount)).to.be.revertedWith("There is a pending redemption request from this wallet to the same address");
298
+ });
299
+ });
300
+ });
301
+ context("when amount is below the dust threshold", () => {
302
+ it("should revert", async () => {
303
+ // Initial dust threshold set in the tests `fixture`
304
+ // for tests is 100000. A value lower by 1 sat should
305
+ // trigger the tested condition.
306
+ await (0, chai_1.expect)(bridge
307
+ .connect(thirdParty)
308
+ .requestRedemption(walletPubKeyHash, mainUtxo, redeemerOutputScriptP2WPKH, 99999)).to.be.revertedWith("Redemption amount too small");
309
+ });
310
+ });
311
+ });
312
+ context("when redeemer output script points to the wallet public key hash", () => {
313
+ it("should revert", async () => {
314
+ // Wallet public key hash hidden under P2WPKH.
315
+ await (0, chai_1.expect)(bridge
316
+ .connect(thirdParty)
317
+ .requestRedemption(walletPubKeyHash, mainUtxo, `0x160014${walletPubKeyHash.substring(2)}`, 100000)).to.be.revertedWith("Redeemer output script must not point to the wallet PKH");
318
+ // Wallet public key hash hidden under P2PKH.
319
+ await (0, chai_1.expect)(bridge
320
+ .connect(thirdParty)
321
+ .requestRedemption(walletPubKeyHash, mainUtxo, `0x1976a914${walletPubKeyHash.substring(2)}88ac`, 100000)).to.be.revertedWith("Redeemer output script must not point to the wallet PKH");
322
+ // Wallet public key hash hidden under P2SH.
323
+ await (0, chai_1.expect)(bridge
324
+ .connect(thirdParty)
325
+ .requestRedemption(walletPubKeyHash, mainUtxo, `0x17a914${walletPubKeyHash.substring(2)}87`, 100000)).to.be.revertedWith("Redeemer output script must not point to the wallet PKH");
326
+ // There is no need to check for P2WSH since that type
327
+ // uses 32-byte hashes. Because wallet public key hash is
328
+ // always 20-byte, there is no possibility those hashes
329
+ // can be confused during change output recognition.
330
+ });
331
+ });
332
+ });
333
+ context("when redeemer output script is not standard type", () => {
334
+ it("should revert", async () => {
335
+ // The set of non-standard/malformed scripts is infinite.
336
+ // A malformed P2PKH redeemer script is used as example.
337
+ await (0, chai_1.expect)(bridge
338
+ .connect(thirdParty)
339
+ .requestRedemption(walletPubKeyHash, mainUtxo, "0x1988a914f4eedc8f40d4b8e30771f792b065ebec0abaddef88ac", 100000)).to.be.revertedWith("Redeemer output script must be a standard type");
340
+ });
341
+ });
342
+ });
343
+ context("when main UTXO data are invalid", () => {
344
+ it("should revert", async () => {
345
+ // The proper main UTXO hash `0` as `txOutputIndex`.
346
+ await (0, chai_1.expect)(bridge.connect(thirdParty).requestRedemption(walletPubKeyHash, {
347
+ txHash: "0x3835ecdee2daa83c9a19b5012104ace55ecab197b5e16489c26d372e475f5d2a",
348
+ txOutputIndex: 1,
349
+ txOutputValue: 10000000,
350
+ }, "0x160014f4eedc8f40d4b8e30771f792b065ebec0abaddef", 100000)).to.be.revertedWith("Invalid main UTXO data");
351
+ });
352
+ });
353
+ });
354
+ context("when there is no main UTXO for the given wallet", () => {
355
+ it("should revert", async () => {
356
+ // Since there is no main UTXO for this wallet recorded in the
357
+ // Bridge, the `mainUtxo` parameter can be anything.
358
+ await (0, chai_1.expect)(bridge
359
+ .connect(thirdParty)
360
+ .requestRedemption(walletPubKeyHash, deposit_sweep_1.NO_MAIN_UTXO, "0x160014f4eedc8f40d4b8e30771f792b065ebec0abaddef", 100000)).to.be.revertedWith("No main UTXO for the given wallet");
361
+ });
362
+ });
363
+ });
364
+ context("when wallet state is other than Live", () => {
365
+ const testData = [
366
+ {
367
+ testName: "when wallet state is Unknown",
368
+ state: fixtures_1.walletState.Unknown,
369
+ },
370
+ {
371
+ testName: "when wallet state is MovingFunds",
372
+ state: fixtures_1.walletState.MovingFunds,
373
+ },
374
+ {
375
+ testName: "when wallet state is Closing",
376
+ state: fixtures_1.walletState.Closing,
377
+ },
378
+ {
379
+ testName: "when wallet state is Closed",
380
+ state: fixtures_1.walletState.Closed,
381
+ },
382
+ {
383
+ testName: "when wallet state is Terminated",
384
+ state: fixtures_1.walletState.Terminated,
385
+ },
386
+ ];
387
+ testData.forEach((test) => {
388
+ context(test.testName, () => {
389
+ before(async () => {
390
+ await createSnapshot();
391
+ await bridge.setWallet(walletPubKeyHash, {
392
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
393
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
394
+ pendingRedemptionsValue: 0,
395
+ createdAt: await lastBlockTime(),
396
+ movingFundsRequestedAt: 0,
397
+ closingStartedAt: 0,
398
+ pendingMovedFundsSweepRequestsCount: 0,
399
+ state: test.state,
400
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
401
+ });
402
+ });
403
+ after(async () => {
404
+ await restoreSnapshot();
405
+ });
406
+ it("should revert", async () => {
407
+ await (0, chai_1.expect)(bridge
408
+ .connect(thirdParty)
409
+ .requestRedemption(walletPubKeyHash, deposit_sweep_1.NO_MAIN_UTXO, "0x160014f4eedc8f40d4b8e30771f792b065ebec0abaddef", 100000)).to.be.revertedWith("Wallet must be in Live state");
410
+ });
411
+ });
412
+ });
413
+ });
414
+ });
415
+ describe("receiveBalanceApproval", () => {
416
+ const walletPubKeyHash = "0x8db50eb52063ea9d98b3eac91489a90f738986f6";
417
+ // Requested amount is 1901000 satoshi.
418
+ const requestedAmount = ethers_1.BigNumber.from(1901000);
419
+ // Treasury fee is `requestedAmount / redemptionTreasuryFeeDivisor`
420
+ // where the divisor is `2000` initially. So, we
421
+ // have 1901000 / 2000 = 950.5 though Solidity
422
+ // loses the decimal part.
423
+ const treasuryFee = 950;
424
+ let redemptionTxMaxFee;
425
+ let balanceOwner;
426
+ let redeemer;
427
+ before(async () => {
428
+ await createSnapshot();
429
+ redemptionTxMaxFee = (await bridge.redemptionParameters())
430
+ .redemptionTxMaxFee;
431
+ balanceOwner = thirdParty;
432
+ // eslint-disable-next-line prefer-destructuring
433
+ redeemer = (await (0, hardhat_1.getUnnamedAccounts)())[10];
434
+ // Simulate the balance owner has a twice Bank balance allowing to make the request.
435
+ await bank.setBalance(balanceOwner.address, requestedAmount.mul(2));
436
+ });
437
+ after(async () => {
438
+ await restoreSnapshot();
439
+ });
440
+ // Only the most basic path is tested. `receiveBalanceApproval` uses the same
441
+ // code as `requestRedemption` so all tests done for `requestRedemption`
442
+ // applies to `receiveBalanceApproval` as well.
443
+ context("when called via Bank.approveBalanceAndCall", () => {
444
+ context("when wallet state is Live", () => {
445
+ before(async () => {
446
+ await createSnapshot();
447
+ // Simulate the wallet is an Live one and is known to the system.
448
+ await bridge.setWallet(walletPubKeyHash, {
449
+ ecdsaWalletID: hardhat_1.ethers.constants.HashZero,
450
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
451
+ pendingRedemptionsValue: 0,
452
+ createdAt: await lastBlockTime(),
453
+ movingFundsRequestedAt: 0,
454
+ closingStartedAt: 0,
455
+ pendingMovedFundsSweepRequestsCount: 0,
456
+ state: fixtures_1.walletState.Live,
457
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
458
+ });
459
+ });
460
+ after(async () => {
461
+ await restoreSnapshot();
462
+ });
463
+ context("when there is a main UTXO for the given wallet", () => {
464
+ // Prepare a dumb main UTXO with 10M satoshi as value. This will
465
+ // be the wallet BTC balance.
466
+ const mainUtxo = {
467
+ txHash: "0x3835ecdee2daa83c9a19b5012104ace55ecab197b5e16489c26d372e475f5d2a",
468
+ txOutputIndex: 0,
469
+ txOutputValue: 10000000,
470
+ };
471
+ before(async () => {
472
+ await createSnapshot();
473
+ // Simulate the prepared main UTXO belongs to the wallet.
474
+ await bridge.setWalletMainUtxo(walletPubKeyHash, mainUtxo);
475
+ });
476
+ after(async () => {
477
+ await restoreSnapshot();
478
+ });
479
+ context("when main UTXO data are valid", () => {
480
+ context("when redeemer output script is standard type", () => {
481
+ // Arbitrary standard output script.
482
+ const redeemerOutputScriptP2WPKH = "0x160014f4eedc8f40d4b8e30771f792b065ebec0abaddef";
483
+ context("when redeemer output script does not point to the wallet public key hash", () => {
484
+ context("when amount is not below the dust threshold", () => {
485
+ context("when redeemer output script is P2WPKH", () => {
486
+ let initialBridgeBalance;
487
+ let initialBalanceOwnerBalance;
488
+ let initialRedeemerBalance;
489
+ let initialWalletPendingRedemptionValue;
490
+ let tx;
491
+ before(async () => {
492
+ await createSnapshot();
493
+ initialBridgeBalance = await bank.balanceOf(bridge.address);
494
+ initialBalanceOwnerBalance = await bank.balanceOf(balanceOwner.address);
495
+ initialRedeemerBalance = await bank.balanceOf(redeemer);
496
+ initialWalletPendingRedemptionValue = (await bridge.wallets(walletPubKeyHash)).pendingRedemptionsValue;
497
+ const { defaultAbiCoder } = hardhat_1.ethers.utils;
498
+ const data = defaultAbiCoder.encode([
499
+ "address",
500
+ "bytes20",
501
+ "bytes32",
502
+ "uint32",
503
+ "uint64",
504
+ "bytes",
505
+ ], [
506
+ redeemer,
507
+ walletPubKeyHash,
508
+ mainUtxo.txHash,
509
+ mainUtxo.txOutputIndex,
510
+ mainUtxo.txOutputValue,
511
+ redeemerOutputScriptP2WPKH,
512
+ ]);
513
+ tx = await bank
514
+ .connect(balanceOwner)
515
+ .approveBalanceAndCall(bridge.address, requestedAmount, data);
516
+ });
517
+ after(async () => {
518
+ await restoreSnapshot();
519
+ });
520
+ it("should increase the wallet's pending redemptions value", async () => {
521
+ const walletPendingRedemptionValue = (await bridge.wallets(walletPubKeyHash)).pendingRedemptionsValue;
522
+ (0, chai_1.expect)(walletPendingRedemptionValue.sub(initialWalletPendingRedemptionValue)).to.be.equal(requestedAmount.sub(treasuryFee));
523
+ });
524
+ it("should store the redemption request", async () => {
525
+ const redemptionKey = buildRedemptionKey(walletPubKeyHash, redeemerOutputScriptP2WPKH);
526
+ const redemptionRequest = await bridge.pendingRedemptions(redemptionKey);
527
+ (0, chai_1.expect)(redemptionRequest.redeemer).to.be.equal(redeemer);
528
+ (0, chai_1.expect)(redemptionRequest.requestedAmount).to.be.equal(requestedAmount);
529
+ (0, chai_1.expect)(redemptionRequest.treasuryFee).to.be.equal(treasuryFee);
530
+ (0, chai_1.expect)(redemptionRequest.txMaxFee).to.be.equal(redemptionTxMaxFee);
531
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(await lastBlockTime());
532
+ });
533
+ it("should emit RedemptionRequested event", async () => {
534
+ await (0, chai_1.expect)(tx)
535
+ .to.emit(bridge, "RedemptionRequested")
536
+ .withArgs(walletPubKeyHash, redeemerOutputScriptP2WPKH, redeemer, requestedAmount, treasuryFee, redemptionTxMaxFee);
537
+ });
538
+ it("should take the right balance from Bank", async () => {
539
+ const bridgeBalance = await bank.balanceOf(bridge.address);
540
+ (0, chai_1.expect)(bridgeBalance.sub(initialBridgeBalance)).to.equal(requestedAmount);
541
+ const redeemerBalance = await bank.balanceOf(redeemer);
542
+ (0, chai_1.expect)(redeemerBalance).to.equal(initialRedeemerBalance);
543
+ const balanceOwnerBalance = await bank.balanceOf(balanceOwner.address);
544
+ (0, chai_1.expect)(balanceOwnerBalance.sub(initialBalanceOwnerBalance)).to.equal(requestedAmount.mul(-1));
545
+ });
546
+ });
547
+ });
548
+ });
549
+ });
550
+ });
551
+ });
552
+ });
553
+ });
554
+ context("when called directly", () => {
555
+ it("should revert", async () => {
556
+ await (0, chai_1.expect)(bridge
557
+ .connect(thirdParty)
558
+ .receiveBalanceApproval(thirdParty.address, 1, [])).to.be.revertedWith("Caller is not the bank");
559
+ });
560
+ });
561
+ });
562
+ describe("submitRedemptionProof", () => {
563
+ context("when transaction proof is valid", () => {
564
+ context("when there is a main UTXO for the given wallet", () => {
565
+ context("when main UTXO data are valid", () => {
566
+ context("when there is only one input", () => {
567
+ context("when the single input points to the wallet's main UTXO", () => {
568
+ context("when wallet state is Live", () => {
569
+ context("when there is only one output", () => {
570
+ context("when the single output is a pending requested redemption", () => {
571
+ const data = redemption_1.SinglePendingRequestedRedemption;
572
+ let tx;
573
+ let bridgeBalance;
574
+ let walletPendingRedemptionsValue;
575
+ let treasuryBalance;
576
+ let redeemersBalances;
577
+ before(async () => {
578
+ await createSnapshot();
579
+ // Simulate the situation when treasury fee is 0% to
580
+ // allow using the whole wallet's main UTXO value
581
+ // to fulfill the redemption request.
582
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
583
+ ({
584
+ tx,
585
+ bridgeBalance,
586
+ walletPendingRedemptionsValue,
587
+ treasuryBalance,
588
+ redeemersBalances,
589
+ } = await runRedemptionScenario(data));
590
+ });
591
+ after(async () => {
592
+ await restoreSnapshot();
593
+ });
594
+ it("should close processed redemption request", async () => {
595
+ const redemptionRequest = await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
596
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, "Redemption request has not been closed");
597
+ });
598
+ it("should delete the wallet's main UTXO", async () => {
599
+ // The Bitcoin redemption transaction has no change
600
+ // output so the wallet's main UTXO should be
601
+ // deleted on the Bridge side.
602
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
603
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
604
+ });
605
+ it("should mark the previous main UTXO as spent", async () => {
606
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
607
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
608
+ .true;
609
+ });
610
+ it("should decrease the wallet's pending redemptions value", async () => {
611
+ // Wallet pending redemptions value should be
612
+ // decreased by the total redeemable amount but since
613
+ // the treasury fee is 0% in this test case, the
614
+ // total redeemable amount is equal to the total
615
+ // requested amount. See docs of the used test
616
+ // data for details.
617
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-1177424);
618
+ });
619
+ it("should decrease Bridge's balance in Bank", async () => {
620
+ // Balance should be decreased by the total
621
+ // redeemable amount but since the treasury fee
622
+ // is 0% in this test case, the total redeemable
623
+ // amount is equal to the total requested amount.
624
+ // See docs of the used test data for details.
625
+ await (0, chai_1.expect)(tx)
626
+ .to.emit(bank, "BalanceDecreased")
627
+ .withArgs(bridge.address, 1177424);
628
+ // In this case, the total Bridge balance change
629
+ // should be also equal to the same amount.
630
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-1177424);
631
+ });
632
+ it("should not transfer anything to the treasury", async () => {
633
+ // Treasury balance should not be increased because
634
+ // the treasury fee is 0% in this test case.
635
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
636
+ });
637
+ it("should not change redeemer balance in any way", async () => {
638
+ // Redemption proof submission should NEVER cause
639
+ // any change of the redeemer balance.
640
+ const redeemerBalance = redeemersBalances[0];
641
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, "Balance of redeemer has changed");
642
+ });
643
+ });
644
+ context("when the single output is a non-reported timed out requested redemption", () => {
645
+ const data = redemption_1.SinglePendingRequestedRedemption;
646
+ let tx;
647
+ let bridgeBalance;
648
+ let walletPendingRedemptionsValue;
649
+ let treasuryBalance;
650
+ let redeemersBalances;
651
+ before(async () => {
652
+ await createSnapshot();
653
+ // Simulate the situation when treasury fee is 0% to
654
+ // allow using the whole wallet's main UTXO value
655
+ // to fulfill the redemption request.
656
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
657
+ // Before submitting the redemption proof, wait
658
+ // an amount of time that will make the request
659
+ // timed out though don't report the timeout.
660
+ const beforeProofActions = async () => {
661
+ await increaseTime(redemptionTimeout);
662
+ };
663
+ ({
664
+ tx,
665
+ bridgeBalance,
666
+ walletPendingRedemptionsValue,
667
+ treasuryBalance,
668
+ redeemersBalances,
669
+ } = await runRedemptionScenario(data, beforeProofActions));
670
+ });
671
+ after(async () => {
672
+ await restoreSnapshot();
673
+ });
674
+ it("should close processed redemption request", async () => {
675
+ const redemptionRequest = await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
676
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, "Redemption request has not been closed");
677
+ });
678
+ it("should delete the wallet's main UTXO", async () => {
679
+ // The Bitcoin redemption transaction has no change
680
+ // output so the wallet's main UTXO should be
681
+ // deleted on the Bridge side.
682
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
683
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
684
+ });
685
+ it("should mark the previous main UTXO as spent", async () => {
686
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
687
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
688
+ .true;
689
+ });
690
+ it("should decrease the wallet's pending redemptions value", async () => {
691
+ // Wallet pending redemptions value should be
692
+ // decreased by the total redeemable amount but since
693
+ // the treasury fee is 0% in this test case, the
694
+ // total redeemable amount is equal to the total
695
+ // requested amount. See docs of the used test
696
+ // data for details.
697
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-1177424);
698
+ });
699
+ it("should decrease Bridge's balance in Bank", async () => {
700
+ // Balance should be decreased by the total
701
+ // redeemable amount but since the treasury fee
702
+ // is 0% in this test case, the total redeemable
703
+ // amount is equal to the total requested amount.
704
+ // See docs of the used test data for details.
705
+ await (0, chai_1.expect)(tx)
706
+ .to.emit(bank, "BalanceDecreased")
707
+ .withArgs(bridge.address, 1177424);
708
+ // In this case, the total Bridge balance change
709
+ // should be also equal to the same amount.
710
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-1177424);
711
+ });
712
+ it("should not transfer anything to the treasury", async () => {
713
+ // Treasury balance should not be increased because
714
+ // the treasury fee is 0% in this test case.
715
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
716
+ });
717
+ it("should not change redeemer balance in any way", async () => {
718
+ // Redemption proof submission should NEVER cause
719
+ // any change of the redeemer balance.
720
+ const redeemerBalance = redeemersBalances[0];
721
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, "Balance of redeemer has changed");
722
+ });
723
+ });
724
+ context("when the single output is a reported timed out requested redemption", () => {
725
+ const data = redemption_1.SinglePendingRequestedRedemption;
726
+ let tx;
727
+ let bridgeBalance;
728
+ let walletPendingRedemptionsValue;
729
+ let treasuryBalance;
730
+ let redeemersBalances;
731
+ before(async () => {
732
+ await createSnapshot();
733
+ // Simulate the situation when treasury fee is 0% to
734
+ // allow using the whole wallet's main UTXO value
735
+ // to fulfill the redemption request.
736
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
737
+ // Before submitting the redemption proof, wait
738
+ // an amount of time that will make the request
739
+ // timed out and then report the timeout.
740
+ const beforeProofActions = async () => {
741
+ await increaseTime(redemptionTimeout);
742
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
743
+ };
744
+ ({
745
+ tx,
746
+ bridgeBalance,
747
+ walletPendingRedemptionsValue,
748
+ treasuryBalance,
749
+ redeemersBalances,
750
+ } = await runRedemptionScenario(data, beforeProofActions));
751
+ });
752
+ after(async () => {
753
+ walletRegistry.seize.reset();
754
+ await restoreSnapshot();
755
+ });
756
+ it("should hold the timed out request in the contract state", async () => {
757
+ const redemptionRequest = await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
758
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.greaterThan(0, "Timed out request was removed from the contract state");
759
+ });
760
+ it("should delete the wallet's main UTXO", async () => {
761
+ // The Bitcoin redemption transaction has no change
762
+ // output so the wallet's main UTXO should be
763
+ // deleted on the Bridge side. It doesn't matter
764
+ // the only redemption handled by the transaction
765
+ // is reported as timed out.
766
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
767
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
768
+ });
769
+ it("should mark the previous main UTXO as spent", async () => {
770
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
771
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
772
+ .true;
773
+ });
774
+ it("should not change the wallet's pending redemptions value", async () => {
775
+ // All the bookkeeping regarding the timed out
776
+ // request was done upon timeout report. The
777
+ // wallet pending redemptions value should not
778
+ // be changed in any way.
779
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(0);
780
+ });
781
+ it("should not change Bridge's balance in Bank", async () => {
782
+ // All the bookkeeping regarding the timed out
783
+ // request was done upon timeout report. The Bridge
784
+ // balance in the bank should neither be decreased...
785
+ await (0, chai_1.expect)(tx)
786
+ .to.emit(bank, "BalanceDecreased")
787
+ .withArgs(bridge.address, 0);
788
+ // ...nor changed in any other way.
789
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(0);
790
+ });
791
+ it("should not transfer anything to the treasury", async () => {
792
+ // Treasury balance should not be increased in any way
793
+ // since the only request handled by the redemption
794
+ // transaction is reported as timed out and is just
795
+ // skipped during processing.
796
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
797
+ });
798
+ it("should not change redeemer balance in any way", async () => {
799
+ // Redemption proof submission should NEVER cause
800
+ // any change of the redeemer balance.
801
+ const redeemerBalance = redeemersBalances[0];
802
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, "Balance of redeemer has changed");
803
+ });
804
+ });
805
+ context("when the single output is a pending requested redemption but redeemed amount is wrong", () => {
806
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
807
+ let outcome;
808
+ before(async () => {
809
+ await createSnapshot();
810
+ // Alter the single redemption request in the test
811
+ // data and set such an amount that will cause
812
+ // the Bitcoin redemption transaction to be deemed
813
+ // as invalid due to a wrong amount. The transaction
814
+ // output has the value of 1176924 so to make this
815
+ // test scenario happen, the request amount must be
816
+ // way different (lesser or greater) than the output
817
+ // value. Worth noting that this test scenario tests
818
+ // the amount condition in a general and simplified
819
+ // way without stressing all specific edge cases.
820
+ // Doing a detailed check would require more dedicated
821
+ // test data sets which would make it far more
822
+ // complicated without giving much value in return.
823
+ data.redemptionRequests[0].amount = 300000;
824
+ outcome = runRedemptionScenario(data);
825
+ });
826
+ after(async () => {
827
+ await restoreSnapshot();
828
+ });
829
+ it("should revert", async () => {
830
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output value is not within the acceptable range of the pending request");
831
+ });
832
+ });
833
+ context("when the single output is a reported timed out requested redemption but amount is wrong", () => {
834
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
835
+ let outcome;
836
+ before(async () => {
837
+ await createSnapshot();
838
+ // Alter the single redemption request in the test
839
+ // data and set such an amount that will cause
840
+ // the Bitcoin redemption transaction to be deemed
841
+ // as invalid due to a wrong amount. The transaction
842
+ // output has the value of 1176924 so to make this
843
+ // test scenario happen, the request amount must be
844
+ // way different (lesser or greater) than the output
845
+ // value. Worth noting that this test scenario tests
846
+ // the amount condition in a general and simplified
847
+ // way without stressing all specific edge cases.
848
+ // Doing a detailed check would require more dedicated
849
+ // test data sets which would make it far more
850
+ // complicated without giving much value in return.
851
+ data.redemptionRequests[0].amount = 300000;
852
+ // Before submitting the redemption proof, wait
853
+ // an amount of time that will make the request
854
+ // timed out and then report the timeout.
855
+ const beforeProofActions = async () => {
856
+ await increaseTime(redemptionTimeout);
857
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
858
+ };
859
+ outcome = runRedemptionScenario(data, beforeProofActions);
860
+ });
861
+ after(async () => {
862
+ walletRegistry.seize.reset();
863
+ await restoreSnapshot();
864
+ });
865
+ it("should revert", async () => {
866
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output value is not within the acceptable range of the timed out request");
867
+ });
868
+ });
869
+ context("when the single output is a legal P2PKH change with a non-zero value", () => {
870
+ const data = redemption_1.SingleP2PKHChange;
871
+ let outcome;
872
+ before(async () => {
873
+ await createSnapshot();
874
+ outcome = runRedemptionScenario(data);
875
+ });
876
+ after(async () => {
877
+ await restoreSnapshot();
878
+ });
879
+ // Should be deemed as valid change though rejected
880
+ // because this change is a single output and at least
881
+ // one requested redemption is expected.
882
+ it("should revert", async () => {
883
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Redemption transaction must process at least one redemption");
884
+ });
885
+ });
886
+ context("when the single output is a legal P2WPKH change with a non-zero value", () => {
887
+ const data = redemption_1.SingleP2WPKHChange;
888
+ let outcome;
889
+ before(async () => {
890
+ await createSnapshot();
891
+ outcome = runRedemptionScenario(data);
892
+ });
893
+ after(async () => {
894
+ await restoreSnapshot();
895
+ });
896
+ // Should be deemed as valid change though rejected
897
+ // because this change is a single output and at least
898
+ // one requested redemption is expected.
899
+ it("should revert", async () => {
900
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Redemption transaction must process at least one redemption");
901
+ });
902
+ });
903
+ context("when the single output is an illegal P2SH change with a non-zero value", () => {
904
+ const data = redemption_1.SingleP2SHChange;
905
+ let outcome;
906
+ before(async () => {
907
+ await createSnapshot();
908
+ outcome = runRedemptionScenario(data);
909
+ });
910
+ after(async () => {
911
+ await restoreSnapshot();
912
+ });
913
+ // We have this case because P2SH script has a 20-byte
914
+ // payload which may match the 20-byte wallet public
915
+ // key hash though it should be always rejected as
916
+ // non-requested output. There is no need to check for
917
+ // P2WSH since the payload is always 32-byte there.
918
+ // The main reason we need to bother about 20-byte
919
+ // hashes is because the wallet public key hash has
920
+ // always 20-bytes and we must make sure no redemption
921
+ // request uses it as a redeemer script to not confuse
922
+ // an output that will try to handle that request with
923
+ // a proper change output also referencing the wallet
924
+ // public key hash.
925
+ it("should revert", async () => {
926
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
927
+ });
928
+ });
929
+ context("when the single output is a change with a zero as value", () => {
930
+ const data = redemption_1.SingleP2WPKHChangeZeroValue;
931
+ let outcome;
932
+ before(async () => {
933
+ await createSnapshot();
934
+ outcome = runRedemptionScenario(data);
935
+ });
936
+ after(async () => {
937
+ await restoreSnapshot();
938
+ });
939
+ it("should revert", async () => {
940
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
941
+ });
942
+ });
943
+ context("when the single output is a non-requested redemption to an arbitrary script", () => {
944
+ const data = redemption_1.SingleNonRequestedRedemption;
945
+ let outcome;
946
+ before(async () => {
947
+ await createSnapshot();
948
+ outcome = runRedemptionScenario(data);
949
+ });
950
+ after(async () => {
951
+ await restoreSnapshot();
952
+ });
953
+ it("should revert", async () => {
954
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
955
+ });
956
+ });
957
+ context("when the single output is provably unspendable OP_RETURN", () => {
958
+ const data = redemption_1.SingleProvablyUnspendable;
959
+ let outcome;
960
+ before(async () => {
961
+ await createSnapshot();
962
+ outcome = runRedemptionScenario(data);
963
+ });
964
+ after(async () => {
965
+ await restoreSnapshot();
966
+ });
967
+ it("should revert", async () => {
968
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
969
+ });
970
+ });
971
+ });
972
+ context("when there are multiple outputs", () => {
973
+ context("when output vector consists only of pending requested redemptions", () => {
974
+ const data = redemption_1.MultiplePendingRequestedRedemptions;
975
+ let tx;
976
+ let bridgeBalance;
977
+ let walletPendingRedemptionsValue;
978
+ let treasuryBalance;
979
+ let redeemersBalances;
980
+ before(async () => {
981
+ await createSnapshot();
982
+ // Simulate the situation when treasury fee is 0% to
983
+ // allow using the whole wallet's main UTXO value
984
+ // to fulfill the redemption requests.
985
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
986
+ ({
987
+ tx,
988
+ bridgeBalance,
989
+ walletPendingRedemptionsValue,
990
+ treasuryBalance,
991
+ redeemersBalances,
992
+ } = await runRedemptionScenario(data));
993
+ });
994
+ after(async () => {
995
+ await restoreSnapshot();
996
+ });
997
+ it("should close processed redemption requests", async () => {
998
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
999
+ const redemptionRequest =
1000
+ // eslint-disable-next-line no-await-in-loop
1001
+ await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1002
+ .redeemerOutputScript));
1003
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, `Redemption request with index ${i} has not been closed`);
1004
+ }
1005
+ });
1006
+ it("should delete the wallet's main UTXO", async () => {
1007
+ // The Bitcoin redemption transaction has no change
1008
+ // output so the wallet's main UTXO should be
1009
+ // deleted on the Bridge side.
1010
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1011
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
1012
+ });
1013
+ it("should mark the previous main UTXO as spent", async () => {
1014
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1015
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1016
+ .true;
1017
+ });
1018
+ it("should decrease the wallet's pending redemptions value", async () => {
1019
+ // Wallet pending redemptions value should be
1020
+ // decreased by the total redeemable amount but since
1021
+ // the treasury fee is 0% in this test case, the
1022
+ // total redeemable amount is equal to the total
1023
+ // requested amount. See docs of the used test
1024
+ // data for details.
1025
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-959845);
1026
+ });
1027
+ it("should decrease Bridge's balance in Bank", async () => {
1028
+ // Balance should be decreased by the total
1029
+ // redeemable amount but since the treasury fee
1030
+ // is 0% in this test case, the total redeemable
1031
+ // amount is equal to the total requested amount.
1032
+ // See docs of the used test data for details.
1033
+ await (0, chai_1.expect)(tx)
1034
+ .to.emit(bank, "BalanceDecreased")
1035
+ .withArgs(bridge.address, 959845);
1036
+ // In this case, the total Bridge balance change
1037
+ // should be also equal to the same amount.
1038
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-959845);
1039
+ });
1040
+ it("should not transfer anything to the treasury", async () => {
1041
+ // Treasury balance should not be increased because
1042
+ // the treasury fee is 0% in this test case.
1043
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
1044
+ });
1045
+ it("should not change redeemers balances in any way", async () => {
1046
+ // Redemption proof submission should NEVER cause
1047
+ // any change of the redeemers balances.
1048
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1049
+ const redeemerBalance = redeemersBalances[i];
1050
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1051
+ }
1052
+ });
1053
+ });
1054
+ context("when output vector consists of pending requested redemptions and a non-zero change", () => {
1055
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1056
+ let tx;
1057
+ let bridgeBalance;
1058
+ let walletPendingRedemptionsValue;
1059
+ let treasuryBalance;
1060
+ let redeemersBalances;
1061
+ before(async () => {
1062
+ await createSnapshot();
1063
+ ({
1064
+ tx,
1065
+ bridgeBalance,
1066
+ walletPendingRedemptionsValue,
1067
+ treasuryBalance,
1068
+ redeemersBalances,
1069
+ } = await runRedemptionScenario(data));
1070
+ });
1071
+ after(async () => {
1072
+ await restoreSnapshot();
1073
+ });
1074
+ it("should close processed redemption requests", async () => {
1075
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1076
+ const redemptionRequest =
1077
+ // eslint-disable-next-line no-await-in-loop
1078
+ await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1079
+ .redeemerOutputScript));
1080
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, `Redemption request with index ${i} has not been closed`);
1081
+ }
1082
+ });
1083
+ it("should update the wallet's main UTXO", async () => {
1084
+ // Change index and value can be taken by exploring
1085
+ // the redemption transaction structure and getting
1086
+ // the output pointing back to wallet PKH.
1087
+ const expectedMainUtxoHash = buildMainUtxoHash(data.redemptionTx.hash, 5, 137130866);
1088
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1089
+ .mainUtxoHash).to.be.equal(expectedMainUtxoHash);
1090
+ });
1091
+ it("should mark the previous main UTXO as spent", async () => {
1092
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1093
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1094
+ .true;
1095
+ });
1096
+ it("should decrease the wallet's pending redemptions value", async () => {
1097
+ // Wallet pending redemptions value should be
1098
+ // decreased by the total redeemable amount. See docs
1099
+ // of the used test data for details.
1100
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-6432350);
1101
+ });
1102
+ it("should decrease Bridge's balance in Bank", async () => {
1103
+ // Balance should be decreased by the total
1104
+ // redeemable amount. See docs of the used test
1105
+ // data for details.
1106
+ await (0, chai_1.expect)(tx)
1107
+ .to.emit(bank, "BalanceDecreased")
1108
+ .withArgs(bridge.address, 6432350);
1109
+ // However, the total balance change of the
1110
+ // Bridge should also consider the treasury
1111
+ // fee collected upon requests and transferred
1112
+ // to the treasury at the end of the proof.
1113
+ // This is why the total Bridge's balance change
1114
+ // is equal to the total requested amount for
1115
+ // all requests. See docs of the used test data
1116
+ // for details.
1117
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-6435567);
1118
+ });
1119
+ it("should transfer collected treasury fee", async () => {
1120
+ // Treasury balance should be increased by the total
1121
+ // treasury fee for all requests. See docs of the
1122
+ // used test data for details.
1123
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(3217);
1124
+ });
1125
+ it("should not change redeemers balances in any way", async () => {
1126
+ // Redemption proof submission should NEVER cause
1127
+ // any change of the redeemers balances.
1128
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1129
+ const redeemerBalance = redeemersBalances[i];
1130
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1131
+ }
1132
+ });
1133
+ });
1134
+ context("when output vector consists only of reported timed out requested redemptions", () => {
1135
+ const data = redemption_1.MultiplePendingRequestedRedemptions;
1136
+ let tx;
1137
+ let bridgeBalance;
1138
+ let walletPendingRedemptionsValue;
1139
+ let treasuryBalance;
1140
+ let redeemersBalances;
1141
+ before(async () => {
1142
+ await createSnapshot();
1143
+ // Simulate the situation when treasury fee is 0% to
1144
+ // allow using the whole wallet's main UTXO value
1145
+ // to fulfill the redemption requests.
1146
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
1147
+ // Before submitting the redemption proof, wait
1148
+ // an amount of time that will make the requests
1149
+ // timed out and then report the timeouts.
1150
+ const beforeProofActions = async () => {
1151
+ await increaseTime(redemptionTimeout);
1152
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1153
+ // eslint-disable-next-line no-await-in-loop
1154
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[i].redeemerOutputScript);
1155
+ }
1156
+ };
1157
+ ({
1158
+ tx,
1159
+ bridgeBalance,
1160
+ walletPendingRedemptionsValue,
1161
+ treasuryBalance,
1162
+ redeemersBalances,
1163
+ } = await runRedemptionScenario(data, beforeProofActions));
1164
+ });
1165
+ after(async () => {
1166
+ walletRegistry.seize.reset();
1167
+ await restoreSnapshot();
1168
+ });
1169
+ it("should hold the timed out requests in the contract state", async () => {
1170
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1171
+ const redemptionRequest =
1172
+ // eslint-disable-next-line no-await-in-loop
1173
+ await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1174
+ .redeemerOutputScript));
1175
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.greaterThan(0, `Timed out request with index ${i} was removed from the contract state`);
1176
+ }
1177
+ });
1178
+ it("should delete the wallet's main UTXO", async () => {
1179
+ // The Bitcoin redemption transaction has no change
1180
+ // output so the wallet's main UTXO should be
1181
+ // deleted on the Bridge side. It doesn't matter
1182
+ // that all redemptions handled by the transaction
1183
+ // are reported as timed out.
1184
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1185
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
1186
+ });
1187
+ it("should mark the previous main UTXO as spent", async () => {
1188
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1189
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1190
+ .true;
1191
+ });
1192
+ it("should not change the wallet's pending redemptions value", async () => {
1193
+ // All the bookkeeping regarding the timed out
1194
+ // requests was done upon timeout reports. The
1195
+ // wallet pending redemptions value should not
1196
+ // be changed in any way.
1197
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(0);
1198
+ });
1199
+ it("should not change Bridge's balance in Bank", async () => {
1200
+ // All the bookkeeping regarding the timed out
1201
+ // requests was done upon timeout reports. The Bridge
1202
+ // balance in the bank should neither be decreased...
1203
+ await (0, chai_1.expect)(tx)
1204
+ .to.emit(bank, "BalanceDecreased")
1205
+ .withArgs(bridge.address, 0);
1206
+ // ...nor changed in any other way.
1207
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(0);
1208
+ });
1209
+ it("should not transfer anything to the treasury", async () => {
1210
+ // Treasury balance should not be increased in any way
1211
+ // since all requests handled by the redemption
1212
+ // transaction are reported as timed out and are just
1213
+ // skipped during processing.
1214
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
1215
+ });
1216
+ it("should not change redeemers balances in any way", async () => {
1217
+ // Redemption proof submission should NEVER cause
1218
+ // any change of the redeemers balances.
1219
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1220
+ const redeemerBalance = redeemersBalances[i];
1221
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1222
+ }
1223
+ });
1224
+ });
1225
+ context("when output vector consists of reported timed out requested redemptions and a non-zero change", () => {
1226
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1227
+ let tx;
1228
+ let bridgeBalance;
1229
+ let walletPendingRedemptionsValue;
1230
+ let treasuryBalance;
1231
+ let redeemersBalances;
1232
+ before(async () => {
1233
+ await createSnapshot();
1234
+ // Before submitting the redemption proof, wait
1235
+ // an amount of time that will make the requests
1236
+ // timed out and then report the timeouts.
1237
+ const beforeProofActions = async () => {
1238
+ await increaseTime(redemptionTimeout);
1239
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1240
+ // eslint-disable-next-line no-await-in-loop
1241
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[i].redeemerOutputScript);
1242
+ }
1243
+ };
1244
+ ({
1245
+ tx,
1246
+ bridgeBalance,
1247
+ walletPendingRedemptionsValue,
1248
+ treasuryBalance,
1249
+ redeemersBalances,
1250
+ } = await runRedemptionScenario(data, beforeProofActions));
1251
+ });
1252
+ after(async () => {
1253
+ walletRegistry.seize.reset();
1254
+ await restoreSnapshot();
1255
+ });
1256
+ it("should hold the timed out requests in the contract state", async () => {
1257
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1258
+ const redemptionRequest =
1259
+ // eslint-disable-next-line no-await-in-loop
1260
+ await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1261
+ .redeemerOutputScript));
1262
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.greaterThan(0, `Timed out request with index ${i} was removed from the contract state`);
1263
+ }
1264
+ });
1265
+ it("should update the wallet's main UTXO", async () => {
1266
+ // Change index and value can be taken by exploring
1267
+ // the redemption transaction structure and getting
1268
+ // the output pointing back to wallet PKH.
1269
+ const expectedMainUtxoHash = buildMainUtxoHash(data.redemptionTx.hash, 5, 137130866);
1270
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1271
+ .mainUtxoHash).to.be.equal(expectedMainUtxoHash);
1272
+ });
1273
+ it("should mark the previous main UTXO as spent", async () => {
1274
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1275
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1276
+ .true;
1277
+ });
1278
+ it("should not change the wallet's pending redemptions value", async () => {
1279
+ // All the bookkeeping regarding the timed out
1280
+ // requests was done upon timeout reports. The
1281
+ // wallet pending redemptions value should not
1282
+ // be changed in any way.
1283
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(0);
1284
+ });
1285
+ it("should not change Bridge's balance in Bank", async () => {
1286
+ // All the bookkeeping regarding the timed out
1287
+ // requests was done upon timeout reports. The Bridge
1288
+ // balance in the bank should neither be decreased...
1289
+ await (0, chai_1.expect)(tx)
1290
+ .to.emit(bank, "BalanceDecreased")
1291
+ .withArgs(bridge.address, 0);
1292
+ // ...nor changed in any other way.
1293
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(0);
1294
+ });
1295
+ it("should not transfer anything to the treasury", async () => {
1296
+ // Treasury balance should not be increased in any way
1297
+ // since all requests handled by the redemption
1298
+ // transaction are reported as timed out and are just
1299
+ // skipped during processing.
1300
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
1301
+ });
1302
+ it("should not change redeemers balances in any way", async () => {
1303
+ // Redemption proof submission should NEVER cause
1304
+ // any change of the redeemers balances.
1305
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1306
+ const redeemerBalance = redeemersBalances[i];
1307
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1308
+ }
1309
+ });
1310
+ });
1311
+ context("when output vector consists of pending requested redemptions and reported timed out requested redemptions", () => {
1312
+ const data = redemption_1.MultiplePendingRequestedRedemptions;
1313
+ let tx;
1314
+ let bridgeBalance;
1315
+ let walletPendingRedemptionsValue;
1316
+ let treasuryBalance;
1317
+ let redeemersBalances;
1318
+ before(async () => {
1319
+ await createSnapshot();
1320
+ // Simulate the situation when treasury fee is 0% to
1321
+ // allow using the whole wallet's main UTXO value
1322
+ // to fulfill the redemption requests.
1323
+ await bridge.setRedemptionTreasuryFeeDivisor(0);
1324
+ // Before submitting the redemption proof, wait
1325
+ // an amount of time that will make the requests
1326
+ // timed out but report timeout only the two first
1327
+ // requests.
1328
+ const beforeProofActions = async () => {
1329
+ await increaseTime(redemptionTimeout);
1330
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
1331
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[1].redeemerOutputScript);
1332
+ };
1333
+ ({
1334
+ tx,
1335
+ bridgeBalance,
1336
+ walletPendingRedemptionsValue,
1337
+ treasuryBalance,
1338
+ redeemersBalances,
1339
+ } = await runRedemptionScenario(data, beforeProofActions));
1340
+ });
1341
+ after(async () => {
1342
+ walletRegistry.seize.reset();
1343
+ await restoreSnapshot();
1344
+ });
1345
+ it("should hold the timed out requests in the contract state", async () => {
1346
+ // Check the two first requests reported as timed out
1347
+ // are actually held in the contract state after
1348
+ // proof submission.
1349
+ for (let i = 0; i < 2; i++) {
1350
+ const redemptionRequest =
1351
+ // eslint-disable-next-line no-await-in-loop
1352
+ await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1353
+ .redeemerOutputScript));
1354
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.greaterThan(0, `Timed out request with index ${i} was removed from the contract state`);
1355
+ }
1356
+ });
1357
+ it("should close processed redemption requests", async () => {
1358
+ // Check the remaining requests not reported as
1359
+ // timed out were actually closed after proof
1360
+ // submission.
1361
+ for (let i = 2; i < data.redemptionRequests.length; i++) {
1362
+ const redemptionRequest =
1363
+ // eslint-disable-next-line no-await-in-loop
1364
+ await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1365
+ .redeemerOutputScript));
1366
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, `Redemption request with index ${i} has not been closed`);
1367
+ }
1368
+ });
1369
+ it("should delete the wallet's main UTXO", async () => {
1370
+ // The Bitcoin redemption transaction has no change
1371
+ // output so the wallet's main UTXO should be
1372
+ // deleted on the Bridge side.
1373
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1374
+ .mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
1375
+ });
1376
+ it("should mark the previous main UTXO as spent", async () => {
1377
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1378
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1379
+ .true;
1380
+ });
1381
+ it("should decrease the wallet's pending redemptions value", async () => {
1382
+ // Wallet pending redemptions value should be
1383
+ // decreased by the total redeemable amount but since
1384
+ // the treasury fee is 0% in this test case, the
1385
+ // total redeemable amount is equal to the total
1386
+ // requested amount. However, only pending
1387
+ // requests are taken into account and all reported
1388
+ // timeouts should be ignored because the appropriate
1389
+ // bookkeeping was already made upon timeout reports.
1390
+ // See docs of the used test data for details.
1391
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-575907);
1392
+ });
1393
+ it("should decrease Bridge's balance in Bank", async () => {
1394
+ // Balance should be decreased by the total
1395
+ // redeemable amount but since the treasury fee
1396
+ // is 0% in this test case, the total redeemable
1397
+ // amount is equal to the total requested amount.
1398
+ // However, only pending requests are taken into
1399
+ // account and all reported timeouts should be
1400
+ // ignored because the appropriate bookkeeping was
1401
+ // already made upon timeout reports. See docs of the
1402
+ // used test data for details.
1403
+ await (0, chai_1.expect)(tx)
1404
+ .to.emit(bank, "BalanceDecreased")
1405
+ .withArgs(bridge.address, 575907);
1406
+ // In this case, the total Bridge balance change
1407
+ // should be also equal to the same amount.
1408
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-575907);
1409
+ });
1410
+ it("should not transfer anything to the treasury", async () => {
1411
+ // Treasury balance should not be increased because
1412
+ // the treasury fee is 0% in this test case.
1413
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(0);
1414
+ });
1415
+ it("should not change redeemers balances in any way", async () => {
1416
+ // Redemption proof submission should NEVER cause
1417
+ // any change of the redeemers balances.
1418
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1419
+ const redeemerBalance = redeemersBalances[i];
1420
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1421
+ }
1422
+ });
1423
+ });
1424
+ context("when output vector consists of pending requested redemptions, reported timed out requested redemptions and a non-zero change", () => {
1425
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1426
+ let tx;
1427
+ let bridgeBalance;
1428
+ let walletPendingRedemptionsValue;
1429
+ let treasuryBalance;
1430
+ let redeemersBalances;
1431
+ before(async () => {
1432
+ await createSnapshot();
1433
+ // Before submitting the redemption proof, wait
1434
+ // an amount of time that will make the requests
1435
+ // timed out but report timeout only the two first
1436
+ // requests.
1437
+ const beforeProofActions = async () => {
1438
+ await increaseTime(redemptionTimeout);
1439
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
1440
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[1].redeemerOutputScript);
1441
+ };
1442
+ ({
1443
+ tx,
1444
+ bridgeBalance,
1445
+ walletPendingRedemptionsValue,
1446
+ treasuryBalance,
1447
+ redeemersBalances,
1448
+ } = await runRedemptionScenario(data, beforeProofActions));
1449
+ });
1450
+ after(async () => {
1451
+ walletRegistry.seize.reset();
1452
+ await restoreSnapshot();
1453
+ });
1454
+ it("should hold the timed out requests in the contract state", async () => {
1455
+ // Check the two first requests reported as timed out
1456
+ // are actually held in the contract state after
1457
+ // proof submission.
1458
+ for (let i = 0; i < 2; i++) {
1459
+ const redemptionRequest =
1460
+ // eslint-disable-next-line no-await-in-loop
1461
+ await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1462
+ .redeemerOutputScript));
1463
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.greaterThan(0, `Timed out request with index ${i} was removed from the contract state`);
1464
+ }
1465
+ });
1466
+ it("should close processed redemption requests", async () => {
1467
+ // Check the remaining requests not reported as
1468
+ // timed out were actually closed after proof
1469
+ // submission.
1470
+ for (let i = 2; i < data.redemptionRequests.length; i++) {
1471
+ const redemptionRequest =
1472
+ // eslint-disable-next-line no-await-in-loop
1473
+ await bridge.pendingRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[i]
1474
+ .redeemerOutputScript));
1475
+ (0, chai_1.expect)(redemptionRequest.requestedAt).to.be.equal(0, `Redemption request with index ${i} has not been closed`);
1476
+ }
1477
+ });
1478
+ it("should update the wallet's main UTXO", async () => {
1479
+ // Change index and value can be taken by exploring
1480
+ // the redemption transaction structure and getting
1481
+ // the output pointing back to wallet PKH.
1482
+ const expectedMainUtxoHash = buildMainUtxoHash(data.redemptionTx.hash, 5, 137130866);
1483
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash))
1484
+ .mainUtxoHash).to.be.equal(expectedMainUtxoHash);
1485
+ });
1486
+ it("should mark the previous main UTXO as spent", async () => {
1487
+ const mainUtxoKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [data.mainUtxo.txHash, data.mainUtxo.txOutputIndex]);
1488
+ (0, chai_1.expect)(await bridge.spentMainUTXOs(mainUtxoKey)).to.be
1489
+ .true;
1490
+ });
1491
+ it("should decrease the wallet's pending redemptions value", async () => {
1492
+ // Wallet pending redemptions value should be
1493
+ // decreased by the total redeemable amount. However,
1494
+ // only pending requests are taken into account and
1495
+ // all reported timeouts should be ignored because
1496
+ // the appropriate bookkeeping was already made upon
1497
+ // timeout reports. See docs of the used test data
1498
+ // for details.
1499
+ (0, chai_1.expect)(walletPendingRedemptionsValue.afterProof.sub(walletPendingRedemptionsValue.beforeProof)).to.equal(-4433350);
1500
+ });
1501
+ it("should decrease Bridge's balance in Bank", async () => {
1502
+ // Balance should be decreased by the total
1503
+ // redeemable amount. However, only pending requests
1504
+ // are taken into account and all reported timeouts
1505
+ // should be ignored because the appropriate
1506
+ // bookkeeping was already made upon timeout reports.
1507
+ // See docs of the used test data for details.
1508
+ await (0, chai_1.expect)(tx)
1509
+ .to.emit(bank, "BalanceDecreased")
1510
+ .withArgs(bridge.address, 4433350);
1511
+ // However, the total balance change of the
1512
+ // Bridge should also consider the treasury
1513
+ // fee collected upon requests and transferred
1514
+ // to the treasury at the end of the proof.
1515
+ // This is why the total Bridge's balance change
1516
+ // is equal to the total requested amount for
1517
+ // all requests (without taking the reported timed
1518
+ // out ones into account). See docs of the used test
1519
+ // data for details.
1520
+ (0, chai_1.expect)(bridgeBalance.afterProof.sub(bridgeBalance.beforeProof)).to.equal(-4435567);
1521
+ });
1522
+ it("should transfer collected treasury fee", async () => {
1523
+ // Treasury balance should be increased by the total
1524
+ // treasury fee for all requests. However, only
1525
+ // pending requests are taken into account and all
1526
+ // reported timeouts should be ignored because the
1527
+ // appropriate bookkeeping was already made upon
1528
+ // timeout reports. See docs of the used test data
1529
+ // for details.
1530
+ (0, chai_1.expect)(treasuryBalance.afterProof.sub(treasuryBalance.beforeProof)).to.equal(2217);
1531
+ });
1532
+ it("should not change redeemers balances in any way", async () => {
1533
+ // Redemption proof submission should NEVER cause
1534
+ // any change of the redeemers balances.
1535
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
1536
+ const redeemerBalance = redeemersBalances[i];
1537
+ (0, chai_1.expect)(redeemerBalance.afterProof.sub(redeemerBalance.beforeProof)).to.be.equal(0, `Balance of redeemer with index ${i} has changed`);
1538
+ }
1539
+ });
1540
+ });
1541
+ context("when output vector contains a pending requested redemption with wrong amount redeemed", () => {
1542
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptions));
1543
+ let outcome;
1544
+ before(async () => {
1545
+ await createSnapshot();
1546
+ // Alter the last redemption requests in the test
1547
+ // data and set such an amount that will cause
1548
+ // the Bitcoin redemption transaction to be deemed
1549
+ // as invalid due to a wrong amount. The corresponding
1550
+ // transaction output has the value of 191169 so to
1551
+ // make this test scenario happen, the request amount
1552
+ // must be way different (lesser or greater) than the
1553
+ // output value. Worth noting that this test scenario
1554
+ // tests the amount condition in a general and simplified
1555
+ // way without stressing all specific edge cases.
1556
+ // Doing a detailed check would require more dedicated
1557
+ // test data sets which would make it far more
1558
+ // complicated without giving much value in return.
1559
+ data.redemptionRequests[4].amount = 100000;
1560
+ outcome = runRedemptionScenario(data);
1561
+ });
1562
+ after(async () => {
1563
+ await restoreSnapshot();
1564
+ });
1565
+ it("should revert", async () => {
1566
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output value is not within the acceptable range of the pending request");
1567
+ });
1568
+ });
1569
+ context("when output vector contains a reported timed out requested redemption with wrong amount redeemed", () => {
1570
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptions));
1571
+ let outcome;
1572
+ before(async () => {
1573
+ await createSnapshot();
1574
+ // Alter the last redemption requests in the test
1575
+ // data and set such an amount that will cause
1576
+ // the Bitcoin redemption transaction to be deemed
1577
+ // as invalid due to a wrong amount. The corresponding
1578
+ // transaction output has the value of 191169 so to
1579
+ // make this test scenario happen, the request amount
1580
+ // must be way different (lesser or greater) than the
1581
+ // output value. Worth noting that this test scenario
1582
+ // tests the amount condition in a general and simplified
1583
+ // way without stressing all specific edge cases.
1584
+ // Doing a detailed check would require more dedicated
1585
+ // test data sets which would make it far more
1586
+ // complicated without giving much value in return.
1587
+ data.redemptionRequests[4].amount = 100000;
1588
+ // Before submitting the redemption proof, wait
1589
+ // an amount of time that will make the last request
1590
+ // timed out and then report the timeout.
1591
+ const beforeProofActions = async () => {
1592
+ await increaseTime(redemptionTimeout);
1593
+ await bridge.notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[4].redeemerOutputScript);
1594
+ };
1595
+ outcome = runRedemptionScenario(data, beforeProofActions);
1596
+ });
1597
+ after(async () => {
1598
+ walletRegistry.seize.reset();
1599
+ await restoreSnapshot();
1600
+ });
1601
+ it("should revert", async () => {
1602
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output value is not within the acceptable range of the timed out request");
1603
+ });
1604
+ });
1605
+ context("when output vector contains a non-zero P2SH change output", () => {
1606
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithP2SHChange));
1607
+ let outcome;
1608
+ before(async () => {
1609
+ await createSnapshot();
1610
+ outcome = runRedemptionScenario(data);
1611
+ });
1612
+ after(async () => {
1613
+ await restoreSnapshot();
1614
+ });
1615
+ // We have this case because P2SH script has a 20-byte
1616
+ // payload which may match the 20-byte wallet public
1617
+ // key hash though it should be always rejected as
1618
+ // non-requested output. There is no need to check for
1619
+ // P2WSH since the payload is always 32-byte there.
1620
+ it("should revert", async () => {
1621
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
1622
+ });
1623
+ });
1624
+ context("when output vector contains multiple non-zero change outputs", () => {
1625
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithMultipleP2WPKHChanges));
1626
+ let outcome;
1627
+ before(async () => {
1628
+ await createSnapshot();
1629
+ outcome = runRedemptionScenario(data);
1630
+ });
1631
+ after(async () => {
1632
+ await restoreSnapshot();
1633
+ });
1634
+ it("should revert", async () => {
1635
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
1636
+ });
1637
+ });
1638
+ context("when output vector contains one change but with zero as value", () => {
1639
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChangeZeroValue));
1640
+ let outcome;
1641
+ before(async () => {
1642
+ await createSnapshot();
1643
+ outcome = runRedemptionScenario(data);
1644
+ });
1645
+ after(async () => {
1646
+ await restoreSnapshot();
1647
+ });
1648
+ it("should revert", async () => {
1649
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
1650
+ });
1651
+ });
1652
+ context("when output vector contains a non-requested redemption to an arbitrary script hash", () => {
1653
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithNonRequestedRedemption));
1654
+ let outcome;
1655
+ before(async () => {
1656
+ await createSnapshot();
1657
+ outcome = runRedemptionScenario(data);
1658
+ });
1659
+ after(async () => {
1660
+ await restoreSnapshot();
1661
+ });
1662
+ it("should revert", async () => {
1663
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
1664
+ });
1665
+ });
1666
+ context("when output vector contains a provably unspendable OP_RETURN output", () => {
1667
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithProvablyUnspendable));
1668
+ let outcome;
1669
+ before(async () => {
1670
+ await createSnapshot();
1671
+ outcome = runRedemptionScenario(data);
1672
+ });
1673
+ after(async () => {
1674
+ await restoreSnapshot();
1675
+ });
1676
+ it("should revert", async () => {
1677
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Output is a non-requested redemption");
1678
+ });
1679
+ });
1680
+ });
1681
+ });
1682
+ context("when wallet state is MovingFunds", () => {
1683
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1684
+ let outcome;
1685
+ before(async () => {
1686
+ await createSnapshot();
1687
+ // Set wallet state to MovingFunds. That must be done
1688
+ // just before proof submission since requests should
1689
+ // be made against a Live wallet.
1690
+ const beforeProofActions = async () => {
1691
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
1692
+ await bridge.setWallet(data.wallet.pubKeyHash, {
1693
+ ...wallet,
1694
+ state: fixtures_1.walletState.MovingFunds,
1695
+ });
1696
+ };
1697
+ outcome = runRedemptionScenario(data, beforeProofActions);
1698
+ });
1699
+ after(async () => {
1700
+ await restoreSnapshot();
1701
+ });
1702
+ // Just assert it passes without revert without repeating
1703
+ // checks from Live state scenario.
1704
+ it("should succeed", async () => {
1705
+ await (0, chai_1.expect)(outcome).to.not.be.reverted;
1706
+ });
1707
+ });
1708
+ context("when wallet state is neither Live nor MovingFunds", () => {
1709
+ const testData = [
1710
+ {
1711
+ testName: "when wallet state is Unknown",
1712
+ walletState: fixtures_1.walletState.Unknown,
1713
+ },
1714
+ {
1715
+ testName: "when wallet state is Closing",
1716
+ walletState: fixtures_1.walletState.Closing,
1717
+ },
1718
+ {
1719
+ testName: "when wallet state is Closed",
1720
+ walletState: fixtures_1.walletState.Closed,
1721
+ },
1722
+ {
1723
+ testName: "when wallet state is Terminated",
1724
+ walletState: fixtures_1.walletState.Terminated,
1725
+ },
1726
+ ];
1727
+ testData.forEach((test) => {
1728
+ context(test.testName, () => {
1729
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1730
+ let outcome;
1731
+ before(async () => {
1732
+ await createSnapshot();
1733
+ // Set wallet state to Unknown. That must be done
1734
+ // just before proof submission since requests should
1735
+ // be made against a Live wallet.
1736
+ const beforeProofActions = async () => {
1737
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
1738
+ await bridge.setWallet(data.wallet.pubKeyHash, {
1739
+ ...wallet,
1740
+ state: test.walletState,
1741
+ });
1742
+ };
1743
+ outcome = runRedemptionScenario(data, beforeProofActions);
1744
+ });
1745
+ after(async () => {
1746
+ await restoreSnapshot();
1747
+ });
1748
+ it("should revert", async () => {
1749
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("'Wallet must be in Live or MovingFunds state");
1750
+ });
1751
+ });
1752
+ });
1753
+ });
1754
+ });
1755
+ context("when the single input doesn't point to the wallet's main UTXO", () => {
1756
+ const data = JSON.parse(JSON.stringify(redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange));
1757
+ let outcome;
1758
+ before(async () => {
1759
+ await createSnapshot();
1760
+ // Corrupt the wallet's main UTXO that is injected to
1761
+ // the Bridge state by the test runner in order to make it
1762
+ // different than the input used by the actual Bitcoin
1763
+ // transaction thus make the tested scenario happen. The
1764
+ // proper value of `txOutputIndex` is `5` so any other value
1765
+ // will do the trick.
1766
+ data.mainUtxo.txOutputIndex = 10;
1767
+ outcome = runRedemptionScenario(data);
1768
+ });
1769
+ after(async () => {
1770
+ await restoreSnapshot();
1771
+ });
1772
+ it("should revert", async () => {
1773
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Outbound transaction input must point to the wallet's main UTXO");
1774
+ });
1775
+ });
1776
+ });
1777
+ context("when input count is other than one", () => {
1778
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithMultipleInputs;
1779
+ let outcome;
1780
+ before(async () => {
1781
+ await createSnapshot();
1782
+ outcome = runRedemptionScenario(data);
1783
+ });
1784
+ after(async () => {
1785
+ await restoreSnapshot();
1786
+ });
1787
+ it("should revert", async () => {
1788
+ await (0, chai_1.expect)(outcome).to.be.revertedWith("Outbound transaction must have a single input");
1789
+ });
1790
+ });
1791
+ });
1792
+ context("when main UTXO data are invalid", () => {
1793
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1794
+ before(async () => {
1795
+ await createSnapshot();
1796
+ // Required for a successful SPV proof.
1797
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1798
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1799
+ // Wallet main UTXO must be set on the Bridge side to make
1800
+ // that scenario happen.
1801
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
1802
+ });
1803
+ after(async () => {
1804
+ await restoreSnapshot();
1805
+ });
1806
+ it("should revert", async () => {
1807
+ // Corrupt the main UTXO parameter passed during
1808
+ // `submitRedemptionProof` call. The proper value of
1809
+ // `txOutputIndex` for this test data set is `5` so any other
1810
+ // value will make this test scenario happen.
1811
+ const corruptedMainUtxo = {
1812
+ ...data.mainUtxo,
1813
+ txOutputIndex: 10,
1814
+ };
1815
+ await (0, chai_1.expect)(bridge.submitRedemptionProof(data.redemptionTx, data.redemptionProof, corruptedMainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("Invalid main UTXO data");
1816
+ });
1817
+ });
1818
+ });
1819
+ context("when there is no main UTXO for the given wallet", () => {
1820
+ const data = redemption_1.MultiplePendingRequestedRedemptionsWithP2WPKHChange;
1821
+ before(async () => {
1822
+ await createSnapshot();
1823
+ // Required for a successful SPV proof.
1824
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1825
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1826
+ });
1827
+ after(async () => {
1828
+ await restoreSnapshot();
1829
+ });
1830
+ it("should revert", async () => {
1831
+ // There was no preparations before `submitRedemptionProof` call
1832
+ // so no main UTXO is set for the given wallet.
1833
+ await (0, chai_1.expect)(bridge.submitRedemptionProof(data.redemptionTx, data.redemptionProof, data.mainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("No main UTXO for given wallet");
1834
+ });
1835
+ });
1836
+ });
1837
+ context("when transaction proof is not valid", () => {
1838
+ context("when input vector is not valid", () => {
1839
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1840
+ before(async () => {
1841
+ await createSnapshot();
1842
+ });
1843
+ after(async () => {
1844
+ await restoreSnapshot();
1845
+ });
1846
+ it("should revert", async () => {
1847
+ // Corrupt the input vector by setting a compactSize uint claiming
1848
+ // there is no inputs at all.
1849
+ data.redemptionTx.inputVector =
1850
+ "0x00b69a2869840aa6fdfd143136ff4514ca46ea2d876855040892ad74ab" +
1851
+ "8c5274220100000000ffffffff";
1852
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Invalid input vector provided");
1853
+ });
1854
+ });
1855
+ context("when output vector is not valid", () => {
1856
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1857
+ before(async () => {
1858
+ await createSnapshot();
1859
+ });
1860
+ after(async () => {
1861
+ await restoreSnapshot();
1862
+ });
1863
+ it("should revert", async () => {
1864
+ // Corrupt the output vector by setting a compactSize uint claiming
1865
+ // there is no outputs at all.
1866
+ data.redemptionTx.outputVector =
1867
+ "0x005cf511000000000017a91486884e6be1525dab5ae0b451bd2c72cee6" +
1868
+ "7dcf4187";
1869
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Invalid output vector provided");
1870
+ });
1871
+ });
1872
+ context("when merkle proof is not valid", () => {
1873
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1874
+ before(async () => {
1875
+ await createSnapshot();
1876
+ });
1877
+ after(async () => {
1878
+ await restoreSnapshot();
1879
+ });
1880
+ it("should revert", async () => {
1881
+ // Corrupt the merkle proof by changing tx index in block to an
1882
+ // invalid one. The proper one is 33 so any other will do the trick.
1883
+ data.redemptionProof.txIndexInBlock = 30;
1884
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Tx merkle proof is not valid for provided header and tx hash");
1885
+ });
1886
+ });
1887
+ context("when proof difficulty is not current nor previous", () => {
1888
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1889
+ before(async () => {
1890
+ await createSnapshot();
1891
+ });
1892
+ after(async () => {
1893
+ await restoreSnapshot();
1894
+ });
1895
+ it("should revert", async () => {
1896
+ // To pass the proof validation, the difficulty returned by the relay
1897
+ // must be 1 for test data used in this scenario. Setting
1898
+ // a different value will cause difficulty comparison failure.
1899
+ data.chainDifficulty = 2;
1900
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Not at current or previous difficulty");
1901
+ });
1902
+ });
1903
+ context("when headers chain length is not valid", () => {
1904
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1905
+ before(async () => {
1906
+ await createSnapshot();
1907
+ });
1908
+ after(async () => {
1909
+ await restoreSnapshot();
1910
+ });
1911
+ it("should revert", async () => {
1912
+ // Corrupt the bitcoin headers length in the redemption proof. The
1913
+ // proper value is length divisible by 80 so any length violating
1914
+ // this rule will cause failure. In this case, we just remove the
1915
+ // last byte from proper headers chain.
1916
+ const properHeaders = data.redemptionProof.bitcoinHeaders.toString();
1917
+ data.redemptionProof.bitcoinHeaders = properHeaders.substring(0, properHeaders.length - 2);
1918
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Invalid length of the headers chain");
1919
+ });
1920
+ });
1921
+ context("when headers chain is not valid", () => {
1922
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1923
+ before(async () => {
1924
+ await createSnapshot();
1925
+ });
1926
+ after(async () => {
1927
+ await restoreSnapshot();
1928
+ });
1929
+ it("should revert", async () => {
1930
+ // Bitcoin headers must form a chain to pass the proof validation.
1931
+ // That means the `previous block hash` encoded in the given block
1932
+ // header must match the actual previous header's hash. To test
1933
+ // that scenario, we corrupt the `previous block hash` of the
1934
+ // second header. Each header is 80 bytes length. First 4 bytes
1935
+ // of each header is `version` and 32 subsequent bytes is
1936
+ // `previous block hash`. Changing byte 85 of the whole chain will
1937
+ // do the work.
1938
+ const properHeaders = data.redemptionProof.bitcoinHeaders.toString();
1939
+ data.redemptionProof.bitcoinHeaders = `${properHeaders.substring(0, 170)}ff${properHeaders.substring(172)}`;
1940
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Invalid headers chain");
1941
+ });
1942
+ });
1943
+ context("when the work in the header is insufficient", () => {
1944
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1945
+ before(async () => {
1946
+ await createSnapshot();
1947
+ });
1948
+ after(async () => {
1949
+ await restoreSnapshot();
1950
+ });
1951
+ it("should revert", async () => {
1952
+ // Each header encodes a `difficulty target` field in bytes 72-76.
1953
+ // The given header's hash (interpreted as uint) must be bigger than
1954
+ // the `difficulty target`. To test this scenario, we change the
1955
+ // last byte of the last header in such a way their hash becomes
1956
+ // lower than their `difficulty target`.
1957
+ const properHeaders = data.redemptionProof.bitcoinHeaders.toString();
1958
+ data.redemptionProof.bitcoinHeaders = `${properHeaders.substring(0, properHeaders.length - 2)}ff`;
1959
+ await (0, chai_1.expect)(runRedemptionScenario(data)).to.be.revertedWith("Insufficient work in a header");
1960
+ });
1961
+ });
1962
+ context("when accumulated difficulty in headers chain is insufficient", () => {
1963
+ let otherBridge;
1964
+ const data = JSON.parse(JSON.stringify(redemption_1.SinglePendingRequestedRedemption));
1965
+ before(async () => {
1966
+ await createSnapshot();
1967
+ // Necessary to pass the first part of proof validation.
1968
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
1969
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
1970
+ // Deploy another bridge which has higher `txProofDifficultyFactor`
1971
+ // than the original bridge. That means it will need 12 confirmations
1972
+ // to deem transaction proof validity. This scenario uses test
1973
+ // data which has only 6 confirmations. That should force the
1974
+ // failure we expect within this scenario.
1975
+ otherBridge = await BridgeFactory.deploy();
1976
+ await otherBridge.initialize(bank.address, relay.address, treasury.address, walletRegistry.address, 12);
1977
+ });
1978
+ after(async () => {
1979
+ await restoreSnapshot();
1980
+ });
1981
+ it("should revert", async () => {
1982
+ await (0, chai_1.expect)(otherBridge.submitRedemptionProof(data.redemptionTx, data.redemptionProof, data.mainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("Insufficient accumulated difficulty in header chain");
1983
+ });
1984
+ });
1985
+ });
1986
+ });
1987
+ describe("notifyRedemptionTimeout", () => {
1988
+ context("when redemption request exists", () => {
1989
+ context("when the redemption request has timed out", () => {
1990
+ context("when the wallet is in Live state", () => {
1991
+ context("when the wallet is the active wallet", () => {
1992
+ const data = redemption_1.SinglePendingRequestedRedemption;
1993
+ let tx;
1994
+ let initialPendingRedemptionsValue;
1995
+ let initialRedeemerBalance;
1996
+ let redemptionRequest;
1997
+ const walletMembersIDs = [1, 2, 3, 4, 5];
1998
+ before(async () => {
1999
+ await createSnapshot();
2000
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2001
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2002
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2003
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2004
+ createdAt: await lastBlockTime(),
2005
+ movingFundsRequestedAt: 0,
2006
+ closingStartedAt: 0,
2007
+ pendingMovedFundsSweepRequestsCount: 0,
2008
+ state: fixtures_1.walletState.Live,
2009
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2010
+ });
2011
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2012
+ await bridge.setActiveWallet(data.wallet.pubKeyHash);
2013
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2014
+ from: governance,
2015
+ value: 10,
2016
+ });
2017
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2018
+ await bridge
2019
+ .connect(redeemerSigner)
2020
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2021
+ await increaseTime(redemptionTimeout);
2022
+ initialPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2023
+ initialRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2024
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2025
+ redemptionRequest = await bridge.pendingRedemptions(redemptionKey);
2026
+ tx = await bridge
2027
+ .connect(thirdParty)
2028
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, walletMembersIDs, data.redemptionRequests[0].redeemerOutputScript);
2029
+ });
2030
+ after(async () => {
2031
+ walletRegistry.seize.reset();
2032
+ await restoreSnapshot();
2033
+ });
2034
+ it("should update the wallet's pending redemptions value", async () => {
2035
+ const expectedPendingRedemptionsValue = initialPendingRedemptionsValue
2036
+ .sub(data.redemptionRequests[0].amount)
2037
+ .add(redemptionRequest.treasuryFee);
2038
+ const currentPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2039
+ (0, chai_1.expect)(currentPendingRedemptionsValue).to.be.equal(expectedPendingRedemptionsValue);
2040
+ });
2041
+ it("should return the requested amount of tokens to the redeemer", async () => {
2042
+ const expectedRedeemerBalance = initialRedeemerBalance.add(data.redemptionRequests[0].amount);
2043
+ const currentRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2044
+ (0, chai_1.expect)(currentRedeemerBalance).to.be.equal(expectedRedeemerBalance);
2045
+ });
2046
+ it("should remove the request from the pending redemptions", async () => {
2047
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2048
+ const request = await bridge.pendingRedemptions(redemptionKey);
2049
+ (0, chai_1.expect)(request.requestedAt).to.be.equal(0);
2050
+ });
2051
+ it("should add the request to the timed-out redemptions", async () => {
2052
+ const timedOutRequest = await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
2053
+ (0, chai_1.expect)(timedOutRequest.redeemer).to.be.equal(data.redemptionRequests[0].redeemer);
2054
+ (0, chai_1.expect)(timedOutRequest.requestedAmount).to.be.equal(redemptionRequest.requestedAmount);
2055
+ (0, chai_1.expect)(timedOutRequest.treasuryFee).to.be.equal(redemptionRequest.treasuryFee);
2056
+ (0, chai_1.expect)(timedOutRequest.txMaxFee).to.be.equal(redemptionRequest.txMaxFee);
2057
+ (0, chai_1.expect)(timedOutRequest.requestedAt).to.be.equal(redemptionRequest.requestedAt);
2058
+ });
2059
+ it("should change the wallet's state to MovingFunds", async () => {
2060
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
2061
+ (0, chai_1.expect)(wallet.state).to.be.equal(fixtures_1.walletState.MovingFunds);
2062
+ });
2063
+ it("should set the wallet's move funds requested timestamp", async () => {
2064
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
2065
+ (0, chai_1.expect)(wallet.movingFundsRequestedAt).to.be.equal(await lastBlockTime());
2066
+ });
2067
+ it("should emit WalletMovingFunds event", async () => {
2068
+ await (0, chai_1.expect)(tx)
2069
+ .to.emit(bridge, "WalletMovingFunds")
2070
+ .withArgs(data.wallet.ecdsaWalletID, data.wallet.pubKeyHash);
2071
+ });
2072
+ it("should delete the active wallet public key hash", async () => {
2073
+ (0, chai_1.expect)(await bridge.activeWalletPubKeyHash()).to.be.equal("0x0000000000000000000000000000000000000000");
2074
+ });
2075
+ it("should call the ECDSA wallet registry's seize function", async () => {
2076
+ (0, chai_1.expect)(walletRegistry.seize).to.have.been.calledOnceWith(redemptionTimeoutSlashingAmount, redemptionTimeoutNotifierRewardMultiplier, await thirdParty.getAddress(), data.wallet.ecdsaWalletID, walletMembersIDs);
2077
+ });
2078
+ it("should emit RedemptionTimedOut event", async () => {
2079
+ await (0, chai_1.expect)(tx)
2080
+ .to.emit(bridge, "RedemptionTimedOut")
2081
+ .withArgs(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2082
+ });
2083
+ it("should decrease the live wallets counter", async () => {
2084
+ (0, chai_1.expect)(await bridge.liveWalletsCount()).to.be.equal(0);
2085
+ });
2086
+ });
2087
+ context("when the wallet is not the active wallet", () => {
2088
+ const data = redemption_1.SinglePendingRequestedRedemption;
2089
+ // Public key hash of a different wallet
2090
+ const anotherWalletPublicKeyHash = "0x123456789abcdef01234567891abcdef01234567";
2091
+ before(async () => {
2092
+ await createSnapshot();
2093
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2094
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2095
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2096
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2097
+ createdAt: await lastBlockTime(),
2098
+ movingFundsRequestedAt: 0,
2099
+ closingStartedAt: 0,
2100
+ pendingMovedFundsSweepRequestsCount: 0,
2101
+ state: fixtures_1.walletState.Live,
2102
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2103
+ });
2104
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2105
+ await bridge.setActiveWallet(anotherWalletPublicKeyHash);
2106
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2107
+ from: governance,
2108
+ value: 10,
2109
+ });
2110
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2111
+ await bridge
2112
+ .connect(redeemerSigner)
2113
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2114
+ await increaseTime(redemptionTimeout);
2115
+ await bridge
2116
+ .connect(thirdParty)
2117
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
2118
+ });
2119
+ after(async () => {
2120
+ walletRegistry.seize.reset();
2121
+ await restoreSnapshot();
2122
+ });
2123
+ // Only verify the active wallet public key hash has not changed.
2124
+ // The other checks are covered in scenarios where the wallet was
2125
+ // the active wallet and they should not be repeated here.
2126
+ it("should not delete the active wallet public key hash", async () => {
2127
+ // Check that the active wallet has not changed
2128
+ (0, chai_1.expect)(await bridge.activeWalletPubKeyHash()).to.be.equal(anotherWalletPublicKeyHash);
2129
+ });
2130
+ });
2131
+ });
2132
+ context("when the wallet is in MovingFunds state", () => {
2133
+ const data = redemption_1.SinglePendingRequestedRedemption;
2134
+ let tx;
2135
+ let initialPendingRedemptionsValue;
2136
+ let initialRedeemerBalance;
2137
+ let redemptionRequest;
2138
+ const walletMembersIDs = [1, 2, 3, 4, 5];
2139
+ before(async () => {
2140
+ await createSnapshot();
2141
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2142
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2143
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2144
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2145
+ createdAt: await lastBlockTime(),
2146
+ movingFundsRequestedAt: 0,
2147
+ closingStartedAt: 0,
2148
+ pendingMovedFundsSweepRequestsCount: 0,
2149
+ // Initially set the state to Live, so that the redemption
2150
+ // request can be made
2151
+ state: fixtures_1.walletState.Live,
2152
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2153
+ });
2154
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2155
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2156
+ from: governance,
2157
+ value: 10,
2158
+ });
2159
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2160
+ await bridge
2161
+ .connect(redeemerSigner)
2162
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2163
+ // Simulate the wallet's state has changed to MovingFunds
2164
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
2165
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2166
+ ecdsaWalletID: wallet.ecdsaWalletID,
2167
+ mainUtxoHash: wallet.mainUtxoHash,
2168
+ pendingRedemptionsValue: wallet.pendingRedemptionsValue,
2169
+ createdAt: wallet.createdAt,
2170
+ movingFundsRequestedAt: wallet.movingFundsRequestedAt,
2171
+ closingStartedAt: wallet.closingStartedAt,
2172
+ pendingMovedFundsSweepRequestsCount: wallet.pendingMovedFundsSweepRequestsCount,
2173
+ state: fixtures_1.walletState.MovingFunds,
2174
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2175
+ });
2176
+ await increaseTime(redemptionTimeout);
2177
+ initialPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2178
+ initialRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2179
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2180
+ redemptionRequest = await bridge.pendingRedemptions(redemptionKey);
2181
+ tx = await bridge
2182
+ .connect(thirdParty)
2183
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, walletMembersIDs, data.redemptionRequests[0].redeemerOutputScript);
2184
+ });
2185
+ after(async () => {
2186
+ walletRegistry.seize.reset();
2187
+ await restoreSnapshot();
2188
+ });
2189
+ it("should update the wallet's pending redemptions value", async () => {
2190
+ const expectedPendingRedemptionsValue = initialPendingRedemptionsValue
2191
+ .sub(data.redemptionRequests[0].amount)
2192
+ .add(redemptionRequest.treasuryFee);
2193
+ const currentPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2194
+ (0, chai_1.expect)(currentPendingRedemptionsValue).to.be.equal(expectedPendingRedemptionsValue);
2195
+ });
2196
+ it("should return the requested amount of tokens to the redeemer", async () => {
2197
+ const expectedRedeemerBalance = initialRedeemerBalance.add(data.redemptionRequests[0].amount);
2198
+ const currentRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2199
+ (0, chai_1.expect)(currentRedeemerBalance).to.be.equal(expectedRedeemerBalance);
2200
+ });
2201
+ it("should remove the request from the pending redemptions", async () => {
2202
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2203
+ const request = await bridge.pendingRedemptions(redemptionKey);
2204
+ (0, chai_1.expect)(request.requestedAt).to.be.equal(0);
2205
+ });
2206
+ it("should add the request to the timed-out redemptions", async () => {
2207
+ const timedOutRequest = await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
2208
+ (0, chai_1.expect)(timedOutRequest.redeemer).to.be.equal(data.redemptionRequests[0].redeemer);
2209
+ (0, chai_1.expect)(timedOutRequest.requestedAmount).to.be.equal(redemptionRequest.requestedAmount);
2210
+ (0, chai_1.expect)(timedOutRequest.treasuryFee).to.be.equal(redemptionRequest.treasuryFee);
2211
+ (0, chai_1.expect)(timedOutRequest.txMaxFee).to.be.equal(redemptionRequest.txMaxFee);
2212
+ (0, chai_1.expect)(timedOutRequest.requestedAt).to.be.equal(redemptionRequest.requestedAt);
2213
+ });
2214
+ it("should not change wallet state", async () => {
2215
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).state).to.be.equal(fixtures_1.walletState.MovingFunds);
2216
+ });
2217
+ it("should call the ECDSA wallet registry's seize function", async () => {
2218
+ (0, chai_1.expect)(walletRegistry.seize).to.have.been.calledOnceWith(redemptionTimeoutSlashingAmount, redemptionTimeoutNotifierRewardMultiplier, await thirdParty.getAddress(), data.wallet.ecdsaWalletID, walletMembersIDs);
2219
+ });
2220
+ it("should emit RedemptionTimedOut event", async () => {
2221
+ await (0, chai_1.expect)(tx)
2222
+ .to.emit(bridge, "RedemptionTimedOut")
2223
+ .withArgs(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2224
+ });
2225
+ });
2226
+ context("when the wallet is in Terminated state", () => {
2227
+ const data = redemption_1.SinglePendingRequestedRedemption;
2228
+ let tx;
2229
+ let initialPendingRedemptionsValue;
2230
+ let initialRedeemerBalance;
2231
+ let redemptionRequest;
2232
+ before(async () => {
2233
+ await createSnapshot();
2234
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2235
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2236
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2237
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2238
+ createdAt: await lastBlockTime(),
2239
+ movingFundsRequestedAt: 0,
2240
+ closingStartedAt: 0,
2241
+ pendingMovedFundsSweepRequestsCount: 0,
2242
+ // Initially set the state to Live, so that the redemption
2243
+ // request can be made
2244
+ state: fixtures_1.walletState.Live,
2245
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2246
+ });
2247
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2248
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2249
+ from: governance,
2250
+ value: 10,
2251
+ });
2252
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2253
+ await bridge
2254
+ .connect(redeemerSigner)
2255
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2256
+ // Simulate the wallet's state has changed to Terminated
2257
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
2258
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2259
+ ecdsaWalletID: wallet.ecdsaWalletID,
2260
+ mainUtxoHash: wallet.mainUtxoHash,
2261
+ pendingRedemptionsValue: wallet.pendingRedemptionsValue,
2262
+ createdAt: wallet.createdAt,
2263
+ movingFundsRequestedAt: wallet.movingFundsRequestedAt,
2264
+ closingStartedAt: wallet.closingStartedAt,
2265
+ pendingMovedFundsSweepRequestsCount: wallet.pendingMovedFundsSweepRequestsCount,
2266
+ state: fixtures_1.walletState.Terminated,
2267
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2268
+ });
2269
+ await increaseTime(redemptionTimeout);
2270
+ initialPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2271
+ initialRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2272
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2273
+ redemptionRequest = await bridge.pendingRedemptions(redemptionKey);
2274
+ tx = await bridge
2275
+ .connect(thirdParty)
2276
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript);
2277
+ });
2278
+ after(async () => {
2279
+ await restoreSnapshot();
2280
+ });
2281
+ it("should update the wallet's pending redemptions value", async () => {
2282
+ const expectedPendingRedemptionsValue = initialPendingRedemptionsValue
2283
+ .sub(data.redemptionRequests[0].amount)
2284
+ .add(redemptionRequest.treasuryFee);
2285
+ const currentPendingRedemptionsValue = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2286
+ (0, chai_1.expect)(currentPendingRedemptionsValue).to.be.equal(expectedPendingRedemptionsValue);
2287
+ });
2288
+ it("should remove the request from the pending redemptions", async () => {
2289
+ const redemptionKey = buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2290
+ const request = await bridge.pendingRedemptions(redemptionKey);
2291
+ (0, chai_1.expect)(request.requestedAt).to.be.equal(0);
2292
+ });
2293
+ it("should add the request to the timed-out redemptions", async () => {
2294
+ const timedOutRequest = await bridge.timedOutRedemptions(buildRedemptionKey(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript));
2295
+ (0, chai_1.expect)(timedOutRequest.redeemer).to.be.equal(data.redemptionRequests[0].redeemer);
2296
+ (0, chai_1.expect)(timedOutRequest.requestedAmount).to.be.equal(redemptionRequest.requestedAmount);
2297
+ (0, chai_1.expect)(timedOutRequest.treasuryFee).to.be.equal(redemptionRequest.treasuryFee);
2298
+ (0, chai_1.expect)(timedOutRequest.txMaxFee).to.be.equal(redemptionRequest.txMaxFee);
2299
+ (0, chai_1.expect)(timedOutRequest.requestedAt).to.be.equal(redemptionRequest.requestedAt);
2300
+ });
2301
+ it("should not change wallet state", async () => {
2302
+ (0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).state).to.be.equal(fixtures_1.walletState.Terminated);
2303
+ });
2304
+ it("should emit RedemptionTimedOut event", async () => {
2305
+ await (0, chai_1.expect)(tx)
2306
+ .to.emit(bridge, "RedemptionTimedOut")
2307
+ .withArgs(data.wallet.pubKeyHash, data.redemptionRequests[0].redeemerOutputScript);
2308
+ });
2309
+ it("should return the requested amount of tokens to the redeemer", async () => {
2310
+ const expectedRedeemerBalance = initialRedeemerBalance.add(data.redemptionRequests[0].amount);
2311
+ const currentRedeemerBalance = await bank.balanceOf(data.redemptionRequests[0].redeemer);
2312
+ (0, chai_1.expect)(currentRedeemerBalance).to.be.equal(expectedRedeemerBalance);
2313
+ });
2314
+ it("should not call the ECDSA wallet registry's seize function", async () => {
2315
+ (0, chai_1.expect)(walletRegistry.seize).not.to.have.been.called;
2316
+ });
2317
+ });
2318
+ context("when the wallet is neither in Live, MovingFunds nor Terminated state", () => {
2319
+ const testData = [
2320
+ {
2321
+ testName: "when wallet state is Unknown",
2322
+ walletState: fixtures_1.walletState.Unknown,
2323
+ },
2324
+ {
2325
+ testName: "when wallet state is Closing",
2326
+ walletState: fixtures_1.walletState.Closing,
2327
+ },
2328
+ {
2329
+ testName: "when wallet state is Closed",
2330
+ walletState: fixtures_1.walletState.Closed,
2331
+ },
2332
+ ];
2333
+ testData.forEach((test) => {
2334
+ context(test.testName, () => {
2335
+ const data = redemption_1.SinglePendingRequestedRedemption;
2336
+ before(async () => {
2337
+ await createSnapshot();
2338
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2339
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2340
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2341
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2342
+ createdAt: await lastBlockTime(),
2343
+ movingFundsRequestedAt: 0,
2344
+ closingStartedAt: 0,
2345
+ pendingMovedFundsSweepRequestsCount: 0,
2346
+ state: data.wallet.state,
2347
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2348
+ });
2349
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2350
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2351
+ from: governance,
2352
+ value: 10,
2353
+ });
2354
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2355
+ await bridge
2356
+ .connect(redeemerSigner)
2357
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2358
+ // Simulate the wallet's state has changed
2359
+ const wallet = await bridge.wallets(data.wallet.pubKeyHash);
2360
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2361
+ ecdsaWalletID: wallet.ecdsaWalletID,
2362
+ mainUtxoHash: wallet.mainUtxoHash,
2363
+ pendingRedemptionsValue: wallet.pendingRedemptionsValue,
2364
+ createdAt: wallet.createdAt,
2365
+ movingFundsRequestedAt: wallet.movingFundsRequestedAt,
2366
+ closingStartedAt: wallet.closingStartedAt,
2367
+ pendingMovedFundsSweepRequestsCount: wallet.pendingMovedFundsSweepRequestsCount,
2368
+ state: test.walletState,
2369
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2370
+ });
2371
+ await increaseTime(redemptionTimeout);
2372
+ });
2373
+ after(async () => {
2374
+ await restoreSnapshot();
2375
+ });
2376
+ it("should revert", async () => {
2377
+ await (0, chai_1.expect)(bridge
2378
+ .connect(thirdParty)
2379
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript)).to.be.revertedWith("Wallet must be in Live or MovingFunds or Terminated state");
2380
+ });
2381
+ });
2382
+ });
2383
+ });
2384
+ });
2385
+ context("when the redemption request has not timed out", () => {
2386
+ const data = redemption_1.SinglePendingRequestedRedemption;
2387
+ before(async () => {
2388
+ await createSnapshot();
2389
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2390
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2391
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2392
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2393
+ createdAt: await lastBlockTime(),
2394
+ movingFundsRequestedAt: 0,
2395
+ closingStartedAt: 0,
2396
+ pendingMovedFundsSweepRequestsCount: 0,
2397
+ state: data.wallet.state,
2398
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2399
+ });
2400
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2401
+ const redeemerSigner = await impersonateAccount(data.redemptionRequests[0].redeemer, {
2402
+ from: governance,
2403
+ value: 10,
2404
+ });
2405
+ await makeRedemptionAllowance(redeemerSigner, data.redemptionRequests[0].amount);
2406
+ await bridge
2407
+ .connect(redeemerSigner)
2408
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, data.redemptionRequests[0].redeemerOutputScript, data.redemptionRequests[0].amount);
2409
+ await increaseTime(redemptionTimeout.sub(1));
2410
+ });
2411
+ after(async () => {
2412
+ await restoreSnapshot();
2413
+ });
2414
+ it("should revert", async () => {
2415
+ await (0, chai_1.expect)(bridge
2416
+ .connect(thirdParty)
2417
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, [], data.redemptionRequests[0].redeemerOutputScript)).to.be.revertedWith("Redemption request has not timed out");
2418
+ });
2419
+ });
2420
+ });
2421
+ context("when redemption request does not exist", () => {
2422
+ const data = redemption_1.SinglePendingRequestedRedemption;
2423
+ const redemptionRequest = data.redemptionRequests[0];
2424
+ before(async () => {
2425
+ await createSnapshot();
2426
+ });
2427
+ after(async () => {
2428
+ await restoreSnapshot();
2429
+ });
2430
+ it("should revert", async () => {
2431
+ await (0, chai_1.expect)(bridge
2432
+ .connect(thirdParty)
2433
+ .notifyRedemptionTimeout(data.wallet.pubKeyHash, [], redemptionRequest.redeemerOutputScript)).to.be.revertedWith("Redemption request does not exist");
2434
+ });
2435
+ });
2436
+ });
2437
+ async function runRedemptionScenario(data, beforeProofActions) {
2438
+ relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
2439
+ relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
2440
+ // Simulate the wallet is a registered one.
2441
+ await bridge.setWallet(data.wallet.pubKeyHash, {
2442
+ ecdsaWalletID: data.wallet.ecdsaWalletID,
2443
+ mainUtxoHash: hardhat_1.ethers.constants.HashZero,
2444
+ pendingRedemptionsValue: data.wallet.pendingRedemptionsValue,
2445
+ createdAt: await lastBlockTime(),
2446
+ movingFundsRequestedAt: 0,
2447
+ closingStartedAt: 0,
2448
+ pendingMovedFundsSweepRequestsCount: 0,
2449
+ state: data.wallet.state,
2450
+ movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
2451
+ });
2452
+ // Simulate the prepared main UTXO belongs to the wallet.
2453
+ await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
2454
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
2455
+ const { redeemer, redeemerOutputScript, amount } = data.redemptionRequests[i];
2456
+ /* eslint-disable no-await-in-loop */
2457
+ const redeemerSigner = await impersonateAccount(redeemer, {
2458
+ from: governance,
2459
+ value: 10,
2460
+ });
2461
+ await makeRedemptionAllowance(redeemerSigner, amount);
2462
+ await bridge
2463
+ .connect(redeemerSigner)
2464
+ .requestRedemption(data.wallet.pubKeyHash, data.mainUtxo, redeemerOutputScript, amount);
2465
+ /* eslint-enable no-await-in-loop */
2466
+ }
2467
+ if (beforeProofActions) {
2468
+ await beforeProofActions();
2469
+ }
2470
+ const bridgeBalanceBeforeProof = await bank.balanceOf(bridge.address);
2471
+ const walletPendingRedemptionsValueBeforeProof = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2472
+ const treasuryBalanceBeforeProof = await bank.balanceOf(treasury.address);
2473
+ const redeemersBalancesBeforeProof = [];
2474
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
2475
+ const { redeemer } = data.redemptionRequests[i];
2476
+ // eslint-disable-next-line no-await-in-loop
2477
+ redeemersBalancesBeforeProof.push(await bank.balanceOf(redeemer));
2478
+ }
2479
+ const tx = await bridge.submitRedemptionProof(data.redemptionTx, data.redemptionProof, data.mainUtxo, data.wallet.pubKeyHash);
2480
+ const bridgeBalanceAfterProof = await bank.balanceOf(bridge.address);
2481
+ const walletPendingRedemptionsValueAfterProof = (await bridge.wallets(data.wallet.pubKeyHash)).pendingRedemptionsValue;
2482
+ const treasuryBalanceAfterProof = await bank.balanceOf(treasury.address);
2483
+ const redeemersBalances = [];
2484
+ for (let i = 0; i < data.redemptionRequests.length; i++) {
2485
+ const { redeemer } = data.redemptionRequests[i];
2486
+ redeemersBalances.push({
2487
+ beforeProof: redeemersBalancesBeforeProof[i],
2488
+ // eslint-disable-next-line no-await-in-loop
2489
+ afterProof: await bank.balanceOf(redeemer),
2490
+ });
2491
+ }
2492
+ return {
2493
+ tx,
2494
+ bridgeBalance: {
2495
+ beforeProof: bridgeBalanceBeforeProof,
2496
+ afterProof: bridgeBalanceAfterProof,
2497
+ },
2498
+ walletPendingRedemptionsValue: {
2499
+ beforeProof: walletPendingRedemptionsValueBeforeProof,
2500
+ afterProof: walletPendingRedemptionsValueAfterProof,
2501
+ },
2502
+ treasuryBalance: {
2503
+ beforeProof: treasuryBalanceBeforeProof,
2504
+ afterProof: treasuryBalanceAfterProof,
2505
+ },
2506
+ redeemersBalances,
2507
+ };
2508
+ }
2509
+ async function makeRedemptionAllowance(redeemer, amount) {
2510
+ // Simulate the redeemer has a Bank balance allowing to make the request.
2511
+ await bank.setBalance(redeemer.address, amount);
2512
+ // Redeemer must allow the Bridge to spent the requested amount.
2513
+ await bank
2514
+ .connect(redeemer)
2515
+ .increaseBalanceAllowance(bridge.address, amount);
2516
+ }
2517
+ function buildRedemptionKey(walletPubKeyHash, redeemerOutputScript) {
2518
+ return hardhat_1.ethers.utils.solidityKeccak256(["bytes20", "bytes"], [walletPubKeyHash, redeemerOutputScript]);
2519
+ }
2520
+ function buildMainUtxoHash(txHash, txOutputIndex, txOutputValue) {
2521
+ return hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [txHash, txOutputIndex, txOutputValue]);
2522
+ }
2523
+ });