@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.
- package/README.adoc +12 -0
- package/artifacts/.chainId +1 -1
- package/artifacts/Bank.json +807 -0
- package/artifacts/Bridge.json +2300 -0
- package/artifacts/Deposit.json +117 -0
- package/artifacts/DepositSweep.json +77 -0
- package/artifacts/EcdsaDkgValidator.json +532 -0
- package/artifacts/EcdsaInactivity.json +156 -0
- package/artifacts/EcdsaSortitionPool.json +1004 -0
- package/artifacts/Fraud.json +164 -0
- package/artifacts/KeepRegistry.json +99 -0
- package/artifacts/KeepStake.json +286 -0
- package/artifacts/KeepToken.json +711 -0
- package/artifacts/KeepTokenStaking.json +483 -0
- package/artifacts/MovingFunds.json +249 -0
- package/artifacts/NuCypherStakingEscrow.json +256 -0
- package/artifacts/NuCypherToken.json +711 -0
- package/artifacts/RandomBeaconStub.json +141 -0
- package/artifacts/Redemption.json +174 -0
- package/artifacts/ReimbursementPool.json +509 -0
- package/artifacts/Relay.json +123 -0
- package/artifacts/T.json +1148 -0
- package/artifacts/TBTC.json +36 -35
- package/artifacts/TBTCToken.json +738 -0
- package/artifacts/TBTCVault.json +691 -0
- package/artifacts/TokenStaking.json +2288 -0
- package/artifacts/TokenholderGovernor.json +1795 -0
- package/artifacts/TokenholderTimelock.json +1058 -0
- package/artifacts/VendingMachine.json +34 -33
- package/artifacts/VendingMachineKeep.json +400 -0
- package/artifacts/VendingMachineNuCypher.json +400 -0
- package/artifacts/WalletRegistry.json +1843 -0
- package/artifacts/WalletRegistryGovernance.json +2754 -0
- package/artifacts/Wallets.json +186 -0
- package/artifacts/solcInputs/5e62cff1ead0900b07facca4b559e818.json +314 -0
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
- package/build/contracts/GovernanceUtils.sol/GovernanceUtils.json +2 -2
- package/build/contracts/bank/Bank.sol/Bank.dbg.json +4 -0
- package/build/contracts/bank/Bank.sol/Bank.json +542 -0
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.dbg.json +4 -0
- package/build/contracts/bank/IReceiveBalanceApproval.sol/IReceiveBalanceApproval.json +34 -0
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +4 -0
- package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +10 -0
- package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +4 -0
- package/build/contracts/bridge/Bridge.sol/Bridge.json +2686 -0
- package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +4 -0
- package/build/contracts/bridge/BridgeState.sol/BridgeState.json +226 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +4 -0
- package/build/contracts/bridge/Deposit.sol/Deposit.json +72 -0
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +4 -0
- package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +30 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +4 -0
- package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.json +10 -0
- package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +4 -0
- package/build/contracts/bridge/Fraud.sol/Fraud.json +86 -0
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
- package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +4 -0
- package/build/contracts/bridge/IRelay.sol/IRelay.json +37 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +4 -0
- package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +138 -0
- package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +4 -0
- package/build/contracts/bridge/Redemption.sol/OutboundTx.json +10 -0
- package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +4 -0
- package/build/contracts/bridge/Redemption.sol/Redemption.json +92 -0
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
- package/build/contracts/bridge/VendingMachine.sol/VendingMachine.json +2 -2
- package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +4 -0
- package/build/contracts/bridge/Wallets.sol/Wallets.json +112 -0
- package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
- package/build/contracts/token/TBTC.sol/TBTC.json +4 -4
- package/build/contracts/vault/DonationVault.sol/DonationVault.dbg.json +4 -0
- package/build/contracts/vault/DonationVault.sol/DonationVault.json +108 -0
- package/build/contracts/vault/IVault.sol/IVault.dbg.json +4 -0
- package/build/contracts/vault/IVault.sol/IVault.json +52 -0
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +4 -0
- package/build/contracts/vault/TBTCVault.sol/TBTCVault.json +449 -0
- package/contracts/GovernanceUtils.sol +4 -4
- package/contracts/bank/Bank.sol +436 -0
- package/contracts/bank/IReceiveBalanceApproval.sol +45 -0
- package/contracts/bridge/BitcoinTx.sol +326 -0
- package/contracts/bridge/Bridge.sol +1793 -0
- package/contracts/bridge/BridgeState.sol +739 -0
- package/contracts/bridge/Deposit.sol +269 -0
- package/contracts/bridge/DepositSweep.sol +574 -0
- package/contracts/bridge/EcdsaLib.sol +45 -0
- package/contracts/bridge/Fraud.sol +579 -0
- package/contracts/bridge/Heartbeat.sol +112 -0
- package/contracts/bridge/IRelay.sol +28 -0
- package/contracts/bridge/MovingFunds.sol +1077 -0
- package/contracts/bridge/Redemption.sol +1020 -0
- package/contracts/bridge/VendingMachine.sol +2 -2
- package/contracts/bridge/Wallets.sol +719 -0
- package/contracts/hardhat-dependency-compiler/.hardhat-dependency-compiler +1 -0
- package/contracts/hardhat-dependency-compiler/@keep-network/ecdsa/contracts/WalletRegistry.sol +3 -0
- package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol +3 -0
- package/contracts/hardhat-dependency-compiler/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +3 -0
- package/contracts/token/TBTC.sol +1 -1
- package/contracts/vault/DonationVault.sol +125 -0
- package/contracts/vault/IVault.sol +44 -0
- package/contracts/vault/TBTCVault.sol +305 -0
- package/deploy/00_resolve_relay.ts +28 -0
- package/deploy/00_resolve_tbtc_v1_token.ts +1 -1
- package/deploy/01_deploy_tbtc_v2_token.ts +8 -1
- package/deploy/02_deploy_vending_machine.ts +7 -0
- package/deploy/{03_transfer_roles.ts → 03_transfer_vending_machine_roles.ts} +1 -1
- package/deploy/04_deploy_bank.ts +27 -0
- package/deploy/05_deploy_bridge.ts +80 -0
- package/deploy/06_deploy_tbtc_vault.ts +30 -0
- package/deploy/07_bank_update_bridge.ts +19 -0
- package/deploy/08_transfer_bank_ownership.ts +15 -0
- package/deploy/09_transfer_tbtc_vault_ownership.ts +15 -0
- package/deploy/10_transfer_bridge_governance.ts +20 -0
- package/deploy/11_initialize_wallet_owner.ts +18 -0
- package/deploy/11_transfer_proxy_admin_ownership.ts +30 -0
- package/deploy/12_deploy_proxy_admin_with_deputy.ts +33 -0
- package/export/deploy/00_resolve_relay.js +24 -0
- package/export/deploy/00_resolve_tbtc_v1_token.js +24 -0
- package/export/deploy/01_deploy_tbtc_v2_token.js +19 -0
- package/export/deploy/02_deploy_vending_machine.js +25 -0
- package/export/deploy/03_transfer_vending_machine_roles.js +19 -0
- package/export/deploy/04_deploy_bank.js +21 -0
- package/export/deploy/05_deploy_bridge.js +69 -0
- package/export/deploy/06_deploy_tbtc_vault.js +24 -0
- package/export/deploy/07_bank_update_bridge.js +13 -0
- package/export/deploy/08_transfer_bank_ownership.js +11 -0
- package/export/deploy/09_transfer_tbtc_vault_ownership.js +11 -0
- package/export/deploy/10_transfer_bridge_governance.js +11 -0
- package/export/deploy/11_initialize_wallet_owner.js +14 -0
- package/export/deploy/11_transfer_proxy_admin_ownership.js +23 -0
- package/export/deploy/12_deploy_proxy_admin_with_deputy.js +22 -0
- package/export/hardhat.config.js +169 -0
- package/export/test/bank/Bank.test.js +1012 -0
- package/export/test/bridge/Bridge.Deployment.test.js +76 -0
- package/export/test/bridge/Bridge.Deposit.test.js +1834 -0
- package/export/test/bridge/Bridge.Frauds.test.js +1349 -0
- package/export/test/bridge/Bridge.MovingFunds.test.js +2437 -0
- package/export/test/bridge/Bridge.Parameters.test.js +400 -0
- package/export/test/bridge/Bridge.Redemption.test.js +2523 -0
- package/export/test/bridge/Bridge.Vaults.test.js +74 -0
- package/export/test/bridge/Bridge.Wallets.test.js +1017 -0
- package/export/test/bridge/EcdsaLib.test.js +46 -0
- package/export/test/bridge/Heartbeat.test.js +77 -0
- package/export/test/bridge/VendingMachine.Upgrade.test.js +160 -0
- package/export/test/bridge/VendingMachine.test.js +762 -0
- package/export/test/data/deposit-sweep.js +655 -0
- package/export/test/data/ecdsa.js +18 -0
- package/export/test/data/fraud.js +158 -0
- package/export/test/data/moving-funds.js +815 -0
- package/export/test/data/redemption.js +1011 -0
- package/export/test/fixtures/bridge.js +54 -0
- package/export/test/fixtures/index.js +57 -0
- package/export/test/helpers/contract-test-helpers.js +18 -0
- package/export/test/integration/Slashing.test.js +279 -0
- package/export/test/integration/WalleCreation.test.js +66 -0
- package/export/test/integration/utils/ecdsa-wallet-registry.js +137 -0
- package/export/test/integration/utils/fixture.js +77 -0
- package/export/test/integration/utils/gas.js +36 -0
- package/export/test/integration/utils/random-beacon.js +26 -0
- package/export/test/integration/utils/staking.js +19 -0
- package/export/test/vault/DonationVault.test.js +202 -0
- package/export/test/vault/TBTCVault.Redemption.test.js +357 -0
- package/export/test/vault/TBTCVault.test.js +768 -0
- package/export/typechain/BTCUtils.js +2 -0
- package/export/typechain/Bank.js +2 -0
- package/export/typechain/BankStub.js +2 -0
- package/export/typechain/Bridge.js +2 -0
- package/export/typechain/BridgeState.js +2 -0
- package/export/typechain/BridgeStub.js +2 -0
- package/export/typechain/Deposit.js +2 -0
- package/export/typechain/DepositSweep.js +2 -0
- package/export/typechain/DonationVault.js +2 -0
- package/export/typechain/ERC165.js +2 -0
- package/export/typechain/ERC1967Proxy.js +2 -0
- package/export/typechain/ERC1967Upgrade.js +2 -0
- package/export/typechain/ERC20WithPermit.js +2 -0
- package/export/typechain/ERC721.js +2 -0
- package/export/typechain/EcdsaAuthorization.js +2 -0
- package/export/typechain/EcdsaDkg.js +2 -0
- package/export/typechain/EcdsaDkgValidator.js +2 -0
- package/export/typechain/EcdsaInactivity.js +2 -0
- package/export/typechain/Fraud.js +2 -0
- package/export/typechain/Governable.js +2 -0
- package/export/typechain/HeartbeatStub.js +2 -0
- package/export/typechain/IApplication.js +2 -0
- package/export/typechain/IApproveAndCall.js +2 -0
- package/export/typechain/IBeacon.js +2 -0
- package/export/typechain/IERC165.js +2 -0
- package/export/typechain/IERC1822Proxiable.js +2 -0
- package/export/typechain/IERC20.js +2 -0
- package/export/typechain/IERC20Metadata.js +2 -0
- package/export/typechain/IERC20WithPermit.js +2 -0
- package/export/typechain/IERC721.js +2 -0
- package/export/typechain/IERC721Metadata.js +2 -0
- package/export/typechain/IERC721Receiver.js +2 -0
- package/export/typechain/IRandomBeacon.js +2 -0
- package/export/typechain/IRandomBeaconConsumer.js +2 -0
- package/export/typechain/IReceiveApproval.js +2 -0
- package/export/typechain/IReceiveBalanceApproval.js +2 -0
- package/export/typechain/IRelay.js +2 -0
- package/export/typechain/IStaking.js +2 -0
- package/export/typechain/IVault.js +2 -0
- package/export/typechain/IWalletOwner.js +2 -0
- package/export/typechain/IWalletRegistry.js +2 -0
- package/export/typechain/Initializable.js +2 -0
- package/export/typechain/MisfundRecovery.js +2 -0
- package/export/typechain/MovingFunds.js +2 -0
- package/export/typechain/Ownable.js +2 -0
- package/export/typechain/Proxy.js +2 -0
- package/export/typechain/ProxyAdmin.js +2 -0
- package/export/typechain/ReceiveApprovalStub.js +2 -0
- package/export/typechain/Redemption.js +2 -0
- package/export/typechain/Reimbursable.js +2 -0
- package/export/typechain/ReimbursementPool.js +2 -0
- package/export/typechain/Rewards.js +2 -0
- package/export/typechain/SortitionPool.js +2 -0
- package/export/typechain/SortitionTree.js +2 -0
- package/export/typechain/TBTC.js +2 -0
- package/export/typechain/TBTCVault.js +2 -0
- package/export/typechain/TestERC20.js +2 -0
- package/export/typechain/TestERC721.js +2 -0
- package/export/typechain/TestEcdsaLib.js +2 -0
- package/export/typechain/TestRelay.js +2 -0
- package/export/typechain/TransparentUpgradeableProxy.js +2 -0
- package/export/typechain/VendingMachine.js +2 -0
- package/export/typechain/WalletRegistry.js +2 -0
- package/export/typechain/Wallets.js +2 -0
- package/export/typechain/common.js +2 -0
- package/export/typechain/factories/BTCUtils__factory.js +94 -0
- package/export/typechain/factories/BankStub__factory.js +586 -0
- package/export/typechain/factories/Bank__factory.js +573 -0
- package/export/typechain/factories/BridgeState__factory.js +257 -0
- package/export/typechain/factories/BridgeStub__factory.js +2912 -0
- package/export/typechain/factories/Bridge__factory.js +2526 -0
- package/export/typechain/factories/DepositSweep__factory.js +61 -0
- package/export/typechain/factories/Deposit__factory.js +103 -0
- package/export/typechain/factories/DonationVault__factory.js +139 -0
- package/export/typechain/factories/ERC165__factory.js +38 -0
- package/export/typechain/factories/ERC1967Proxy__factory.js +111 -0
- package/export/typechain/factories/ERC1967Upgrade__factory.js +64 -0
- package/export/typechain/factories/ERC20WithPermit__factory.js +524 -0
- package/export/typechain/factories/ERC721__factory.js +388 -0
- package/export/typechain/factories/EcdsaAuthorization__factory.js +211 -0
- package/export/typechain/factories/EcdsaDkgValidator__factory.js +441 -0
- package/export/typechain/factories/EcdsaDkg__factory.js +192 -0
- package/export/typechain/factories/EcdsaInactivity__factory.js +134 -0
- package/export/typechain/factories/Fraud__factory.js +117 -0
- package/export/typechain/factories/Governable__factory.js +64 -0
- package/export/typechain/factories/HeartbeatStub__factory.js +61 -0
- package/export/typechain/factories/IApplication__factory.js +152 -0
- package/export/typechain/factories/IApproveAndCall__factory.js +48 -0
- package/export/typechain/factories/IBeacon__factory.js +32 -0
- package/export/typechain/factories/IERC165__factory.js +38 -0
- package/export/typechain/factories/IERC1822Proxiable__factory.js +32 -0
- package/export/typechain/factories/IERC20Metadata__factory.js +241 -0
- package/export/typechain/factories/IERC20WithPermit__factory.js +389 -0
- package/export/typechain/factories/IERC20__factory.js +202 -0
- package/export/typechain/factories/IERC721Metadata__factory.js +349 -0
- package/export/typechain/factories/IERC721Receiver__factory.js +53 -0
- package/export/typechain/factories/IERC721__factory.js +304 -0
- package/export/typechain/factories/IRandomBeaconConsumer__factory.js +37 -0
- package/export/typechain/factories/IRandomBeacon__factory.js +32 -0
- package/export/typechain/factories/IReceiveApproval__factory.js +47 -0
- package/export/typechain/factories/IReceiveBalanceApproval__factory.js +42 -0
- package/export/typechain/factories/IRelay__factory.js +45 -0
- package/export/typechain/factories/IStaking__factory.js +722 -0
- package/export/typechain/factories/IVault__factory.js +60 -0
- package/export/typechain/factories/IWalletOwner__factory.js +65 -0
- package/export/typechain/factories/IWalletRegistry__factory.js +138 -0
- package/export/typechain/factories/Initializable__factory.js +32 -0
- package/export/typechain/factories/MisfundRecovery__factory.js +145 -0
- package/export/typechain/factories/MovingFunds__factory.js +169 -0
- package/export/typechain/factories/Ownable__factory.js +71 -0
- package/export/typechain/factories/ProxyAdmin__factory.js +191 -0
- package/export/typechain/factories/Proxy__factory.js +27 -0
- package/export/typechain/factories/ReceiveApprovalStub__factory.js +127 -0
- package/export/typechain/factories/Redemption__factory.js +123 -0
- package/export/typechain/factories/Reimbursable__factory.js +58 -0
- package/export/typechain/factories/ReimbursementPool__factory.js +350 -0
- package/export/typechain/factories/Rewards__factory.js +117 -0
- package/export/typechain/factories/SortitionPool__factory.js +610 -0
- package/export/typechain/factories/SortitionTree__factory.js +149 -0
- package/export/typechain/factories/TBTCVault__factory.js +480 -0
- package/export/typechain/factories/TBTC__factory.js +564 -0
- package/export/typechain/factories/TestERC20__factory.js +539 -0
- package/export/typechain/factories/TestERC721__factory.js +421 -0
- package/export/typechain/factories/TestEcdsaLib__factory.js +66 -0
- package/export/typechain/factories/TestRelay__factory.js +94 -0
- package/export/typechain/factories/TransparentUpgradeableProxy__factory.js +186 -0
- package/export/typechain/factories/VendingMachine__factory.js +549 -0
- package/export/typechain/factories/WalletRegistry__factory.js +1919 -0
- package/export/typechain/factories/Wallets__factory.js +143 -0
- package/export/typechain/index.js +132 -0
- package/export.json +15932 -503
- package/package.json +47 -26
- package/artifacts/solcInputs/7cc3eda3cb3ff2522d18b5e7b31ea228.json +0 -104
|
@@ -0,0 +1,2437 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
/* eslint-disable no-underscore-dangle */
|
|
30
|
+
const hardhat_1 = require("hardhat");
|
|
31
|
+
const chai_1 = __importStar(require("chai"));
|
|
32
|
+
const smock_1 = require("@defi-wonderland/smock");
|
|
33
|
+
const bridge_1 = __importDefault(require("../fixtures/bridge"));
|
|
34
|
+
const fixtures_1 = require("../fixtures");
|
|
35
|
+
const moving_funds_1 = require("../data/moving-funds");
|
|
36
|
+
const ecdsa_1 = require("../data/ecdsa");
|
|
37
|
+
const deposit_sweep_1 = require("../data/deposit-sweep");
|
|
38
|
+
const contract_test_helpers_1 = require("../helpers/contract-test-helpers");
|
|
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
|
+
describe("Bridge - Moving funds", () => {
|
|
43
|
+
let thirdParty;
|
|
44
|
+
let treasury;
|
|
45
|
+
let bank;
|
|
46
|
+
let relay;
|
|
47
|
+
let walletRegistry;
|
|
48
|
+
let bridge;
|
|
49
|
+
let BridgeFactory;
|
|
50
|
+
let movingFundsTimeoutResetDelay;
|
|
51
|
+
let movingFundsTimeout;
|
|
52
|
+
let movingFundsTimeoutSlashingAmount;
|
|
53
|
+
let movingFundsTimeoutNotifierRewardMultiplier;
|
|
54
|
+
let movedFundsSweepTimeout;
|
|
55
|
+
let movedFundsSweepTimeoutSlashingAmount;
|
|
56
|
+
let movedFundsSweepTimeoutNotifierRewardMultiplier;
|
|
57
|
+
before(async () => {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
|
59
|
+
;
|
|
60
|
+
({
|
|
61
|
+
thirdParty,
|
|
62
|
+
treasury,
|
|
63
|
+
bank,
|
|
64
|
+
relay,
|
|
65
|
+
walletRegistry,
|
|
66
|
+
bridge,
|
|
67
|
+
BridgeFactory,
|
|
68
|
+
} = await hardhat_1.waffle.loadFixture(bridge_1.default));
|
|
69
|
+
({
|
|
70
|
+
movingFundsTimeoutResetDelay,
|
|
71
|
+
movingFundsTimeout,
|
|
72
|
+
movingFundsTimeoutSlashingAmount,
|
|
73
|
+
movingFundsTimeoutNotifierRewardMultiplier,
|
|
74
|
+
movedFundsSweepTimeout,
|
|
75
|
+
movedFundsSweepTimeoutSlashingAmount,
|
|
76
|
+
movedFundsSweepTimeoutNotifierRewardMultiplier,
|
|
77
|
+
} = await bridge.movingFundsParameters());
|
|
78
|
+
});
|
|
79
|
+
describe("submitMovingFundsCommitment", () => {
|
|
80
|
+
const walletDraft = {
|
|
81
|
+
ecdsaWalletID: ecdsa_1.ecdsaWalletTestData.walletID,
|
|
82
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
83
|
+
pendingRedemptionsValue: 0,
|
|
84
|
+
createdAt: 0,
|
|
85
|
+
movingFundsRequestedAt: 0,
|
|
86
|
+
closingStartedAt: 0,
|
|
87
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
88
|
+
state: fixtures_1.walletState.Unknown,
|
|
89
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
90
|
+
};
|
|
91
|
+
context("when source wallet is in the MovingFunds state", () => {
|
|
92
|
+
before(async () => {
|
|
93
|
+
await createSnapshot();
|
|
94
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
95
|
+
...walletDraft,
|
|
96
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
after(async () => {
|
|
100
|
+
await restoreSnapshot();
|
|
101
|
+
});
|
|
102
|
+
context("when source wallet has no pending redemptions", () => {
|
|
103
|
+
// The wallet created using the `walletDraft` has no pending redemptions
|
|
104
|
+
// by default. No need to do anything here.
|
|
105
|
+
context("when source wallet has no pending moved funds sweep requests", () => {
|
|
106
|
+
// The wallet created using the `walletDraft` has no pending moved
|
|
107
|
+
// funds sweep requests by default. No need to do anything here.
|
|
108
|
+
context("when the commitment was not submitted yet", () => {
|
|
109
|
+
// The wallet created using the `walletDraft` has no commitment
|
|
110
|
+
// submitted by default. No need to do anything here.
|
|
111
|
+
context("when the caller is a member of the source wallet", () => {
|
|
112
|
+
const walletMembersIDs = [1, 2, 3, 4, 5];
|
|
113
|
+
const walletMemberIndex = 2;
|
|
114
|
+
let caller;
|
|
115
|
+
before(async () => {
|
|
116
|
+
await createSnapshot();
|
|
117
|
+
caller = thirdParty;
|
|
118
|
+
walletRegistry.isWalletMember
|
|
119
|
+
.whenCalledWith(ecdsa_1.ecdsaWalletTestData.walletID, walletMembersIDs, caller.address, walletMemberIndex)
|
|
120
|
+
.returns(true);
|
|
121
|
+
});
|
|
122
|
+
after(async () => {
|
|
123
|
+
walletRegistry.isWalletMember.reset();
|
|
124
|
+
await restoreSnapshot();
|
|
125
|
+
});
|
|
126
|
+
context("when passed wallet main UTXO is valid", () => {
|
|
127
|
+
context("when wallet balance is greater than zero", () => {
|
|
128
|
+
// Just an arbitrary main UTXO with value of 26 BTC.
|
|
129
|
+
const mainUtxo = {
|
|
130
|
+
txHash: "0xc9e58780c6c289c25ae1fe293f85a4db4d0af4f305172f2a1868ddd917458bdf",
|
|
131
|
+
txOutputIndex: 0,
|
|
132
|
+
txOutputValue: (0, contract_test_helpers_1.to1ePrecision)(26, 8),
|
|
133
|
+
};
|
|
134
|
+
before(async () => {
|
|
135
|
+
await createSnapshot();
|
|
136
|
+
// Set up a main UTXO for the source wallet.
|
|
137
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
138
|
+
});
|
|
139
|
+
after(async () => {
|
|
140
|
+
await restoreSnapshot();
|
|
141
|
+
});
|
|
142
|
+
context("when the expected target wallets count is greater than zero", () => {
|
|
143
|
+
// Just some arbitrary 20-byte hashes to simulate live
|
|
144
|
+
// wallets PKHs. They are ordered in the expected way, i.e.
|
|
145
|
+
// the hashes represented as numbers form a strictly
|
|
146
|
+
// increasing sequence.
|
|
147
|
+
const liveWallets = [
|
|
148
|
+
"0x4b440cb29c80c3f256212d8fdd4f2125366f3c91",
|
|
149
|
+
"0x888f01315e0268bfa05d5e522f8d63f6824d9a96",
|
|
150
|
+
"0xb2a89e53a4227dbe530a52a1c419040735fa636c",
|
|
151
|
+
"0xbf198e8fff0f90af01024153701da99b9bc08dc5",
|
|
152
|
+
"0xffb9e05013f5cd126915bc03d340cc5c1be81862",
|
|
153
|
+
];
|
|
154
|
+
before(async () => {
|
|
155
|
+
await createSnapshot();
|
|
156
|
+
for (let i = 0; i < liveWallets.length; i++) {
|
|
157
|
+
// eslint-disable-next-line no-await-in-loop
|
|
158
|
+
await bridge.setWallet(liveWallets[i], {
|
|
159
|
+
...walletDraft,
|
|
160
|
+
state: fixtures_1.walletState.Live,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
after(async () => {
|
|
165
|
+
await restoreSnapshot();
|
|
166
|
+
});
|
|
167
|
+
context("when the submitted target wallets count is same as the expected", () => {
|
|
168
|
+
// The source wallet has a main UTXO with value of 26 BTC,
|
|
169
|
+
// the max BTC transfer is 10 BTC by default (see
|
|
170
|
+
// `constants.walletMaxBtcTransfer`) and the count of
|
|
171
|
+
// live wallets is `5`. We compute the expected target
|
|
172
|
+
// wallets count as:
|
|
173
|
+
// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`
|
|
174
|
+
// so we have `N = min(5, 26 / 10) = min(5, 3) = 3`
|
|
175
|
+
const expectedTargetWalletsCount = 3;
|
|
176
|
+
context("when all target wallets are different than the source wallet", () => {
|
|
177
|
+
context("when all target wallets follow the expected order", () => {
|
|
178
|
+
context("when all target wallets are in the Live state", () => {
|
|
179
|
+
let tx;
|
|
180
|
+
const targetWallets = liveWallets.slice(0, expectedTargetWalletsCount);
|
|
181
|
+
before(async () => {
|
|
182
|
+
await createSnapshot();
|
|
183
|
+
tx = await bridge
|
|
184
|
+
.connect(caller)
|
|
185
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, targetWallets);
|
|
186
|
+
});
|
|
187
|
+
after(async () => {
|
|
188
|
+
await restoreSnapshot();
|
|
189
|
+
});
|
|
190
|
+
it("should store the target wallets commitment for the given wallet", async () => {
|
|
191
|
+
(0, chai_1.expect)((await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160))
|
|
192
|
+
.movingFundsTargetWalletsCommitmentHash).to.be.equal(hardhat_1.ethers.utils.solidityKeccak256(["bytes20[]"], [targetWallets]));
|
|
193
|
+
});
|
|
194
|
+
it("should emit the MovingFundsCommitmentSubmitted event", async () => {
|
|
195
|
+
await (0, chai_1.expect)(tx)
|
|
196
|
+
.to.emit(bridge, "MovingFundsCommitmentSubmitted")
|
|
197
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, targetWallets, caller.address);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
context("when one of the target wallets is not in the Live state", () => {
|
|
201
|
+
it("should revert", async () => {
|
|
202
|
+
// Put an Unknown wallet into the mix.
|
|
203
|
+
const targetWallets = [
|
|
204
|
+
"0x2313e29d08e6b5e0d3cda040ed7f664ce9c840c4",
|
|
205
|
+
liveWallets[0],
|
|
206
|
+
liveWallets[1],
|
|
207
|
+
];
|
|
208
|
+
await (0, chai_1.expect)(bridge
|
|
209
|
+
.connect(caller)
|
|
210
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, targetWallets)).to.be.revertedWith("Submitted target wallet must be in Live state");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
context("when one of the target wallets break the expected order", () => {
|
|
215
|
+
it("should revert", async () => {
|
|
216
|
+
const targetWallets = [
|
|
217
|
+
liveWallets[0],
|
|
218
|
+
liveWallets[1],
|
|
219
|
+
liveWallets[1],
|
|
220
|
+
];
|
|
221
|
+
await (0, chai_1.expect)(bridge
|
|
222
|
+
.connect(caller)
|
|
223
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, targetWallets)).to.be.revertedWith("Submitted target wallet breaks the expected order");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
context("when one of the target wallets is same as the source wallet", () => {
|
|
228
|
+
it("should revert", async () => {
|
|
229
|
+
const targetWallets = [
|
|
230
|
+
liveWallets[0],
|
|
231
|
+
ecdsa_1.ecdsaWalletTestData.pubKeyHash160,
|
|
232
|
+
liveWallets[1],
|
|
233
|
+
];
|
|
234
|
+
await (0, chai_1.expect)(bridge
|
|
235
|
+
.connect(caller)
|
|
236
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, targetWallets)).to.be.revertedWith("Submitted target wallet cannot be equal to the source wallet");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
context("when the submitted target wallets count is other than the expected", () => {
|
|
241
|
+
it("should revert", async () => {
|
|
242
|
+
await (0, chai_1.expect)(bridge
|
|
243
|
+
.connect(caller)
|
|
244
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, [liveWallets[0], liveWallets[1]])).to.be.revertedWith("Submitted target wallets count is other than expected");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
context("when the expected target wallets count is zero", () => {
|
|
249
|
+
it("should revert", async () => {
|
|
250
|
+
await (0, chai_1.expect)(
|
|
251
|
+
// The last parameter doesn't matter in this scenario.
|
|
252
|
+
bridge
|
|
253
|
+
.connect(caller)
|
|
254
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo, walletMembersIDs, walletMemberIndex, [])).to.be.revertedWith("No target wallets available");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
context("when wallet balance is zero", () => {
|
|
259
|
+
it("should revert", async () => {
|
|
260
|
+
await (0, chai_1.expect)(
|
|
261
|
+
// The last parameter doesn't matter in this scenario.
|
|
262
|
+
bridge.connect(caller).submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, // That makes the balance to be 0 BTC.
|
|
263
|
+
walletMembersIDs, walletMemberIndex, [])).to.be.revertedWith("Wallet BTC balance is zero");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
context("when passed wallet main UTXO is invalid", () => {
|
|
268
|
+
const mainUtxo = {
|
|
269
|
+
txHash: "0xc9e58780c6c289c25ae1fe293f85a4db4d0af4f305172f2a1868ddd917458bdf",
|
|
270
|
+
txOutputIndex: 0,
|
|
271
|
+
txOutputValue: (0, contract_test_helpers_1.to1ePrecision)(26, 8), // 26 BTC
|
|
272
|
+
};
|
|
273
|
+
before(async () => {
|
|
274
|
+
await createSnapshot();
|
|
275
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
276
|
+
});
|
|
277
|
+
after(async () => {
|
|
278
|
+
await restoreSnapshot();
|
|
279
|
+
});
|
|
280
|
+
it("should revert", async () => {
|
|
281
|
+
// Changing the `txOutputValue` to a value other than `0` will
|
|
282
|
+
// make that scenario happen.
|
|
283
|
+
const corruptedMainUtxo = {
|
|
284
|
+
...mainUtxo,
|
|
285
|
+
txOutputValue: 1,
|
|
286
|
+
};
|
|
287
|
+
await (0, chai_1.expect)(
|
|
288
|
+
// The last parameter doesn't matter in this scenario.
|
|
289
|
+
bridge
|
|
290
|
+
.connect(caller)
|
|
291
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, corruptedMainUtxo, walletMembersIDs, walletMemberIndex, [])).to.be.revertedWith("Invalid wallet main UTXO data");
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
context("when the caller is not a member of the source wallet", () => {
|
|
296
|
+
const walletMembersIDs = [1, 2, 3, 4, 5];
|
|
297
|
+
const walletMemberIndex = 2;
|
|
298
|
+
before(async () => {
|
|
299
|
+
await createSnapshot();
|
|
300
|
+
// That's the default behavior, but we just make it explicit.
|
|
301
|
+
walletRegistry.isWalletMember.returns(false);
|
|
302
|
+
});
|
|
303
|
+
after(async () => {
|
|
304
|
+
walletRegistry.isWalletMember.reset();
|
|
305
|
+
await restoreSnapshot();
|
|
306
|
+
});
|
|
307
|
+
it("should revert", async () => {
|
|
308
|
+
await (0, chai_1.expect)(
|
|
309
|
+
// The last parameter doesn't matter in this scenario.
|
|
310
|
+
bridge
|
|
311
|
+
.connect(thirdParty)
|
|
312
|
+
.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, walletMembersIDs, walletMemberIndex, [])).to.be.revertedWith("Caller is not a member of the source wallet");
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
context("when the commitment was already submitted", () => {
|
|
317
|
+
before(async () => {
|
|
318
|
+
await createSnapshot();
|
|
319
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
320
|
+
...walletDraft,
|
|
321
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
322
|
+
// Set a non-zero commitment to make this scenario work.
|
|
323
|
+
movingFundsTargetWalletsCommitmentHash: "0x959e95e0bd83d34878f77ead61cb4e955bf5e3bdc9e16cdfbd51c4c20ab7e6b4",
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
after(async () => {
|
|
327
|
+
await restoreSnapshot();
|
|
328
|
+
});
|
|
329
|
+
it("should revert", async () => {
|
|
330
|
+
await (0, chai_1.expect)(
|
|
331
|
+
// Parameters others than the first doesn't matter in this scenario.
|
|
332
|
+
bridge.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, [], 0, [])).to.be.revertedWith("Target wallets commitment already submitted");
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
context("when source wallet has pending moved funds sweep requests", () => {
|
|
337
|
+
before(async () => {
|
|
338
|
+
await createSnapshot();
|
|
339
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
340
|
+
...walletDraft,
|
|
341
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
342
|
+
// Set non-zero pending requests count to make this scenario work.
|
|
343
|
+
pendingMovedFundsSweepRequestsCount: 1,
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
after(async () => {
|
|
347
|
+
await restoreSnapshot();
|
|
348
|
+
});
|
|
349
|
+
it("should revert", async () => {
|
|
350
|
+
await (0, chai_1.expect)(
|
|
351
|
+
// Parameters others than the first doesn't matter in this scenario.
|
|
352
|
+
bridge.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, [], 0, [])).to.be.revertedWith("Source wallet must handle all pending moved funds sweep requests first");
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
context("when source wallet has pending redemptions", () => {
|
|
357
|
+
before(async () => {
|
|
358
|
+
await createSnapshot();
|
|
359
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
360
|
+
...walletDraft,
|
|
361
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
362
|
+
// Set non-zero pending redemptions value to make this scenario work.
|
|
363
|
+
pendingRedemptionsValue: 10000,
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
after(async () => {
|
|
367
|
+
await restoreSnapshot();
|
|
368
|
+
});
|
|
369
|
+
it("should revert", async () => {
|
|
370
|
+
await (0, chai_1.expect)(
|
|
371
|
+
// Parameters others than the first doesn't matter in this scenario.
|
|
372
|
+
bridge.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, [], 0, [])).to.be.revertedWith("Source wallet must handle all pending redemptions first");
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
context("when source wallet is not in the MovingFunds state", () => {
|
|
377
|
+
const testData = [
|
|
378
|
+
{
|
|
379
|
+
testName: "when the source wallet is in the Unknown state",
|
|
380
|
+
state: fixtures_1.walletState.Unknown,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
testName: "when the source wallet is in the Live state",
|
|
384
|
+
state: fixtures_1.walletState.Live,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
testName: "when the source wallet is in the Closing state",
|
|
388
|
+
state: fixtures_1.walletState.Closing,
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
testName: "when the source wallet is in the Closed state",
|
|
392
|
+
state: fixtures_1.walletState.Closed,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
testName: "when the source wallet is in the Terminated state",
|
|
396
|
+
state: fixtures_1.walletState.Terminated,
|
|
397
|
+
},
|
|
398
|
+
];
|
|
399
|
+
testData.forEach((test) => {
|
|
400
|
+
context(test.testName, () => {
|
|
401
|
+
before(async () => {
|
|
402
|
+
await createSnapshot();
|
|
403
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
404
|
+
...walletDraft,
|
|
405
|
+
state: test.state,
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
after(async () => {
|
|
409
|
+
await restoreSnapshot();
|
|
410
|
+
});
|
|
411
|
+
it("should revert", async () => {
|
|
412
|
+
await (0, chai_1.expect)(
|
|
413
|
+
// Parameters other than the first doesn't matter in this scenario.
|
|
414
|
+
bridge.submitMovingFundsCommitment(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO, [], 0, [])).to.be.revertedWith("Source wallet must be in MovingFunds state");
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
describe("resetMovingFundsTimeout", () => {
|
|
421
|
+
const walletDraft = {
|
|
422
|
+
ecdsaWalletID: ecdsa_1.ecdsaWalletTestData.walletID,
|
|
423
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
424
|
+
pendingRedemptionsValue: 0,
|
|
425
|
+
createdAt: 0,
|
|
426
|
+
movingFundsRequestedAt: 0,
|
|
427
|
+
closingStartedAt: 0,
|
|
428
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
429
|
+
state: fixtures_1.walletState.Unknown,
|
|
430
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
431
|
+
};
|
|
432
|
+
context("when the wallet is in the MovingFunds state", () => {
|
|
433
|
+
before(async () => {
|
|
434
|
+
await createSnapshot();
|
|
435
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
436
|
+
...walletDraft,
|
|
437
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
after(async () => {
|
|
441
|
+
await restoreSnapshot();
|
|
442
|
+
});
|
|
443
|
+
context("when the wallet's commitment is not submitted yet", () => {
|
|
444
|
+
context("when Live wallets count is zero", () => {
|
|
445
|
+
// No need to do any specific setup. There is only one MovingFunds
|
|
446
|
+
// wallet in the system and its commitment is not yet submitted.
|
|
447
|
+
// Those preconditions are met by default.
|
|
448
|
+
context("when reset delay has elapsed", () => {
|
|
449
|
+
let tx;
|
|
450
|
+
before(async () => {
|
|
451
|
+
await createSnapshot();
|
|
452
|
+
// Set the timestamp of the block that contains the `setWallet` tx.
|
|
453
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
454
|
+
...(await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)),
|
|
455
|
+
movingFundsRequestedAt: (await lastBlockTime()) + 1,
|
|
456
|
+
});
|
|
457
|
+
await increaseTime(movingFundsTimeoutResetDelay);
|
|
458
|
+
tx = await bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
459
|
+
});
|
|
460
|
+
after(async () => {
|
|
461
|
+
await restoreSnapshot();
|
|
462
|
+
});
|
|
463
|
+
it("should reset the moving funds timeout", async () => {
|
|
464
|
+
(0, chai_1.expect)((await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160))
|
|
465
|
+
.movingFundsRequestedAt).to.be.equal(await lastBlockTime());
|
|
466
|
+
});
|
|
467
|
+
it("should emit MovingFundsTimeoutReset event", async () => {
|
|
468
|
+
await (0, chai_1.expect)(tx)
|
|
469
|
+
.to.emit(bridge, "MovingFundsTimeoutReset")
|
|
470
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
context("when reset delay has not elapsed yet", () => {
|
|
474
|
+
before(async () => {
|
|
475
|
+
await createSnapshot();
|
|
476
|
+
// Set the timestamp of the block that contains the `setWallet` tx.
|
|
477
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
478
|
+
...(await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)),
|
|
479
|
+
movingFundsRequestedAt: (await lastBlockTime()) + 1,
|
|
480
|
+
});
|
|
481
|
+
await increaseTime(movingFundsTimeoutResetDelay - 1);
|
|
482
|
+
});
|
|
483
|
+
after(async () => {
|
|
484
|
+
await restoreSnapshot();
|
|
485
|
+
});
|
|
486
|
+
it("should revert", async () => {
|
|
487
|
+
await (0, chai_1.expect)(bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).to.be.revertedWith("Moving funds timeout cannot be reset yet");
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
context("when one reset occurred and the reset delay has elapsed again", () => {
|
|
491
|
+
let tx;
|
|
492
|
+
before(async () => {
|
|
493
|
+
await createSnapshot();
|
|
494
|
+
// Set the timestamp of the block that contains the `setWallet` tx.
|
|
495
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
496
|
+
...(await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)),
|
|
497
|
+
movingFundsRequestedAt: (await lastBlockTime()) + 1,
|
|
498
|
+
});
|
|
499
|
+
await increaseTime(movingFundsTimeoutResetDelay);
|
|
500
|
+
// Reset for the first time.
|
|
501
|
+
await bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
502
|
+
// The reset delay elapses again.
|
|
503
|
+
await increaseTime(movingFundsTimeoutResetDelay);
|
|
504
|
+
// The next reset.
|
|
505
|
+
tx = await bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
506
|
+
});
|
|
507
|
+
after(async () => {
|
|
508
|
+
await restoreSnapshot();
|
|
509
|
+
});
|
|
510
|
+
it("should reset the moving funds timeout", async () => {
|
|
511
|
+
(0, chai_1.expect)((await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160))
|
|
512
|
+
.movingFundsRequestedAt).to.be.equal(await lastBlockTime());
|
|
513
|
+
});
|
|
514
|
+
it("should emit MovingFundsTimeoutReset event", async () => {
|
|
515
|
+
await (0, chai_1.expect)(tx)
|
|
516
|
+
.to.emit(bridge, "MovingFundsTimeoutReset")
|
|
517
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
context("when one reset occurred and the reset delay has not elapsed yet", () => {
|
|
521
|
+
before(async () => {
|
|
522
|
+
await createSnapshot();
|
|
523
|
+
// Set the timestamp of the block that contains the `setWallet` tx.
|
|
524
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
525
|
+
...(await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)),
|
|
526
|
+
movingFundsRequestedAt: (await lastBlockTime()) + 1,
|
|
527
|
+
});
|
|
528
|
+
await increaseTime(movingFundsTimeoutResetDelay);
|
|
529
|
+
// Reset for the first time.
|
|
530
|
+
await bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
531
|
+
// The reset delay has not elapsed again yet.
|
|
532
|
+
await increaseTime(movingFundsTimeoutResetDelay - 1);
|
|
533
|
+
});
|
|
534
|
+
after(async () => {
|
|
535
|
+
await restoreSnapshot();
|
|
536
|
+
});
|
|
537
|
+
it("should revert", async () => {
|
|
538
|
+
await (0, chai_1.expect)(bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).to.be.revertedWith("Moving funds timeout cannot be reset yet");
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
context("when Live wallets count is not zero", () => {
|
|
543
|
+
before(async () => {
|
|
544
|
+
await createSnapshot();
|
|
545
|
+
// This call will add one Live wallet and increase the Live wallet
|
|
546
|
+
// counter accordingly. Note that the wallet's public key hash
|
|
547
|
+
// must be different from the PKH of the tested wallet.
|
|
548
|
+
await bridge.setWallet("0x7ac2d9378a1c47e589dfb8095ca95ed2140d2726", {
|
|
549
|
+
...walletDraft,
|
|
550
|
+
state: fixtures_1.walletState.Live,
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
after(async () => {
|
|
554
|
+
await restoreSnapshot();
|
|
555
|
+
});
|
|
556
|
+
it("should revert", async () => {
|
|
557
|
+
await (0, chai_1.expect)(bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).to.be.revertedWith("Live wallets count must be zero");
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
context("when the wallet's commitment is already submitted", () => {
|
|
562
|
+
before(async () => {
|
|
563
|
+
await createSnapshot();
|
|
564
|
+
// Set an arbitrary non-zero commitment.
|
|
565
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
566
|
+
...(await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)),
|
|
567
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.utils.solidityKeccak256(["bytes20"], ["0xc214a5e9ec1b7792af9894e8f9ff0dd9bf427d79"]),
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
after(async () => {
|
|
571
|
+
await restoreSnapshot();
|
|
572
|
+
});
|
|
573
|
+
it("should revert", async () => {
|
|
574
|
+
await (0, chai_1.expect)(bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).to.be.revertedWith("Target wallets commitment already submitted");
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
context("when the wallet is not in the MovingFunds state", () => {
|
|
579
|
+
const testData = [
|
|
580
|
+
{
|
|
581
|
+
testName: "when the wallet is in the Unknown state",
|
|
582
|
+
walletState: fixtures_1.walletState.Unknown,
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
testName: "when the wallet is in the Live state",
|
|
586
|
+
walletState: fixtures_1.walletState.Live,
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
testName: "when the wallet is in the Closing state",
|
|
590
|
+
walletState: fixtures_1.walletState.Closing,
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
testName: "when the wallet is in the Closed state",
|
|
594
|
+
walletState: fixtures_1.walletState.Closed,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
testName: "when the wallet is in the Terminated state",
|
|
598
|
+
walletState: fixtures_1.walletState.Terminated,
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
testData.forEach((test) => {
|
|
602
|
+
context(test.testName, () => {
|
|
603
|
+
before(async () => {
|
|
604
|
+
await createSnapshot();
|
|
605
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
606
|
+
...walletDraft,
|
|
607
|
+
state: test.walletState,
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
after(async () => {
|
|
611
|
+
await restoreSnapshot();
|
|
612
|
+
});
|
|
613
|
+
it("should revert", async () => {
|
|
614
|
+
await (0, chai_1.expect)(bridge.resetMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).to.be.revertedWith("Wallet must be in MovingFunds state");
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
describe("submitMovingFundsProof", () => {
|
|
621
|
+
context("when transaction proof is valid", () => {
|
|
622
|
+
context("when there is a main UTXO for the given wallet", () => {
|
|
623
|
+
context("when main UTXO data are valid", () => {
|
|
624
|
+
context("when there is only one input", () => {
|
|
625
|
+
context("when the single input points to the wallet's main UTXO", () => {
|
|
626
|
+
context("when the output vector references only 20-byte hashes", () => {
|
|
627
|
+
context("when the output vector has only P2PKH and P2WPKH outputs", () => {
|
|
628
|
+
context("when transaction amount is distributed evenly", () => {
|
|
629
|
+
context("when transaction fee is not too high", () => {
|
|
630
|
+
context("when source wallet is in the MovingFunds state", () => {
|
|
631
|
+
context("when target wallets commitment is submitted", () => {
|
|
632
|
+
context("when actual target wallets correspond to the commitment", () => {
|
|
633
|
+
const testData = [
|
|
634
|
+
{
|
|
635
|
+
testName: "when there is a single target wallet",
|
|
636
|
+
data: moving_funds_1.SingleTargetWallet,
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
testName: "when there are multiple target wallets and the amount is indivisible",
|
|
640
|
+
data: moving_funds_1.MultipleTargetWalletsAndIndivisibleAmount,
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
testName: "when there are multiple target wallets and the amount is divisible",
|
|
644
|
+
data: moving_funds_1.MultipleTargetWalletsAndDivisibleAmount,
|
|
645
|
+
},
|
|
646
|
+
];
|
|
647
|
+
testData.forEach((test) => {
|
|
648
|
+
context(test.testName, () => {
|
|
649
|
+
let tx;
|
|
650
|
+
before(async () => {
|
|
651
|
+
await createSnapshot();
|
|
652
|
+
tx =
|
|
653
|
+
await runMovingFundsScenario(test.data);
|
|
654
|
+
});
|
|
655
|
+
after(async () => {
|
|
656
|
+
await restoreSnapshot();
|
|
657
|
+
});
|
|
658
|
+
it("should mark the main UTXO as correctly spent", async () => {
|
|
659
|
+
const key = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
660
|
+
test.data.mainUtxo
|
|
661
|
+
.txHash,
|
|
662
|
+
test.data.mainUtxo
|
|
663
|
+
.txOutputIndex,
|
|
664
|
+
]);
|
|
665
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
666
|
+
(0, chai_1.expect)(await bridge.spentMainUTXOs(key)).to.be.true;
|
|
667
|
+
});
|
|
668
|
+
it("should unset the main UTXO for the source wallet", async () => {
|
|
669
|
+
(0, chai_1.expect)((await bridge.wallets(test.data.wallet
|
|
670
|
+
.pubKeyHash)).mainUtxoHash).to.be.equal(hardhat_1.ethers.constants.HashZero);
|
|
671
|
+
});
|
|
672
|
+
it("should put the source wallet in the Closing state", async () => {
|
|
673
|
+
(0, chai_1.expect)((await bridge.wallets(test.data.wallet
|
|
674
|
+
.pubKeyHash)).state).to.be.equal(fixtures_1.walletState.Closing);
|
|
675
|
+
});
|
|
676
|
+
it("should set the closing started timestamp", async () => {
|
|
677
|
+
(0, chai_1.expect)((await bridge.wallets(test.data.wallet
|
|
678
|
+
.pubKeyHash)).closingStartedAt).to.be.equal(await lastBlockTime());
|
|
679
|
+
});
|
|
680
|
+
it("should emit the WalletClosing event", async () => {
|
|
681
|
+
await (0, chai_1.expect)(tx)
|
|
682
|
+
.to.emit(bridge, "WalletClosing")
|
|
683
|
+
.withArgs(test.data.wallet
|
|
684
|
+
.ecdsaWalletID, test.data.wallet
|
|
685
|
+
.pubKeyHash);
|
|
686
|
+
});
|
|
687
|
+
it("should emit the MovingFundsCompleted event", async () => {
|
|
688
|
+
await (0, chai_1.expect)(tx)
|
|
689
|
+
.to.emit(bridge, "MovingFundsCompleted")
|
|
690
|
+
.withArgs(test.data.wallet
|
|
691
|
+
.pubKeyHash, test.data.movingFundsTx
|
|
692
|
+
.hash);
|
|
693
|
+
});
|
|
694
|
+
it("should create appropriate moved funds sweep requests", async () => {
|
|
695
|
+
for (let i = 0; i <
|
|
696
|
+
test.data
|
|
697
|
+
.expectedMovedFundsSweepRequests
|
|
698
|
+
.length; i++) {
|
|
699
|
+
const expectedMovedFundsSweepRequest = test.data
|
|
700
|
+
.expectedMovedFundsSweepRequests[i];
|
|
701
|
+
const requestKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
702
|
+
expectedMovedFundsSweepRequest.txHash,
|
|
703
|
+
expectedMovedFundsSweepRequest.txOutputIndex,
|
|
704
|
+
]);
|
|
705
|
+
const actualMovedFundsSweepRequest =
|
|
706
|
+
// eslint-disable-next-line no-await-in-loop
|
|
707
|
+
await bridge.movedFundsSweepRequests(requestKey);
|
|
708
|
+
(0, chai_1.expect)(actualMovedFundsSweepRequest.walletPubKeyHash).to.be.equal(expectedMovedFundsSweepRequest.walletPubKeyHash, `Unexpected wallet for sweep request ${i}`);
|
|
709
|
+
(0, chai_1.expect)(actualMovedFundsSweepRequest.value).to.be.equal(expectedMovedFundsSweepRequest.txOutputValue, `Unexpected value for sweep request ${i}`);
|
|
710
|
+
(0, chai_1.expect)(actualMovedFundsSweepRequest.createdAt).to.be.equal(
|
|
711
|
+
// eslint-disable-next-line no-await-in-loop
|
|
712
|
+
await lastBlockTime(), `Unexpected created timestamp for sweep request ${i}`);
|
|
713
|
+
(0, chai_1.expect)(actualMovedFundsSweepRequest.state).to.be.equal(fixtures_1.movedFundsSweepRequestState.Pending, `Unexpected state for sweep request ${i}`);
|
|
714
|
+
/* eslint-disable no-await-in-loop */
|
|
715
|
+
(0, chai_1.expect)((await bridge.wallets(expectedMovedFundsSweepRequest.walletPubKeyHash))
|
|
716
|
+
.pendingMovedFundsSweepRequestsCount).to.be.equal(1);
|
|
717
|
+
/* eslint-enable no-await-in-loop */
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
context("when actual target wallets does not correspond to the commitment", () => {
|
|
724
|
+
// The below test data send funds to
|
|
725
|
+
// three wallets with the following
|
|
726
|
+
// 20-byte PKHs (in this order):
|
|
727
|
+
// - 0x2cd680318747b720d67bf4246eb7403b476adb34
|
|
728
|
+
// - 0x8900de8fc6e4cd1db4c7ab0759d28503b4cb0ab1
|
|
729
|
+
// - 0xaf7a841e055fc19bf31acf4cbed5ef548a2cc453
|
|
730
|
+
// If the commitment is not exactly
|
|
731
|
+
// this list, the moving funds proof
|
|
732
|
+
// will revert.
|
|
733
|
+
const data = moving_funds_1.MultipleTargetWalletsAndIndivisibleAmount;
|
|
734
|
+
const testData = [
|
|
735
|
+
{
|
|
736
|
+
testName: "when funds were sent to more wallets than submitted in the commitment",
|
|
737
|
+
modifyData: (dataCopy) =>
|
|
738
|
+
// Simulate that the commitment
|
|
739
|
+
// contains only the two first
|
|
740
|
+
// wallets used in the transaction.
|
|
741
|
+
({
|
|
742
|
+
...dataCopy,
|
|
743
|
+
targetWalletsCommitment: [
|
|
744
|
+
dataCopy
|
|
745
|
+
.targetWalletsCommitment[0],
|
|
746
|
+
dataCopy
|
|
747
|
+
.targetWalletsCommitment[1],
|
|
748
|
+
],
|
|
749
|
+
}),
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
testName: "when funds were sent to less wallets than submitted in the commitment",
|
|
753
|
+
modifyData: (dataCopy) =>
|
|
754
|
+
// Simulate that the commitment
|
|
755
|
+
// contains an additional wallet apart
|
|
756
|
+
// from all wallets used in the transaction.
|
|
757
|
+
({
|
|
758
|
+
...dataCopy,
|
|
759
|
+
targetWalletsCommitment: [
|
|
760
|
+
dataCopy
|
|
761
|
+
.targetWalletsCommitment[0],
|
|
762
|
+
dataCopy
|
|
763
|
+
.targetWalletsCommitment[1],
|
|
764
|
+
dataCopy
|
|
765
|
+
.targetWalletsCommitment[2],
|
|
766
|
+
"0xe04f5dbeafea147699fce4e0e12027aa0bc12e78",
|
|
767
|
+
],
|
|
768
|
+
}),
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
testName: "when funds were sent to completely different wallets than submitted in the commitment",
|
|
772
|
+
modifyData: (dataCopy) =>
|
|
773
|
+
// Simulate that the commitment
|
|
774
|
+
// contains three different wallets
|
|
775
|
+
// than the wallets used in the
|
|
776
|
+
// transaction.
|
|
777
|
+
({
|
|
778
|
+
...dataCopy,
|
|
779
|
+
targetWalletsCommitment: [
|
|
780
|
+
"0x1e445df2d9136831193d90c5e2c6b7ea8a0882fb",
|
|
781
|
+
"0x9d134f065a6566bcc2f99fffe21856e129638526",
|
|
782
|
+
"0x4f524b05f817bd8f3be613ff5bc5ef87f0b68b46",
|
|
783
|
+
],
|
|
784
|
+
}),
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
testName: "when funds were sent to the wallets submitted in the commitment but with a wrong order",
|
|
788
|
+
modifyData: (dataCopy) =>
|
|
789
|
+
// Simulate that the commitment
|
|
790
|
+
// contains three different wallets
|
|
791
|
+
// than the wallets used in the
|
|
792
|
+
// transaction.
|
|
793
|
+
({
|
|
794
|
+
...dataCopy,
|
|
795
|
+
targetWalletsCommitment: [
|
|
796
|
+
dataCopy
|
|
797
|
+
.targetWalletsCommitment[2],
|
|
798
|
+
dataCopy
|
|
799
|
+
.targetWalletsCommitment[1],
|
|
800
|
+
dataCopy
|
|
801
|
+
.targetWalletsCommitment[0],
|
|
802
|
+
],
|
|
803
|
+
}),
|
|
804
|
+
},
|
|
805
|
+
];
|
|
806
|
+
testData.forEach((test) => {
|
|
807
|
+
context(test.testName, () => {
|
|
808
|
+
let tx;
|
|
809
|
+
before(async () => {
|
|
810
|
+
await createSnapshot();
|
|
811
|
+
// Pass a copy of the original data.
|
|
812
|
+
const modifiedData = test.modifyData(JSON.parse(JSON.stringify(data)));
|
|
813
|
+
tx =
|
|
814
|
+
runMovingFundsScenario(modifiedData);
|
|
815
|
+
});
|
|
816
|
+
after(async () => {
|
|
817
|
+
await restoreSnapshot();
|
|
818
|
+
});
|
|
819
|
+
it("should revert", async () => {
|
|
820
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Target wallets don't correspond to the commitment");
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
context("when target wallets commitment is not submitted", () => {
|
|
827
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
828
|
+
let tx;
|
|
829
|
+
before(async () => {
|
|
830
|
+
await createSnapshot();
|
|
831
|
+
tx = runMovingFundsScenario({
|
|
832
|
+
...data,
|
|
833
|
+
targetWalletsCommitment: [],
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
after(async () => {
|
|
837
|
+
await restoreSnapshot();
|
|
838
|
+
});
|
|
839
|
+
it("should revert", async () => {
|
|
840
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Target wallets commitment not submitted yet");
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
context("when source wallet is not in the MovingFunds state", () => {
|
|
845
|
+
const testData = [
|
|
846
|
+
{
|
|
847
|
+
testName: "when wallet state is Unknown",
|
|
848
|
+
state: fixtures_1.walletState.Unknown,
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
testName: "when wallet state is Live",
|
|
852
|
+
state: fixtures_1.walletState.Live,
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
testName: "when wallet state is Closing",
|
|
856
|
+
state: fixtures_1.walletState.Closing,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
testName: "when wallet state is Closed",
|
|
860
|
+
state: fixtures_1.walletState.Closed,
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
testName: "when wallet state is Terminated",
|
|
864
|
+
state: fixtures_1.walletState.Terminated,
|
|
865
|
+
},
|
|
866
|
+
];
|
|
867
|
+
testData.forEach((test) => {
|
|
868
|
+
context(test.testName, () => {
|
|
869
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
870
|
+
let tx;
|
|
871
|
+
before(async () => {
|
|
872
|
+
await createSnapshot();
|
|
873
|
+
data.wallet.state = test.state;
|
|
874
|
+
tx = runMovingFundsScenario(data);
|
|
875
|
+
});
|
|
876
|
+
after(async () => {
|
|
877
|
+
await restoreSnapshot();
|
|
878
|
+
});
|
|
879
|
+
it("should revert", async () => {
|
|
880
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Wallet must be in MovingFunds state");
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
context("when transaction fee is too high", () => {
|
|
887
|
+
const data = moving_funds_1.SingleTargetWallet;
|
|
888
|
+
let tx;
|
|
889
|
+
before(async () => {
|
|
890
|
+
await createSnapshot();
|
|
891
|
+
const beforeProofActions = async () => {
|
|
892
|
+
// The transaction used by this scenario's
|
|
893
|
+
// test data has a fee of 9000 satoshis. Lowering
|
|
894
|
+
// the max fee in the Bridge by one should
|
|
895
|
+
// cause the expected failure.
|
|
896
|
+
await bridge.setMovingFundsTxMaxTotalFee(8999);
|
|
897
|
+
};
|
|
898
|
+
tx = runMovingFundsScenario(data, beforeProofActions);
|
|
899
|
+
});
|
|
900
|
+
after(async () => {
|
|
901
|
+
await restoreSnapshot();
|
|
902
|
+
});
|
|
903
|
+
it("should revert", async () => {
|
|
904
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Transaction fee is too high");
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
context("when transaction amount is not distributed evenly", () => {
|
|
909
|
+
const data = moving_funds_1.MultipleTargetWalletsButAmountDistributedUnevenly;
|
|
910
|
+
let tx;
|
|
911
|
+
before(async () => {
|
|
912
|
+
await createSnapshot();
|
|
913
|
+
tx = runMovingFundsScenario(data);
|
|
914
|
+
});
|
|
915
|
+
after(async () => {
|
|
916
|
+
await restoreSnapshot();
|
|
917
|
+
});
|
|
918
|
+
it("should revert", async () => {
|
|
919
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Transaction amount is not distributed evenly");
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
context("when the output vector contains P2SH output", () => {
|
|
924
|
+
// The only possible case is P2SH which contains the
|
|
925
|
+
// 20-byte payload, just like P2PKH and P2WPKH.
|
|
926
|
+
// No need to check P2WSH as it uses a 32-byte payload
|
|
927
|
+
// so it would fail earlier, at the payload length
|
|
928
|
+
// assertion.
|
|
929
|
+
const data = moving_funds_1.SingleTargetWalletButP2SH;
|
|
930
|
+
let tx;
|
|
931
|
+
before(async () => {
|
|
932
|
+
await createSnapshot();
|
|
933
|
+
tx = runMovingFundsScenario(data);
|
|
934
|
+
});
|
|
935
|
+
after(async () => {
|
|
936
|
+
await restoreSnapshot();
|
|
937
|
+
});
|
|
938
|
+
it("should revert", async () => {
|
|
939
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Output must be P2PKH or P2WPKH");
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
context("when the output vector does not only reference 20-byte hashes", () => {
|
|
944
|
+
// Use a provably unspendable output whose payload length
|
|
945
|
+
// is zero so it should cause a failure upon the assertion
|
|
946
|
+
// that makes sure the output payload is 20-byte.
|
|
947
|
+
const data = moving_funds_1.SingleProvablyUnspendable;
|
|
948
|
+
let tx;
|
|
949
|
+
before(async () => {
|
|
950
|
+
await createSnapshot();
|
|
951
|
+
tx = runMovingFundsScenario(data);
|
|
952
|
+
});
|
|
953
|
+
after(async () => {
|
|
954
|
+
await restoreSnapshot();
|
|
955
|
+
});
|
|
956
|
+
it("should revert", async () => {
|
|
957
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Output's public key hash must have 20 bytes");
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
context("when the single input doesn't point to the wallet's main UTXO", () => {
|
|
962
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
963
|
+
let tx;
|
|
964
|
+
before(async () => {
|
|
965
|
+
await createSnapshot();
|
|
966
|
+
// Corrupt the wallet's main UTXO that is injected to
|
|
967
|
+
// the Bridge state by the test runner in order to make it
|
|
968
|
+
// different than the input used by the actual Bitcoin
|
|
969
|
+
// transaction thus make the tested scenario happen. The
|
|
970
|
+
// proper value of `txOutputIndex` is `1` so any other value
|
|
971
|
+
// will do the trick.
|
|
972
|
+
data.mainUtxo.txOutputIndex = 0;
|
|
973
|
+
tx = runMovingFundsScenario(data);
|
|
974
|
+
});
|
|
975
|
+
after(async () => {
|
|
976
|
+
await restoreSnapshot();
|
|
977
|
+
});
|
|
978
|
+
it("should revert", async () => {
|
|
979
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Outbound transaction input must point to the wallet's main UTXO");
|
|
980
|
+
});
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
context("when input count is other than one", () => {
|
|
984
|
+
const data = moving_funds_1.MultipleInputs;
|
|
985
|
+
let tx;
|
|
986
|
+
before(async () => {
|
|
987
|
+
await createSnapshot();
|
|
988
|
+
tx = runMovingFundsScenario(data);
|
|
989
|
+
});
|
|
990
|
+
after(async () => {
|
|
991
|
+
await restoreSnapshot();
|
|
992
|
+
});
|
|
993
|
+
it("should revert", async () => {
|
|
994
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Outbound transaction must have a single input");
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
context("when main UTXO data are invalid", () => {
|
|
999
|
+
const data = moving_funds_1.SingleTargetWallet;
|
|
1000
|
+
before(async () => {
|
|
1001
|
+
await createSnapshot();
|
|
1002
|
+
// Required for a successful SPV proof.
|
|
1003
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
1004
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
1005
|
+
// Wallet main UTXO must be set on the Bridge side to make
|
|
1006
|
+
// that scenario happen.
|
|
1007
|
+
await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
|
|
1008
|
+
});
|
|
1009
|
+
after(async () => {
|
|
1010
|
+
relay.getPrevEpochDifficulty.reset();
|
|
1011
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
1012
|
+
await restoreSnapshot();
|
|
1013
|
+
});
|
|
1014
|
+
it("should revert", async () => {
|
|
1015
|
+
// Corrupt the main UTXO parameter passed during
|
|
1016
|
+
// `submitMovingFundsProof` call. The proper value of
|
|
1017
|
+
// `txOutputIndex` for this test data set is `1` so any other
|
|
1018
|
+
// value will make this test scenario happen.
|
|
1019
|
+
const corruptedMainUtxo = {
|
|
1020
|
+
...data.mainUtxo,
|
|
1021
|
+
txOutputIndex: 0,
|
|
1022
|
+
};
|
|
1023
|
+
await (0, chai_1.expect)(bridge.submitMovingFundsProof(data.movingFundsTx, data.movingFundsProof, corruptedMainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("Invalid main UTXO data");
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
context("when there is no main UTXO for the given wallet", () => {
|
|
1028
|
+
const data = moving_funds_1.SingleTargetWallet;
|
|
1029
|
+
before(async () => {
|
|
1030
|
+
await createSnapshot();
|
|
1031
|
+
// Required for a successful SPV proof.
|
|
1032
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
1033
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
1034
|
+
});
|
|
1035
|
+
after(async () => {
|
|
1036
|
+
relay.getPrevEpochDifficulty.reset();
|
|
1037
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
1038
|
+
await restoreSnapshot();
|
|
1039
|
+
});
|
|
1040
|
+
it("should revert", async () => {
|
|
1041
|
+
// There was no preparations before `submitMovingFundsProof` call
|
|
1042
|
+
// so no main UTXO is set for the given wallet.
|
|
1043
|
+
await (0, chai_1.expect)(bridge.submitMovingFundsProof(data.movingFundsTx, data.movingFundsProof, data.mainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("No main UTXO for given wallet");
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
context("when transaction proof is not valid", () => {
|
|
1048
|
+
context("when input vector is not valid", () => {
|
|
1049
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1050
|
+
before(async () => {
|
|
1051
|
+
await createSnapshot();
|
|
1052
|
+
});
|
|
1053
|
+
after(async () => {
|
|
1054
|
+
await restoreSnapshot();
|
|
1055
|
+
});
|
|
1056
|
+
it("should revert", async () => {
|
|
1057
|
+
// Corrupt the input vector by setting a compactSize uint claiming
|
|
1058
|
+
// there is no inputs at all.
|
|
1059
|
+
data.movingFundsTx.inputVector =
|
|
1060
|
+
"0x00b69a2869840aa6fdfd143136ff4514ca46ea2d876855040892ad74ab" +
|
|
1061
|
+
"8c5274220100000000ffffffff";
|
|
1062
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Invalid input vector provided");
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
1065
|
+
context("when output vector is not valid", () => {
|
|
1066
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1067
|
+
before(async () => {
|
|
1068
|
+
await createSnapshot();
|
|
1069
|
+
});
|
|
1070
|
+
after(async () => {
|
|
1071
|
+
await restoreSnapshot();
|
|
1072
|
+
});
|
|
1073
|
+
it("should revert", async () => {
|
|
1074
|
+
// Corrupt the output vector by setting a compactSize uint claiming
|
|
1075
|
+
// there is no outputs at all.
|
|
1076
|
+
data.movingFundsTx.outputVector =
|
|
1077
|
+
"0x005cf511000000000017a91486884e6be1525dab5ae0b451bd2c72cee6" +
|
|
1078
|
+
"7dcf4187";
|
|
1079
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Invalid output vector provided");
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
context("when merkle proof is not valid", () => {
|
|
1083
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1084
|
+
before(async () => {
|
|
1085
|
+
await createSnapshot();
|
|
1086
|
+
});
|
|
1087
|
+
after(async () => {
|
|
1088
|
+
await restoreSnapshot();
|
|
1089
|
+
});
|
|
1090
|
+
it("should revert", async () => {
|
|
1091
|
+
// Corrupt the merkle proof by changing tx index in block to an
|
|
1092
|
+
// invalid one. The proper one is 1 so any other will do the trick.
|
|
1093
|
+
data.movingFundsProof.txIndexInBlock = 30;
|
|
1094
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Tx merkle proof is not valid for provided header and tx hash");
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
1097
|
+
context("when proof difficulty is not current nor previous", () => {
|
|
1098
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1099
|
+
before(async () => {
|
|
1100
|
+
await createSnapshot();
|
|
1101
|
+
});
|
|
1102
|
+
after(async () => {
|
|
1103
|
+
await restoreSnapshot();
|
|
1104
|
+
});
|
|
1105
|
+
it("should revert", async () => {
|
|
1106
|
+
// To pass the proof validation, the difficulty returned by the relay
|
|
1107
|
+
// must be 21461933 for test data used in this scenario. Setting
|
|
1108
|
+
// a different value will cause difficulty comparison failure.
|
|
1109
|
+
data.chainDifficulty = 2;
|
|
1110
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Not at current or previous difficulty");
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
context("when headers chain length is not valid", () => {
|
|
1114
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1115
|
+
before(async () => {
|
|
1116
|
+
await createSnapshot();
|
|
1117
|
+
});
|
|
1118
|
+
after(async () => {
|
|
1119
|
+
await restoreSnapshot();
|
|
1120
|
+
});
|
|
1121
|
+
it("should revert", async () => {
|
|
1122
|
+
// Corrupt the bitcoin headers length in the moving funds proof. The
|
|
1123
|
+
// proper value is length divisible by 80 so any length violating
|
|
1124
|
+
// this rule will cause failure. In this case, we just remove the
|
|
1125
|
+
// last byte from proper headers chain.
|
|
1126
|
+
const properHeaders = data.movingFundsProof.bitcoinHeaders.toString();
|
|
1127
|
+
data.movingFundsProof.bitcoinHeaders = properHeaders.substring(0, properHeaders.length - 2);
|
|
1128
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Invalid length of the headers chain");
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
context("when headers chain is not valid", () => {
|
|
1132
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1133
|
+
before(async () => {
|
|
1134
|
+
await createSnapshot();
|
|
1135
|
+
});
|
|
1136
|
+
after(async () => {
|
|
1137
|
+
await restoreSnapshot();
|
|
1138
|
+
});
|
|
1139
|
+
it("should revert", async () => {
|
|
1140
|
+
// Bitcoin headers must form a chain to pass the proof validation.
|
|
1141
|
+
// That means the `previous block hash` encoded in the given block
|
|
1142
|
+
// header must match the actual previous header's hash. To test
|
|
1143
|
+
// that scenario, we corrupt the `previous block hash` of the
|
|
1144
|
+
// second header. Each header is 80 bytes length. First 4 bytes
|
|
1145
|
+
// of each header is `version` and 32 subsequent bytes is
|
|
1146
|
+
// `previous block hash`. Changing byte 85 of the whole chain will
|
|
1147
|
+
// do the work.
|
|
1148
|
+
const properHeaders = data.movingFundsProof.bitcoinHeaders.toString();
|
|
1149
|
+
data.movingFundsProof.bitcoinHeaders = `${properHeaders.substring(0, 170)}ff${properHeaders.substring(172)}`;
|
|
1150
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Invalid headers chain");
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
context("when the work in the header is insufficient", () => {
|
|
1154
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1155
|
+
before(async () => {
|
|
1156
|
+
await createSnapshot();
|
|
1157
|
+
});
|
|
1158
|
+
after(async () => {
|
|
1159
|
+
await restoreSnapshot();
|
|
1160
|
+
});
|
|
1161
|
+
it("should revert", async () => {
|
|
1162
|
+
// Each header encodes a `difficulty target` field in bytes 72-76.
|
|
1163
|
+
// The given header's hash (interpreted as uint) must be bigger than
|
|
1164
|
+
// the `difficulty target`. To test this scenario, we change the
|
|
1165
|
+
// last byte of the last header in such a way their hash becomes
|
|
1166
|
+
// lower than their `difficulty target`.
|
|
1167
|
+
const properHeaders = data.movingFundsProof.bitcoinHeaders.toString();
|
|
1168
|
+
data.movingFundsProof.bitcoinHeaders = `${properHeaders.substring(0, properHeaders.length - 2)}ff`;
|
|
1169
|
+
await (0, chai_1.expect)(runMovingFundsScenario(data)).to.be.revertedWith("Insufficient work in a header");
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
1172
|
+
context("when accumulated difficulty in headers chain is insufficient", () => {
|
|
1173
|
+
let otherBridge;
|
|
1174
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.SingleTargetWallet));
|
|
1175
|
+
before(async () => {
|
|
1176
|
+
await createSnapshot();
|
|
1177
|
+
// Necessary to pass the first part of proof validation.
|
|
1178
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
1179
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
1180
|
+
// Deploy another bridge which has higher `txProofDifficultyFactor`
|
|
1181
|
+
// than the original bridge. That means it will need 12 confirmations
|
|
1182
|
+
// to deem transaction proof validity. This scenario uses test
|
|
1183
|
+
// data which has only 6 confirmations. That should force the
|
|
1184
|
+
// failure we expect within this scenario.
|
|
1185
|
+
otherBridge = await BridgeFactory.deploy();
|
|
1186
|
+
await otherBridge.initialize(bank.address, relay.address, treasury.address, walletRegistry.address, 12);
|
|
1187
|
+
});
|
|
1188
|
+
after(async () => {
|
|
1189
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
1190
|
+
relay.getPrevEpochDifficulty.reset();
|
|
1191
|
+
await restoreSnapshot();
|
|
1192
|
+
});
|
|
1193
|
+
it("should revert", async () => {
|
|
1194
|
+
await (0, chai_1.expect)(otherBridge.submitMovingFundsProof(data.movingFundsTx, data.movingFundsProof, data.mainUtxo, data.wallet.pubKeyHash)).to.be.revertedWith("Insufficient accumulated difficulty in header chain");
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
describe("notifyMovingFundsTimeout", () => {
|
|
1200
|
+
const walletDraft = {
|
|
1201
|
+
ecdsaWalletID: ecdsa_1.ecdsaWalletTestData.walletID,
|
|
1202
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
1203
|
+
pendingRedemptionsValue: 0,
|
|
1204
|
+
createdAt: 0,
|
|
1205
|
+
movingFundsRequestedAt: 0,
|
|
1206
|
+
closingStartedAt: 0,
|
|
1207
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
1208
|
+
state: fixtures_1.walletState.Unknown,
|
|
1209
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
1210
|
+
};
|
|
1211
|
+
context("when source wallet is in the MovingFunds state", () => {
|
|
1212
|
+
before(async () => {
|
|
1213
|
+
await createSnapshot();
|
|
1214
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1215
|
+
...walletDraft,
|
|
1216
|
+
state: fixtures_1.walletState.Live,
|
|
1217
|
+
});
|
|
1218
|
+
// Wallet must have funds to be not closed immediately by
|
|
1219
|
+
// the following `__ecdsaWalletHeartbeatFailedCallback` call.
|
|
1220
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1221
|
+
txHash: hardhat_1.ethers.constants.HashZero,
|
|
1222
|
+
txOutputIndex: 0,
|
|
1223
|
+
txOutputValue: (0, contract_test_helpers_1.to1ePrecision)(10, 8),
|
|
1224
|
+
});
|
|
1225
|
+
// Switches the wallet to moving funds.
|
|
1226
|
+
await bridge
|
|
1227
|
+
.connect(walletRegistry.wallet)
|
|
1228
|
+
.__ecdsaWalletHeartbeatFailedCallback(ecdsa_1.ecdsaWalletTestData.walletID, ecdsa_1.ecdsaWalletTestData.publicKeyX, ecdsa_1.ecdsaWalletTestData.publicKeyY);
|
|
1229
|
+
});
|
|
1230
|
+
after(async () => {
|
|
1231
|
+
await restoreSnapshot();
|
|
1232
|
+
});
|
|
1233
|
+
context("when the moving funds process has timed out", () => {
|
|
1234
|
+
let tx;
|
|
1235
|
+
const walletMembersIDs = [1, 2, 3, 4, 5];
|
|
1236
|
+
before(async () => {
|
|
1237
|
+
await createSnapshot();
|
|
1238
|
+
walletRegistry.closeWallet.reset();
|
|
1239
|
+
walletRegistry.seize.reset();
|
|
1240
|
+
await increaseTime(movingFundsTimeout);
|
|
1241
|
+
tx = await bridge
|
|
1242
|
+
.connect(thirdParty)
|
|
1243
|
+
.notifyMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, walletMembersIDs);
|
|
1244
|
+
});
|
|
1245
|
+
after(async () => {
|
|
1246
|
+
walletRegistry.closeWallet.reset();
|
|
1247
|
+
walletRegistry.seize.reset();
|
|
1248
|
+
await restoreSnapshot();
|
|
1249
|
+
});
|
|
1250
|
+
it("should switch the wallet to Terminated state", async () => {
|
|
1251
|
+
(0, chai_1.expect)((await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160)).state).to.be.equal(fixtures_1.walletState.Terminated);
|
|
1252
|
+
});
|
|
1253
|
+
it("should emit WalletTerminated event", async () => {
|
|
1254
|
+
await (0, chai_1.expect)(tx)
|
|
1255
|
+
.to.emit(bridge, "WalletTerminated")
|
|
1256
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.walletID, ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1257
|
+
});
|
|
1258
|
+
it("should call ECDSA Wallet Registry's closeWallet function", async () => {
|
|
1259
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1260
|
+
(0, chai_1.expect)(walletRegistry.closeWallet).to.have.been.calledOnceWith(ecdsa_1.ecdsaWalletTestData.walletID);
|
|
1261
|
+
});
|
|
1262
|
+
it("should call the ECDSA wallet registry's seize function", async () => {
|
|
1263
|
+
(0, chai_1.expect)(walletRegistry.seize).to.have.been.calledOnceWith(movingFundsTimeoutSlashingAmount, movingFundsTimeoutNotifierRewardMultiplier, await thirdParty.getAddress(), ecdsa_1.ecdsaWalletTestData.walletID, walletMembersIDs);
|
|
1264
|
+
});
|
|
1265
|
+
it("should emit MovingFundsTimedOut event", async () => {
|
|
1266
|
+
await (0, chai_1.expect)(tx)
|
|
1267
|
+
.to.emit(bridge, "MovingFundsTimedOut")
|
|
1268
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
context("when the moving funds process has not timed out", () => {
|
|
1272
|
+
before(async () => {
|
|
1273
|
+
await createSnapshot();
|
|
1274
|
+
await increaseTime(movingFundsTimeout - 1);
|
|
1275
|
+
});
|
|
1276
|
+
after(async () => {
|
|
1277
|
+
await restoreSnapshot();
|
|
1278
|
+
});
|
|
1279
|
+
it("should revert", async () => {
|
|
1280
|
+
await (0, chai_1.expect)(bridge.notifyMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, [])).to.be.revertedWith("Moving funds has not timed out yet");
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
context("when source wallet is not in the MovingFunds state", () => {
|
|
1285
|
+
const testData = [
|
|
1286
|
+
{
|
|
1287
|
+
testName: "when the source wallet is in the Unknown state",
|
|
1288
|
+
state: fixtures_1.walletState.Unknown,
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
testName: "when the source wallet is in the Live state",
|
|
1292
|
+
state: fixtures_1.walletState.Live,
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
testName: "when the source wallet is in the Closing state",
|
|
1296
|
+
state: fixtures_1.walletState.Closing,
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
testName: "when the source wallet is in the Closed state",
|
|
1300
|
+
state: fixtures_1.walletState.Closed,
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
testName: "when the source wallet is in the Terminated state",
|
|
1304
|
+
state: fixtures_1.walletState.Terminated,
|
|
1305
|
+
},
|
|
1306
|
+
];
|
|
1307
|
+
testData.forEach((test) => {
|
|
1308
|
+
context(test.testName, () => {
|
|
1309
|
+
before(async () => {
|
|
1310
|
+
await createSnapshot();
|
|
1311
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1312
|
+
...walletDraft,
|
|
1313
|
+
state: test.state,
|
|
1314
|
+
});
|
|
1315
|
+
});
|
|
1316
|
+
after(async () => {
|
|
1317
|
+
await restoreSnapshot();
|
|
1318
|
+
});
|
|
1319
|
+
it("should revert", async () => {
|
|
1320
|
+
await (0, chai_1.expect)(bridge.notifyMovingFundsTimeout(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, [])).to.be.revertedWith("Wallet must be in MovingFunds state");
|
|
1321
|
+
});
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
});
|
|
1325
|
+
});
|
|
1326
|
+
describe("notifyMovingFundsBelowDust", () => {
|
|
1327
|
+
const walletDraft = {
|
|
1328
|
+
ecdsaWalletID: ecdsa_1.ecdsaWalletTestData.walletID,
|
|
1329
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
1330
|
+
pendingRedemptionsValue: 0,
|
|
1331
|
+
createdAt: 0,
|
|
1332
|
+
movingFundsRequestedAt: 0,
|
|
1333
|
+
closingStartedAt: 0,
|
|
1334
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
1335
|
+
state: fixtures_1.walletState.Unknown,
|
|
1336
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
1337
|
+
};
|
|
1338
|
+
context("when the wallet is in the MovingFunds state", () => {
|
|
1339
|
+
before(async () => {
|
|
1340
|
+
await createSnapshot();
|
|
1341
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1342
|
+
...walletDraft,
|
|
1343
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
after(async () => {
|
|
1347
|
+
await restoreSnapshot();
|
|
1348
|
+
});
|
|
1349
|
+
context("when the main UTXO parameter is valid", () => {
|
|
1350
|
+
context("when the balance is below the dust threshold", () => {
|
|
1351
|
+
const mainUtxo = {
|
|
1352
|
+
txHash: hardhat_1.ethers.constants.HashZero,
|
|
1353
|
+
txOutputIndex: 0,
|
|
1354
|
+
txOutputValue: fixtures_1.constants.movingFundsDustThreshold - 1,
|
|
1355
|
+
};
|
|
1356
|
+
let tx;
|
|
1357
|
+
before(async () => {
|
|
1358
|
+
await createSnapshot();
|
|
1359
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
1360
|
+
tx = await bridge.notifyMovingFundsBelowDust(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
1361
|
+
});
|
|
1362
|
+
after(async () => {
|
|
1363
|
+
await restoreSnapshot();
|
|
1364
|
+
});
|
|
1365
|
+
it("should change wallet's state to Closing", async () => {
|
|
1366
|
+
const { state } = await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1367
|
+
(0, chai_1.expect)(state).to.be.equal(fixtures_1.walletState.Closing);
|
|
1368
|
+
});
|
|
1369
|
+
it("should set the wallet's closing started timestamp", async () => {
|
|
1370
|
+
const wallet = await bridge.wallets(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1371
|
+
(0, chai_1.expect)(wallet.closingStartedAt).to.be.equal(await lastBlockTime());
|
|
1372
|
+
});
|
|
1373
|
+
it("should emit WalletClosing event", async () => {
|
|
1374
|
+
await (0, chai_1.expect)(tx)
|
|
1375
|
+
.to.emit(bridge, "WalletClosing")
|
|
1376
|
+
.withArgs(walletDraft.ecdsaWalletID, ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1377
|
+
});
|
|
1378
|
+
it("should emit MovingFundsBelowDustReported event", async () => {
|
|
1379
|
+
await (0, chai_1.expect)(tx)
|
|
1380
|
+
.to.emit(bridge, "MovingFundsBelowDustReported")
|
|
1381
|
+
.withArgs(ecdsa_1.ecdsaWalletTestData.pubKeyHash160);
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
context("when the balance is not below the dust threshold", () => {
|
|
1385
|
+
const mainUtxo = {
|
|
1386
|
+
txHash: hardhat_1.ethers.constants.HashZero,
|
|
1387
|
+
txOutputIndex: 0,
|
|
1388
|
+
txOutputValue: fixtures_1.constants.movingFundsDustThreshold,
|
|
1389
|
+
};
|
|
1390
|
+
before(async () => {
|
|
1391
|
+
await createSnapshot();
|
|
1392
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
1393
|
+
});
|
|
1394
|
+
after(async () => {
|
|
1395
|
+
await restoreSnapshot();
|
|
1396
|
+
});
|
|
1397
|
+
it("should revert", async () => {
|
|
1398
|
+
await (0, chai_1.expect)(bridge.notifyMovingFundsBelowDust(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo)).to.be.revertedWith("Wallet BTC balance must be below the moving funds dust threshold");
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
});
|
|
1402
|
+
context("when the main UTXO parameter is invalid", () => {
|
|
1403
|
+
const mainUtxo = {
|
|
1404
|
+
txHash: hardhat_1.ethers.constants.HashZero,
|
|
1405
|
+
txOutputIndex: 0,
|
|
1406
|
+
txOutputValue: (0, contract_test_helpers_1.to1ePrecision)(1, 8),
|
|
1407
|
+
};
|
|
1408
|
+
before(async () => {
|
|
1409
|
+
await createSnapshot();
|
|
1410
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1411
|
+
...walletDraft,
|
|
1412
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
1413
|
+
});
|
|
1414
|
+
await bridge.setWalletMainUtxo(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, mainUtxo);
|
|
1415
|
+
});
|
|
1416
|
+
after(async () => {
|
|
1417
|
+
await restoreSnapshot();
|
|
1418
|
+
});
|
|
1419
|
+
it("should revert", async () => {
|
|
1420
|
+
const corruptedMainUtxo = {
|
|
1421
|
+
...mainUtxo,
|
|
1422
|
+
txOutputIndex: 1,
|
|
1423
|
+
};
|
|
1424
|
+
await (0, chai_1.expect)(bridge.notifyMovingFundsBelowDust(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, corruptedMainUtxo)).to.be.revertedWith("Invalid wallet main UTXO data");
|
|
1425
|
+
});
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
context("when the wallet is not in the MovingFunds state", () => {
|
|
1429
|
+
const testData = [
|
|
1430
|
+
{
|
|
1431
|
+
testName: "when wallet state is Unknown",
|
|
1432
|
+
walletState: fixtures_1.walletState.Unknown,
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
testName: "when wallet state is Live",
|
|
1436
|
+
walletState: fixtures_1.walletState.Live,
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
testName: "when wallet state is Closing",
|
|
1440
|
+
walletState: fixtures_1.walletState.Closing,
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
testName: "when wallet state is Closed",
|
|
1444
|
+
walletState: fixtures_1.walletState.Closed,
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
testName: "when wallet state is Terminated",
|
|
1448
|
+
walletState: fixtures_1.walletState.Terminated,
|
|
1449
|
+
},
|
|
1450
|
+
];
|
|
1451
|
+
testData.forEach((test) => {
|
|
1452
|
+
context(test.testName, () => {
|
|
1453
|
+
before(async () => {
|
|
1454
|
+
await createSnapshot();
|
|
1455
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, {
|
|
1456
|
+
...walletDraft,
|
|
1457
|
+
state: test.walletState,
|
|
1458
|
+
});
|
|
1459
|
+
});
|
|
1460
|
+
after(async () => {
|
|
1461
|
+
await restoreSnapshot();
|
|
1462
|
+
});
|
|
1463
|
+
it("should revert", async () => {
|
|
1464
|
+
await (0, chai_1.expect)(bridge.notifyMovingFundsBelowDust(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, deposit_sweep_1.NO_MAIN_UTXO)).to.be.revertedWith("Wallet must be in MovingFunds state");
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
});
|
|
1470
|
+
describe("submitMovedFundsSweepProof", () => {
|
|
1471
|
+
context("when transaction proof is valid", () => {
|
|
1472
|
+
context("when there is only one output", () => {
|
|
1473
|
+
context("when the single output is 20-byte", () => {
|
|
1474
|
+
context("when single output is either P2PKH or P2WPKH", () => {
|
|
1475
|
+
context("when sweeping wallet is either in the Live or MovingFunds state", () => {
|
|
1476
|
+
context("when sweeping wallet is in the Live state", () => {
|
|
1477
|
+
context("when main UTXO data are valid", () => {
|
|
1478
|
+
context("when transaction fee does not exceed the sweep transaction maximum fee", () => {
|
|
1479
|
+
context("when the sweeping wallet has no main UTXO set", () => {
|
|
1480
|
+
context("when there is a single input referring to a Pending sweep request", () => {
|
|
1481
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1482
|
+
let tx;
|
|
1483
|
+
before(async () => {
|
|
1484
|
+
await createSnapshot();
|
|
1485
|
+
tx = await runMovedFundsSweepScenario(data);
|
|
1486
|
+
});
|
|
1487
|
+
after(async () => {
|
|
1488
|
+
await restoreSnapshot();
|
|
1489
|
+
});
|
|
1490
|
+
it("should mark the sweep request as processed", async () => {
|
|
1491
|
+
const key = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
1492
|
+
data.movedFundsSweepRequest.txHash,
|
|
1493
|
+
data.movedFundsSweepRequest.txOutputIndex,
|
|
1494
|
+
]);
|
|
1495
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1496
|
+
(0, chai_1.expect)((await bridge.movedFundsSweepRequests(key))
|
|
1497
|
+
.state).to.be.equal(fixtures_1.movedFundsSweepRequestState.Processed);
|
|
1498
|
+
});
|
|
1499
|
+
it("should decrease the sweeping wallet's pending requests count", async () => {
|
|
1500
|
+
// The `setPendingMovedFundsSweepRequest` call
|
|
1501
|
+
// made as part of `runMovedFundsSweepScenario`
|
|
1502
|
+
// set this counter to 1. Eventually, it
|
|
1503
|
+
// should be decreased back to 0.
|
|
1504
|
+
(0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).pendingMovedFundsSweepRequestsCount).to.be.equal(0);
|
|
1505
|
+
});
|
|
1506
|
+
it("should set the transaction output as new sweeping wallet main UTXO", async () => {
|
|
1507
|
+
// Amount can be checked by opening the sweep tx
|
|
1508
|
+
// in a Bitcoin testnet explorer. In this case,
|
|
1509
|
+
// the output value is 16500.
|
|
1510
|
+
const expectedMainUtxoHash = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 16500]);
|
|
1511
|
+
(0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).mainUtxoHash).to.be.equal(expectedMainUtxoHash);
|
|
1512
|
+
});
|
|
1513
|
+
it("should emit the MovedFundsSwept event", async () => {
|
|
1514
|
+
await (0, chai_1.expect)(tx)
|
|
1515
|
+
.to.emit(bridge, "MovedFundsSwept")
|
|
1516
|
+
.withArgs(data.wallet.pubKeyHash, data.sweepTx.hash);
|
|
1517
|
+
});
|
|
1518
|
+
});
|
|
1519
|
+
context("when the single input does not refer to a Pending sweep request", () => {
|
|
1520
|
+
context("when the single input refers to an Unknown sweep request", () => {
|
|
1521
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1522
|
+
before(async () => {
|
|
1523
|
+
await createSnapshot();
|
|
1524
|
+
});
|
|
1525
|
+
after(async () => {
|
|
1526
|
+
await restoreSnapshot();
|
|
1527
|
+
});
|
|
1528
|
+
it("should revert", async () => {
|
|
1529
|
+
// Getting rid of the `movedFundsSweepRequest`
|
|
1530
|
+
// allows running that scenario because
|
|
1531
|
+
// the sweep request will not exist in the system.
|
|
1532
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1533
|
+
...data,
|
|
1534
|
+
movedFundsSweepRequest: null,
|
|
1535
|
+
})).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1536
|
+
});
|
|
1537
|
+
});
|
|
1538
|
+
context("when the single input refers to a Processed sweep request", () => {
|
|
1539
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1540
|
+
let tx;
|
|
1541
|
+
before(async () => {
|
|
1542
|
+
await createSnapshot();
|
|
1543
|
+
const beforeProofActions = async () => {
|
|
1544
|
+
await bridge.processPendingMovedFundsSweepRequest(data.movedFundsSweepRequest
|
|
1545
|
+
.walletPubKeyHash, data.movedFundsSweepRequest);
|
|
1546
|
+
};
|
|
1547
|
+
tx = runMovedFundsSweepScenario(data, beforeProofActions);
|
|
1548
|
+
});
|
|
1549
|
+
after(async () => {
|
|
1550
|
+
await restoreSnapshot();
|
|
1551
|
+
});
|
|
1552
|
+
it("should revert", async () => {
|
|
1553
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1554
|
+
});
|
|
1555
|
+
});
|
|
1556
|
+
context("when the single input refers to a TimedOut sweep request", () => {
|
|
1557
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1558
|
+
let tx;
|
|
1559
|
+
before(async () => {
|
|
1560
|
+
await createSnapshot();
|
|
1561
|
+
const beforeProofActions = async () => {
|
|
1562
|
+
await bridge.timeoutPendingMovedFundsSweepRequest(data.movedFundsSweepRequest
|
|
1563
|
+
.walletPubKeyHash, data.movedFundsSweepRequest);
|
|
1564
|
+
};
|
|
1565
|
+
tx = runMovedFundsSweepScenario(data, beforeProofActions);
|
|
1566
|
+
});
|
|
1567
|
+
after(async () => {
|
|
1568
|
+
await restoreSnapshot();
|
|
1569
|
+
});
|
|
1570
|
+
it("should revert", async () => {
|
|
1571
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
1574
|
+
});
|
|
1575
|
+
context("when the single input does refer to a Pending sweep request that belongs to another wallet", () => {
|
|
1576
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1577
|
+
before(async () => {
|
|
1578
|
+
await createSnapshot();
|
|
1579
|
+
});
|
|
1580
|
+
after(async () => {
|
|
1581
|
+
await restoreSnapshot();
|
|
1582
|
+
});
|
|
1583
|
+
it("should revert", async () => {
|
|
1584
|
+
// To make this scenario happen, we just
|
|
1585
|
+
// change the wallet in the test data' sweep
|
|
1586
|
+
// request.
|
|
1587
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1588
|
+
...data,
|
|
1589
|
+
movedFundsSweepRequest: {
|
|
1590
|
+
...data.movedFundsSweepRequest,
|
|
1591
|
+
walletPubKeyHash: "0x7ac2d9378a1c47e589dfb8095ca95ed2140d2726",
|
|
1592
|
+
},
|
|
1593
|
+
})).to.be.revertedWith("Sweep request belongs to another wallet");
|
|
1594
|
+
});
|
|
1595
|
+
});
|
|
1596
|
+
context("when the number of inputs is other than one", () => {
|
|
1597
|
+
// Use a test data that contains a two-input
|
|
1598
|
+
// transaction.
|
|
1599
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1600
|
+
before(async () => {
|
|
1601
|
+
await createSnapshot();
|
|
1602
|
+
});
|
|
1603
|
+
after(async () => {
|
|
1604
|
+
await restoreSnapshot();
|
|
1605
|
+
});
|
|
1606
|
+
it("should revert", async () => {
|
|
1607
|
+
// However, do not set wallet main UTXO. In
|
|
1608
|
+
// that case, the system will expect a
|
|
1609
|
+
// sweep transaction with a single input.
|
|
1610
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1611
|
+
...data,
|
|
1612
|
+
mainUtxo: deposit_sweep_1.NO_MAIN_UTXO,
|
|
1613
|
+
})).to.be.revertedWith("Moved funds sweep transaction must have a proper inputs count");
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
});
|
|
1617
|
+
context("when the sweeping wallet has a main UTXO set", () => {
|
|
1618
|
+
context("when the first input refers to a Pending sweep request and the second input refers to the sweeping wallet main UTXO", () => {
|
|
1619
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1620
|
+
let tx;
|
|
1621
|
+
before(async () => {
|
|
1622
|
+
await createSnapshot();
|
|
1623
|
+
tx = await runMovedFundsSweepScenario(data);
|
|
1624
|
+
});
|
|
1625
|
+
after(async () => {
|
|
1626
|
+
await restoreSnapshot();
|
|
1627
|
+
});
|
|
1628
|
+
it("should mark the sweep request as processed", async () => {
|
|
1629
|
+
const key = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
1630
|
+
data.movedFundsSweepRequest.txHash,
|
|
1631
|
+
data.movedFundsSweepRequest.txOutputIndex,
|
|
1632
|
+
]);
|
|
1633
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1634
|
+
(0, chai_1.expect)((await bridge.movedFundsSweepRequests(key))
|
|
1635
|
+
.state).to.be.equal(fixtures_1.movedFundsSweepRequestState.Processed);
|
|
1636
|
+
});
|
|
1637
|
+
it("should decrease the sweeping wallet's pending requests count", async () => {
|
|
1638
|
+
// The `setPendingMovedFundsSweepRequest` call
|
|
1639
|
+
// made as part of `runMovedFundsSweepScenario`
|
|
1640
|
+
// set this counter to 1. Eventually, it
|
|
1641
|
+
// should be decreased back to 0.
|
|
1642
|
+
(0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).pendingMovedFundsSweepRequestsCount).to.be.equal(0);
|
|
1643
|
+
});
|
|
1644
|
+
it("should set the transaction output as new sweeping wallet main UTXO", async () => {
|
|
1645
|
+
// Amount can be checked by opening the sweep tx
|
|
1646
|
+
// in a Bitcoin testnet explorer. In this case,
|
|
1647
|
+
// the output value is 2612530.
|
|
1648
|
+
const expectedMainUtxoHash = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32", "uint64"], [data.sweepTx.hash, 0, 2612530]);
|
|
1649
|
+
(0, chai_1.expect)((await bridge.wallets(data.wallet.pubKeyHash)).mainUtxoHash).to.be.equal(expectedMainUtxoHash);
|
|
1650
|
+
});
|
|
1651
|
+
it("should emit the MovedFundsSwept event", async () => {
|
|
1652
|
+
await (0, chai_1.expect)(tx)
|
|
1653
|
+
.to.emit(bridge, "MovedFundsSwept")
|
|
1654
|
+
.withArgs(data.wallet.pubKeyHash, data.sweepTx.hash);
|
|
1655
|
+
});
|
|
1656
|
+
it("should mark the current sweeping wallet main UTXO as correctly spent", async () => {
|
|
1657
|
+
const key = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
1658
|
+
data.mainUtxo.txHash,
|
|
1659
|
+
data.mainUtxo.txOutputIndex,
|
|
1660
|
+
]);
|
|
1661
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1662
|
+
(0, chai_1.expect)(await bridge.spentMainUTXOs(key)).to.be
|
|
1663
|
+
.true;
|
|
1664
|
+
});
|
|
1665
|
+
});
|
|
1666
|
+
context("when the first input refers to the sweeping wallet main UTXO and the second input refers to a Pending sweep request", () => {
|
|
1667
|
+
// The sweep transaction used by this test data
|
|
1668
|
+
// has two inputs. The first input is registered
|
|
1669
|
+
// as a sweep request (i.e. it is referred by
|
|
1670
|
+
// `movedFundsSweepRequest`) and the second one
|
|
1671
|
+
// is meant to be the main UTXO (i.e. it is
|
|
1672
|
+
// referred by `mainUtxo`).
|
|
1673
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1674
|
+
before(async () => {
|
|
1675
|
+
await createSnapshot();
|
|
1676
|
+
});
|
|
1677
|
+
after(async () => {
|
|
1678
|
+
await restoreSnapshot();
|
|
1679
|
+
});
|
|
1680
|
+
it("should revert", async () => {
|
|
1681
|
+
// To make that scenario happen, we just
|
|
1682
|
+
// let the test runner to register the first
|
|
1683
|
+
// input as the main UTXO and the second
|
|
1684
|
+
// one as the sweep request.
|
|
1685
|
+
const movedFundsSweepRequest = {
|
|
1686
|
+
...data.mainUtxo,
|
|
1687
|
+
walletPubKeyHash: data.movedFundsSweepRequest
|
|
1688
|
+
.walletPubKeyHash,
|
|
1689
|
+
};
|
|
1690
|
+
const mainUtxo = {
|
|
1691
|
+
...data.movedFundsSweepRequest,
|
|
1692
|
+
};
|
|
1693
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1694
|
+
...data,
|
|
1695
|
+
movedFundsSweepRequest,
|
|
1696
|
+
mainUtxo,
|
|
1697
|
+
})).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
context("when the first input does not refer to a Pending sweep request and the second input refers to the sweeping wallet main UTXO", () => {
|
|
1701
|
+
context("when the first input refers to an Unknown sweep request", () => {
|
|
1702
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1703
|
+
before(async () => {
|
|
1704
|
+
await createSnapshot();
|
|
1705
|
+
});
|
|
1706
|
+
after(async () => {
|
|
1707
|
+
await restoreSnapshot();
|
|
1708
|
+
});
|
|
1709
|
+
it("should revert", async () => {
|
|
1710
|
+
// Getting rid of the `movedFundsSweepRequest`
|
|
1711
|
+
// allows running that scenario because
|
|
1712
|
+
// the sweep request will not exist in the system.
|
|
1713
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1714
|
+
...data,
|
|
1715
|
+
movedFundsSweepRequest: null,
|
|
1716
|
+
})).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1717
|
+
});
|
|
1718
|
+
});
|
|
1719
|
+
context("when the first input refers to a Processed sweep request", () => {
|
|
1720
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1721
|
+
let tx;
|
|
1722
|
+
before(async () => {
|
|
1723
|
+
await createSnapshot();
|
|
1724
|
+
const beforeProofActions = async () => {
|
|
1725
|
+
await bridge.processPendingMovedFundsSweepRequest(data.movedFundsSweepRequest
|
|
1726
|
+
.walletPubKeyHash, data.movedFundsSweepRequest);
|
|
1727
|
+
};
|
|
1728
|
+
tx = runMovedFundsSweepScenario(data, beforeProofActions);
|
|
1729
|
+
});
|
|
1730
|
+
after(async () => {
|
|
1731
|
+
await restoreSnapshot();
|
|
1732
|
+
});
|
|
1733
|
+
it("should revert", async () => {
|
|
1734
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1737
|
+
context("when the first input refers to a TimedOut sweep request", () => {
|
|
1738
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1739
|
+
let tx;
|
|
1740
|
+
before(async () => {
|
|
1741
|
+
await createSnapshot();
|
|
1742
|
+
const beforeProofActions = async () => {
|
|
1743
|
+
await bridge.timeoutPendingMovedFundsSweepRequest(data.movedFundsSweepRequest
|
|
1744
|
+
.walletPubKeyHash, data.movedFundsSweepRequest);
|
|
1745
|
+
};
|
|
1746
|
+
tx = runMovedFundsSweepScenario(data, beforeProofActions);
|
|
1747
|
+
});
|
|
1748
|
+
after(async () => {
|
|
1749
|
+
await restoreSnapshot();
|
|
1750
|
+
});
|
|
1751
|
+
it("should revert", async () => {
|
|
1752
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Sweep request must be in Pending state");
|
|
1753
|
+
});
|
|
1754
|
+
});
|
|
1755
|
+
});
|
|
1756
|
+
context("when the first input refers to a Pending sweep request that belongs to another wallet and the second input refers to the sweeping wallet main UTXO", () => {
|
|
1757
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1758
|
+
before(async () => {
|
|
1759
|
+
await createSnapshot();
|
|
1760
|
+
});
|
|
1761
|
+
after(async () => {
|
|
1762
|
+
await restoreSnapshot();
|
|
1763
|
+
});
|
|
1764
|
+
it("should revert", async () => {
|
|
1765
|
+
// To make this scenario happen, we just
|
|
1766
|
+
// change the wallet in the test data' sweep
|
|
1767
|
+
// request.
|
|
1768
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1769
|
+
...data,
|
|
1770
|
+
movedFundsSweepRequest: {
|
|
1771
|
+
...data.movedFundsSweepRequest,
|
|
1772
|
+
walletPubKeyHash: "0x8db50eb52063ea9d98b3eac91489a90f738986f6",
|
|
1773
|
+
},
|
|
1774
|
+
})).to.be.revertedWith("Sweep request belongs to another wallet");
|
|
1775
|
+
});
|
|
1776
|
+
});
|
|
1777
|
+
context("when the first input refers to a Pending sweep request and the second input does not refer to the sweeping wallet main UTXO", () => {
|
|
1778
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1779
|
+
before(async () => {
|
|
1780
|
+
await createSnapshot();
|
|
1781
|
+
});
|
|
1782
|
+
after(async () => {
|
|
1783
|
+
await restoreSnapshot();
|
|
1784
|
+
});
|
|
1785
|
+
it("should revert", async () => {
|
|
1786
|
+
// To make this scenario happen, we just need
|
|
1787
|
+
// to simulate that the sweeping wallet has
|
|
1788
|
+
// a different main UTXO than the one used
|
|
1789
|
+
// by the second transaction input.
|
|
1790
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1791
|
+
...data,
|
|
1792
|
+
mainUtxo: {
|
|
1793
|
+
...data.mainUtxo,
|
|
1794
|
+
txOutputIndex: 2,
|
|
1795
|
+
},
|
|
1796
|
+
})).to.be.revertedWith("Second input must point to the wallet's main UTXO");
|
|
1797
|
+
});
|
|
1798
|
+
});
|
|
1799
|
+
context("when the number of inputs is other than two", () => {
|
|
1800
|
+
// Use a test data with a one-input transaction.
|
|
1801
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1802
|
+
before(async () => {
|
|
1803
|
+
await createSnapshot();
|
|
1804
|
+
});
|
|
1805
|
+
after(async () => {
|
|
1806
|
+
await restoreSnapshot();
|
|
1807
|
+
});
|
|
1808
|
+
it("should revert", async () => {
|
|
1809
|
+
// However, register a main UTXO for the
|
|
1810
|
+
// sweeping wallet in order to force the
|
|
1811
|
+
// system to expect a two-input transaction
|
|
1812
|
+
// for that sweeping wallet.
|
|
1813
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario({
|
|
1814
|
+
...data,
|
|
1815
|
+
// Just an arbitrary main UTXO
|
|
1816
|
+
mainUtxo: {
|
|
1817
|
+
txHash: "0x7d5f7d4ae705d6adb8a402e5cd7f25f839a3f3ed243a8961c8ac5887d5aaf528",
|
|
1818
|
+
txOutputIndex: 0,
|
|
1819
|
+
txOutputValue: 873510,
|
|
1820
|
+
},
|
|
1821
|
+
})).to.be.revertedWith("Moved funds sweep transaction must have a proper inputs count");
|
|
1822
|
+
});
|
|
1823
|
+
});
|
|
1824
|
+
});
|
|
1825
|
+
});
|
|
1826
|
+
context("when transaction fee exceeds the sweep transaction maximum fee", () => {
|
|
1827
|
+
// Use a test data where the sweep transaction has
|
|
1828
|
+
// a fee of 2000 satoshi.
|
|
1829
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1830
|
+
before(async () => {
|
|
1831
|
+
await createSnapshot();
|
|
1832
|
+
// Set the max fee to one satoshi less than the fee
|
|
1833
|
+
// used by the transaction.
|
|
1834
|
+
await bridge.setMovedFundsSweepTxMaxTotalFee(1999);
|
|
1835
|
+
});
|
|
1836
|
+
after(async () => {
|
|
1837
|
+
await restoreSnapshot();
|
|
1838
|
+
});
|
|
1839
|
+
it("should revert", async () => {
|
|
1840
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Transaction fee is too high");
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
});
|
|
1844
|
+
context("when main UTXO data are invalid", () => {
|
|
1845
|
+
const data = moving_funds_1.MovedFundsSweepWithMainUtxo;
|
|
1846
|
+
let tx;
|
|
1847
|
+
before(async () => {
|
|
1848
|
+
await createSnapshot();
|
|
1849
|
+
const beforeProofAction = async () => {
|
|
1850
|
+
// Swap the main UTXO just before the proof to make
|
|
1851
|
+
// this scenario happen.
|
|
1852
|
+
await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, {
|
|
1853
|
+
...data.mainUtxo,
|
|
1854
|
+
txOutputIndex: 2,
|
|
1855
|
+
});
|
|
1856
|
+
};
|
|
1857
|
+
tx = runMovedFundsSweepScenario(data, beforeProofAction);
|
|
1858
|
+
});
|
|
1859
|
+
after(async () => {
|
|
1860
|
+
await restoreSnapshot();
|
|
1861
|
+
});
|
|
1862
|
+
it("should revert", async () => {
|
|
1863
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Invalid main UTXO data");
|
|
1864
|
+
});
|
|
1865
|
+
});
|
|
1866
|
+
});
|
|
1867
|
+
context("when sweeping wallet is in the MovingFunds state", () => {
|
|
1868
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1869
|
+
let tx;
|
|
1870
|
+
before(async () => {
|
|
1871
|
+
await createSnapshot();
|
|
1872
|
+
tx = runMovedFundsSweepScenario({
|
|
1873
|
+
...data,
|
|
1874
|
+
wallet: {
|
|
1875
|
+
...data.wallet,
|
|
1876
|
+
state: fixtures_1.walletState.MovingFunds,
|
|
1877
|
+
},
|
|
1878
|
+
});
|
|
1879
|
+
});
|
|
1880
|
+
after(async () => {
|
|
1881
|
+
await restoreSnapshot();
|
|
1882
|
+
});
|
|
1883
|
+
it("should succeed", async () => {
|
|
1884
|
+
// The assertions were already performed for Live wallet
|
|
1885
|
+
// scenarios. Here we just make sure the transaction
|
|
1886
|
+
// succeeds for a MovingFunds wallet.
|
|
1887
|
+
await (0, chai_1.expect)(tx).to.not.be.reverted;
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
});
|
|
1891
|
+
context("when sweeping wallet is neither in the Live nor MovingFunds state", () => {
|
|
1892
|
+
const testData = [
|
|
1893
|
+
{
|
|
1894
|
+
testName: "when sweeping wallet is in the Unknown state",
|
|
1895
|
+
walletState: fixtures_1.walletState.Unknown,
|
|
1896
|
+
},
|
|
1897
|
+
{
|
|
1898
|
+
testName: "when sweeping wallet is in the Closing state",
|
|
1899
|
+
walletState: fixtures_1.walletState.Closing,
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
testName: "when sweeping wallet is in the Closed state",
|
|
1903
|
+
walletState: fixtures_1.walletState.Closed,
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
testName: "when sweeping wallet is in the Terminated state",
|
|
1907
|
+
walletState: fixtures_1.walletState.Terminated,
|
|
1908
|
+
},
|
|
1909
|
+
];
|
|
1910
|
+
testData.forEach((test) => {
|
|
1911
|
+
context(test.testName, () => {
|
|
1912
|
+
const data = moving_funds_1.MovedFundsSweepWithoutMainUtxo;
|
|
1913
|
+
let tx;
|
|
1914
|
+
before(async () => {
|
|
1915
|
+
await createSnapshot();
|
|
1916
|
+
tx = runMovedFundsSweepScenario({
|
|
1917
|
+
...data,
|
|
1918
|
+
wallet: {
|
|
1919
|
+
...data.wallet,
|
|
1920
|
+
state: test.walletState,
|
|
1921
|
+
},
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1924
|
+
after(async () => {
|
|
1925
|
+
await restoreSnapshot();
|
|
1926
|
+
});
|
|
1927
|
+
it("should revert", async () => {
|
|
1928
|
+
await (0, chai_1.expect)(tx).to.be.revertedWith("Wallet must be in Live or MovingFunds state");
|
|
1929
|
+
});
|
|
1930
|
+
});
|
|
1931
|
+
});
|
|
1932
|
+
});
|
|
1933
|
+
});
|
|
1934
|
+
context("when single output is neither P2PKH nor P2WPKH", () => {
|
|
1935
|
+
const data = moving_funds_1.MovedFundsSweepP2SHOutput;
|
|
1936
|
+
before(async () => {
|
|
1937
|
+
await createSnapshot();
|
|
1938
|
+
});
|
|
1939
|
+
after(async () => {
|
|
1940
|
+
await restoreSnapshot();
|
|
1941
|
+
});
|
|
1942
|
+
it("should revert", async () => {
|
|
1943
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Output must be P2PKH or P2WPKH");
|
|
1944
|
+
});
|
|
1945
|
+
});
|
|
1946
|
+
});
|
|
1947
|
+
context("when the single output is not 20-byte", () => {
|
|
1948
|
+
const data = moving_funds_1.MovedFundsSweepProvablyUnspendableOutput;
|
|
1949
|
+
before(async () => {
|
|
1950
|
+
await createSnapshot();
|
|
1951
|
+
});
|
|
1952
|
+
after(async () => {
|
|
1953
|
+
await restoreSnapshot();
|
|
1954
|
+
});
|
|
1955
|
+
it("should revert", async () => {
|
|
1956
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Output's public key hash must have 20 bytes");
|
|
1957
|
+
});
|
|
1958
|
+
});
|
|
1959
|
+
});
|
|
1960
|
+
context("when output count is other than one", () => {
|
|
1961
|
+
const data = moving_funds_1.MovedFundsSweepMultipleOutputs;
|
|
1962
|
+
before(async () => {
|
|
1963
|
+
await createSnapshot();
|
|
1964
|
+
});
|
|
1965
|
+
after(async () => {
|
|
1966
|
+
await restoreSnapshot();
|
|
1967
|
+
});
|
|
1968
|
+
it("should revert", async () => {
|
|
1969
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Moved funds sweep transaction must have a single output");
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
});
|
|
1973
|
+
context("when transaction proof is not valid", () => {
|
|
1974
|
+
context("when input vector is not valid", () => {
|
|
1975
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
1976
|
+
before(async () => {
|
|
1977
|
+
await createSnapshot();
|
|
1978
|
+
});
|
|
1979
|
+
after(async () => {
|
|
1980
|
+
await restoreSnapshot();
|
|
1981
|
+
});
|
|
1982
|
+
it("should revert", async () => {
|
|
1983
|
+
// Corrupt the input vector by setting a compactSize uint claiming
|
|
1984
|
+
// there are no inputs at all.
|
|
1985
|
+
data.sweepTx.inputVector =
|
|
1986
|
+
"0x00b69a2869840aa6fdfd143136ff4514ca46ea2d876855040892ad74ab" +
|
|
1987
|
+
"8c5274220100000000ffffffff";
|
|
1988
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Invalid input vector provided");
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1991
|
+
context("when output vector is not valid", () => {
|
|
1992
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
1993
|
+
before(async () => {
|
|
1994
|
+
await createSnapshot();
|
|
1995
|
+
});
|
|
1996
|
+
after(async () => {
|
|
1997
|
+
await restoreSnapshot();
|
|
1998
|
+
});
|
|
1999
|
+
it("should revert", async () => {
|
|
2000
|
+
// Corrupt the output vector by setting a compactSize uint claiming
|
|
2001
|
+
// there is no outputs at all.
|
|
2002
|
+
data.sweepTx.outputVector =
|
|
2003
|
+
"0x005cf511000000000017a91486884e6be1525dab5ae0b451bd2c72cee6" +
|
|
2004
|
+
"7dcf4187";
|
|
2005
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Invalid output vector provided");
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
2008
|
+
context("when merkle proof is not valid", () => {
|
|
2009
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
2010
|
+
before(async () => {
|
|
2011
|
+
await createSnapshot();
|
|
2012
|
+
});
|
|
2013
|
+
after(async () => {
|
|
2014
|
+
await restoreSnapshot();
|
|
2015
|
+
});
|
|
2016
|
+
it("should revert", async () => {
|
|
2017
|
+
// Corrupt the merkle proof by changing tx index in block to an
|
|
2018
|
+
// invalid one. The proper one is 12 so any other will do the trick.
|
|
2019
|
+
data.sweepProof.txIndexInBlock = 30;
|
|
2020
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Tx merkle proof is not valid for provided header and tx hash");
|
|
2021
|
+
});
|
|
2022
|
+
});
|
|
2023
|
+
context("when proof difficulty is not current nor previous", () => {
|
|
2024
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
2025
|
+
before(async () => {
|
|
2026
|
+
await createSnapshot();
|
|
2027
|
+
});
|
|
2028
|
+
after(async () => {
|
|
2029
|
+
await restoreSnapshot();
|
|
2030
|
+
});
|
|
2031
|
+
it("should revert", async () => {
|
|
2032
|
+
// To pass the proof validation, the difficulty returned by the relay
|
|
2033
|
+
// must be 1 for test data used in this scenario. Setting
|
|
2034
|
+
// a different value will cause difficulty comparison failure.
|
|
2035
|
+
data.chainDifficulty = 2;
|
|
2036
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Not at current or previous difficulty");
|
|
2037
|
+
});
|
|
2038
|
+
});
|
|
2039
|
+
context("when headers chain length is not valid", () => {
|
|
2040
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
2041
|
+
before(async () => {
|
|
2042
|
+
await createSnapshot();
|
|
2043
|
+
});
|
|
2044
|
+
after(async () => {
|
|
2045
|
+
await restoreSnapshot();
|
|
2046
|
+
});
|
|
2047
|
+
it("should revert", async () => {
|
|
2048
|
+
// Corrupt the bitcoin headers length in the moving funds proof. The
|
|
2049
|
+
// proper value is length divisible by 80 so any length violating
|
|
2050
|
+
// this rule will cause failure. In this case, we just remove the
|
|
2051
|
+
// last byte from proper headers chain.
|
|
2052
|
+
const properHeaders = data.sweepProof.bitcoinHeaders.toString();
|
|
2053
|
+
data.sweepProof.bitcoinHeaders = properHeaders.substring(0, properHeaders.length - 2);
|
|
2054
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Invalid length of the headers chain");
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
context("when headers chain is not valid", () => {
|
|
2058
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
2059
|
+
before(async () => {
|
|
2060
|
+
await createSnapshot();
|
|
2061
|
+
});
|
|
2062
|
+
after(async () => {
|
|
2063
|
+
await restoreSnapshot();
|
|
2064
|
+
});
|
|
2065
|
+
it("should revert", async () => {
|
|
2066
|
+
// Bitcoin headers must form a chain to pass the proof validation.
|
|
2067
|
+
// That means the `previous block hash` encoded in the given block
|
|
2068
|
+
// header must match the actual previous header's hash. To test
|
|
2069
|
+
// that scenario, we corrupt the `previous block hash` of the
|
|
2070
|
+
// second header. Each header is 80 bytes length. First 4 bytes
|
|
2071
|
+
// of each header is `version` and 32 subsequent bytes is
|
|
2072
|
+
// `previous block hash`. Changing byte 85 of the whole chain will
|
|
2073
|
+
// do the work.
|
|
2074
|
+
const properHeaders = data.sweepProof.bitcoinHeaders.toString();
|
|
2075
|
+
data.sweepProof.bitcoinHeaders = `${properHeaders.substring(0, 170)}ff${properHeaders.substring(172)}`;
|
|
2076
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Invalid headers chain");
|
|
2077
|
+
});
|
|
2078
|
+
});
|
|
2079
|
+
context("when the work in the header is insufficient", () => {
|
|
2080
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithoutMainUtxo));
|
|
2081
|
+
before(async () => {
|
|
2082
|
+
await createSnapshot();
|
|
2083
|
+
});
|
|
2084
|
+
after(async () => {
|
|
2085
|
+
await restoreSnapshot();
|
|
2086
|
+
});
|
|
2087
|
+
it("should revert", async () => {
|
|
2088
|
+
// Each header encodes a `difficulty target` field in bytes 72-76.
|
|
2089
|
+
// The given header's hash (interpreted as uint) must be bigger than
|
|
2090
|
+
// the `difficulty target`. To test this scenario, we change the
|
|
2091
|
+
// last byte of the last header in such a way their hash becomes
|
|
2092
|
+
// lower than their `difficulty target`.
|
|
2093
|
+
const properHeaders = data.sweepProof.bitcoinHeaders.toString();
|
|
2094
|
+
data.sweepProof.bitcoinHeaders = `${properHeaders.substring(0, properHeaders.length - 2)}ff`;
|
|
2095
|
+
await (0, chai_1.expect)(runMovedFundsSweepScenario(data)).to.be.revertedWith("Insufficient work in a header");
|
|
2096
|
+
});
|
|
2097
|
+
});
|
|
2098
|
+
context("when accumulated difficulty in headers chain is insufficient", () => {
|
|
2099
|
+
let otherBridge;
|
|
2100
|
+
const data = JSON.parse(JSON.stringify(moving_funds_1.MovedFundsSweepWithMainUtxo));
|
|
2101
|
+
before(async () => {
|
|
2102
|
+
await createSnapshot();
|
|
2103
|
+
// Necessary to pass the first part of proof validation.
|
|
2104
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
2105
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
2106
|
+
// Deploy another bridge which has higher `txProofDifficultyFactor`
|
|
2107
|
+
// than the original bridge. That means it will need 12 confirmations
|
|
2108
|
+
// to deem transaction proof validity. This scenario uses test
|
|
2109
|
+
// data which has only 6 confirmations. That should force the
|
|
2110
|
+
// failure we expect within this scenario.
|
|
2111
|
+
otherBridge = await BridgeFactory.deploy();
|
|
2112
|
+
await otherBridge.initialize(bank.address, relay.address, treasury.address, walletRegistry.address, 12);
|
|
2113
|
+
await otherBridge.deployed();
|
|
2114
|
+
});
|
|
2115
|
+
after(async () => {
|
|
2116
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
2117
|
+
relay.getPrevEpochDifficulty.reset();
|
|
2118
|
+
await restoreSnapshot();
|
|
2119
|
+
});
|
|
2120
|
+
it("should revert", async () => {
|
|
2121
|
+
await (0, chai_1.expect)(otherBridge.submitMovedFundsSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo)).to.be.revertedWith("Insufficient accumulated difficulty in header chain");
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
});
|
|
2125
|
+
});
|
|
2126
|
+
describe("notifyMovedFundsSweepTimeout", () => {
|
|
2127
|
+
const walletDraft = {
|
|
2128
|
+
ecdsaWalletID: ecdsa_1.ecdsaWalletTestData.walletID,
|
|
2129
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
2130
|
+
pendingRedemptionsValue: 0,
|
|
2131
|
+
createdAt: 0,
|
|
2132
|
+
movingFundsRequestedAt: 0,
|
|
2133
|
+
closingStartedAt: 0,
|
|
2134
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
2135
|
+
state: fixtures_1.walletState.Unknown,
|
|
2136
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
2137
|
+
};
|
|
2138
|
+
const walletMembersIDs = [1, 2, 3, 4, 5];
|
|
2139
|
+
// Just an arbitrary request for test purposes.
|
|
2140
|
+
const movedFundsSweepRequest = {
|
|
2141
|
+
walletPubKeyHash: ecdsa_1.ecdsaWalletTestData.pubKeyHash160,
|
|
2142
|
+
txHash: "0x7d5f7d4ae705d6adb8a402e5cd7f25f839a3f3ed243a8961c8ac5887d5aaf528",
|
|
2143
|
+
txOutputIndex: 1,
|
|
2144
|
+
txOutputValue: 1747020,
|
|
2145
|
+
};
|
|
2146
|
+
context("when moved funds sweep request is in the Pending state", () => {
|
|
2147
|
+
before(async () => {
|
|
2148
|
+
await createSnapshot();
|
|
2149
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, walletDraft);
|
|
2150
|
+
await bridge.setPendingMovedFundsSweepRequest(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest);
|
|
2151
|
+
});
|
|
2152
|
+
after(async () => {
|
|
2153
|
+
await restoreSnapshot();
|
|
2154
|
+
});
|
|
2155
|
+
context("when moved funds sweep request has timed out", () => {
|
|
2156
|
+
before(async () => {
|
|
2157
|
+
await createSnapshot();
|
|
2158
|
+
await increaseTime(movedFundsSweepTimeout);
|
|
2159
|
+
});
|
|
2160
|
+
after(async () => {
|
|
2161
|
+
await restoreSnapshot();
|
|
2162
|
+
});
|
|
2163
|
+
context("when the wallet is either in the Live or MovingFunds state", () => {
|
|
2164
|
+
const testData = [
|
|
2165
|
+
{
|
|
2166
|
+
testName: "when the wallet is in the Live state but the wallet is not the active one",
|
|
2167
|
+
walletState: fixtures_1.walletState.Live,
|
|
2168
|
+
additionalSetup: async () => {
|
|
2169
|
+
// The active wallet is a different wallet than the tested one
|
|
2170
|
+
await bridge.setActiveWallet("0x0b9f85c224b0e018a5865392927b3f9e16cf5e79");
|
|
2171
|
+
},
|
|
2172
|
+
additionalAssertions: async () => {
|
|
2173
|
+
it("should decrease the live wallets count", async () => {
|
|
2174
|
+
(0, chai_1.expect)(await bridge.liveWalletsCount()).to.be.equal(0);
|
|
2175
|
+
});
|
|
2176
|
+
it("should not unset the active wallet", async () => {
|
|
2177
|
+
(0, chai_1.expect)(await bridge.activeWalletPubKeyHash()).to.be.not.equal("0x0000000000000000000000000000000000000000");
|
|
2178
|
+
});
|
|
2179
|
+
},
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
testName: "when the wallet is in the Live state and the wallet is the active one",
|
|
2183
|
+
walletState: fixtures_1.walletState.Live,
|
|
2184
|
+
additionalSetup: async () => {
|
|
2185
|
+
await bridge.setActiveWallet(movedFundsSweepRequest.walletPubKeyHash);
|
|
2186
|
+
},
|
|
2187
|
+
additionalAssertions: async () => {
|
|
2188
|
+
it("should decrease the live wallets count", async () => {
|
|
2189
|
+
(0, chai_1.expect)(await bridge.liveWalletsCount()).to.be.equal(0);
|
|
2190
|
+
});
|
|
2191
|
+
it("should unset the active wallet", async () => {
|
|
2192
|
+
(0, chai_1.expect)(await bridge.activeWalletPubKeyHash()).to.be.equal("0x0000000000000000000000000000000000000000");
|
|
2193
|
+
});
|
|
2194
|
+
},
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
testName: "when the wallet is in the MovingFunds state",
|
|
2198
|
+
walletState: fixtures_1.walletState.MovingFunds,
|
|
2199
|
+
additionalSetup: async () => { },
|
|
2200
|
+
additionalAssertions: async () => { },
|
|
2201
|
+
},
|
|
2202
|
+
];
|
|
2203
|
+
testData.forEach((test) => {
|
|
2204
|
+
context(test.testName, async () => {
|
|
2205
|
+
let tx;
|
|
2206
|
+
before(async () => {
|
|
2207
|
+
await createSnapshot();
|
|
2208
|
+
// We change the wallet state while preserving other fields.
|
|
2209
|
+
// We don't have an update function in the stub so we just use
|
|
2210
|
+
// the getter to get wallet fields and set them through the
|
|
2211
|
+
// setter with state field overwritten.
|
|
2212
|
+
await bridge.setWallet(movedFundsSweepRequest.walletPubKeyHash, {
|
|
2213
|
+
...(await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash)),
|
|
2214
|
+
state: test.walletState,
|
|
2215
|
+
});
|
|
2216
|
+
await test.additionalSetup();
|
|
2217
|
+
tx = await bridge
|
|
2218
|
+
.connect(thirdParty)
|
|
2219
|
+
.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs);
|
|
2220
|
+
});
|
|
2221
|
+
after(async () => {
|
|
2222
|
+
walletRegistry.closeWallet.reset();
|
|
2223
|
+
walletRegistry.seize.reset();
|
|
2224
|
+
await restoreSnapshot();
|
|
2225
|
+
});
|
|
2226
|
+
it("should switch the moved funds sweep request to the TimedOut state", async () => {
|
|
2227
|
+
const requestKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
2228
|
+
movedFundsSweepRequest.txHash,
|
|
2229
|
+
movedFundsSweepRequest.txOutputIndex,
|
|
2230
|
+
]);
|
|
2231
|
+
(0, chai_1.expect)((await bridge.movedFundsSweepRequests(requestKey)).state).to.be.equal(fixtures_1.movedFundsSweepRequestState.TimedOut);
|
|
2232
|
+
});
|
|
2233
|
+
it("should decrease the number of pending moved funds sweep requests for the given wallet", async () => (0, chai_1.expect)((await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash)).pendingMovedFundsSweepRequestsCount).to.be.equal(0));
|
|
2234
|
+
it("should switch the wallet to Terminated state", async () => {
|
|
2235
|
+
(0, chai_1.expect)((await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash)).state).to.be.equal(fixtures_1.walletState.Terminated);
|
|
2236
|
+
});
|
|
2237
|
+
it("should emit WalletTerminated event", async () => {
|
|
2238
|
+
await (0, chai_1.expect)(tx)
|
|
2239
|
+
.to.emit(bridge, "WalletTerminated")
|
|
2240
|
+
.withArgs(walletDraft.ecdsaWalletID, movedFundsSweepRequest.walletPubKeyHash);
|
|
2241
|
+
});
|
|
2242
|
+
it("should call ECDSA Wallet Registry's closeWallet function", async () => {
|
|
2243
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
2244
|
+
(0, chai_1.expect)(walletRegistry.closeWallet).to.have.been.calledOnceWith(walletDraft.ecdsaWalletID);
|
|
2245
|
+
});
|
|
2246
|
+
it("should call the ECDSA wallet registry's seize function", async () => {
|
|
2247
|
+
(0, chai_1.expect)(walletRegistry.seize).to.have.been.calledOnceWith(movedFundsSweepTimeoutSlashingAmount, movedFundsSweepTimeoutNotifierRewardMultiplier, await thirdParty.getAddress(), walletDraft.ecdsaWalletID, walletMembersIDs);
|
|
2248
|
+
});
|
|
2249
|
+
it("should emit MovedFundsSweepTimedOut event", async () => {
|
|
2250
|
+
await (0, chai_1.expect)(tx)
|
|
2251
|
+
.to.emit(bridge, "MovedFundsSweepTimedOut")
|
|
2252
|
+
.withArgs(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex);
|
|
2253
|
+
});
|
|
2254
|
+
await test.additionalAssertions();
|
|
2255
|
+
});
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2258
|
+
context("when the wallet is in the Terminated state", () => {
|
|
2259
|
+
let tx;
|
|
2260
|
+
before(async () => {
|
|
2261
|
+
await createSnapshot();
|
|
2262
|
+
await bridge.setWallet(movedFundsSweepRequest.walletPubKeyHash, {
|
|
2263
|
+
...(await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash)),
|
|
2264
|
+
state: fixtures_1.walletState.Terminated,
|
|
2265
|
+
});
|
|
2266
|
+
tx = await bridge
|
|
2267
|
+
.connect(thirdParty)
|
|
2268
|
+
.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs);
|
|
2269
|
+
});
|
|
2270
|
+
after(async () => {
|
|
2271
|
+
await restoreSnapshot();
|
|
2272
|
+
});
|
|
2273
|
+
it("should switch the moved funds sweep request to the TimedOut state", async () => {
|
|
2274
|
+
const requestKey = hardhat_1.ethers.utils.solidityKeccak256(["bytes32", "uint32"], [
|
|
2275
|
+
movedFundsSweepRequest.txHash,
|
|
2276
|
+
movedFundsSweepRequest.txOutputIndex,
|
|
2277
|
+
]);
|
|
2278
|
+
(0, chai_1.expect)((await bridge.movedFundsSweepRequests(requestKey)).state).to.be.equal(fixtures_1.movedFundsSweepRequestState.TimedOut);
|
|
2279
|
+
});
|
|
2280
|
+
it("should decrease the number of pending moved funds sweep requests for the given wallet", async () => (0, chai_1.expect)((await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash))
|
|
2281
|
+
.pendingMovedFundsSweepRequestsCount).to.be.equal(0));
|
|
2282
|
+
it("should not change the wallet state", async () => {
|
|
2283
|
+
(0, chai_1.expect)((await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash))
|
|
2284
|
+
.state).to.be.equal(fixtures_1.walletState.Terminated);
|
|
2285
|
+
});
|
|
2286
|
+
});
|
|
2287
|
+
context("when the wallet is neither in the Live nor MovingFunds nor Terminated state", () => {
|
|
2288
|
+
const testData = [
|
|
2289
|
+
{
|
|
2290
|
+
testName: "when the wallet is in the Unknown state",
|
|
2291
|
+
walletState: fixtures_1.walletState.Unknown,
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
testName: "when the wallet is in the Closing state",
|
|
2295
|
+
walletState: fixtures_1.walletState.Closing,
|
|
2296
|
+
},
|
|
2297
|
+
{
|
|
2298
|
+
testName: "when the wallet is in the Closed state",
|
|
2299
|
+
walletState: fixtures_1.walletState.Closed,
|
|
2300
|
+
},
|
|
2301
|
+
];
|
|
2302
|
+
testData.forEach((test) => {
|
|
2303
|
+
context(test.testName, async () => {
|
|
2304
|
+
before(async () => {
|
|
2305
|
+
await createSnapshot();
|
|
2306
|
+
await bridge.setWallet(movedFundsSweepRequest.walletPubKeyHash, {
|
|
2307
|
+
...(await bridge.wallets(movedFundsSweepRequest.walletPubKeyHash)),
|
|
2308
|
+
state: test.walletState,
|
|
2309
|
+
});
|
|
2310
|
+
});
|
|
2311
|
+
after(async () => {
|
|
2312
|
+
await restoreSnapshot();
|
|
2313
|
+
});
|
|
2314
|
+
it("should revert", async () => {
|
|
2315
|
+
await (0, chai_1.expect)(bridge.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs)).to.be.revertedWith("Wallet must be in Live or MovingFunds or Terminated state");
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
});
|
|
2319
|
+
});
|
|
2320
|
+
});
|
|
2321
|
+
context("when moved funds sweep request has not timed out yet", () => {
|
|
2322
|
+
before(async () => {
|
|
2323
|
+
await createSnapshot();
|
|
2324
|
+
await increaseTime(movedFundsSweepTimeout - 1);
|
|
2325
|
+
});
|
|
2326
|
+
after(async () => {
|
|
2327
|
+
await restoreSnapshot();
|
|
2328
|
+
});
|
|
2329
|
+
it("should revert", async () => {
|
|
2330
|
+
await (0, chai_1.expect)(bridge.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs)).to.be.revertedWith("Sweep request has not timed out yet");
|
|
2331
|
+
});
|
|
2332
|
+
});
|
|
2333
|
+
});
|
|
2334
|
+
context("when moved funds sweep request is not in the Pending state", () => {
|
|
2335
|
+
context("when moved funds sweep request is in the Unknown state", () => {
|
|
2336
|
+
before(async () => {
|
|
2337
|
+
await createSnapshot();
|
|
2338
|
+
});
|
|
2339
|
+
after(async () => {
|
|
2340
|
+
await restoreSnapshot();
|
|
2341
|
+
});
|
|
2342
|
+
it("should revert", async () => {
|
|
2343
|
+
await (0, chai_1.expect)(bridge.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs)).to.be.revertedWith("Sweep request must be in Pending state");
|
|
2344
|
+
});
|
|
2345
|
+
});
|
|
2346
|
+
context("when moved funds sweep request is in the Processed state", () => {
|
|
2347
|
+
before(async () => {
|
|
2348
|
+
await createSnapshot();
|
|
2349
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, walletDraft);
|
|
2350
|
+
await bridge.setPendingMovedFundsSweepRequest(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest);
|
|
2351
|
+
await bridge.processPendingMovedFundsSweepRequest(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest);
|
|
2352
|
+
});
|
|
2353
|
+
after(async () => {
|
|
2354
|
+
await restoreSnapshot();
|
|
2355
|
+
});
|
|
2356
|
+
it("should revert", async () => {
|
|
2357
|
+
await (0, chai_1.expect)(bridge.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs)).to.be.revertedWith("Sweep request must be in Pending state");
|
|
2358
|
+
});
|
|
2359
|
+
});
|
|
2360
|
+
context("when moved funds sweep request is in the TimedOut state", () => {
|
|
2361
|
+
before(async () => {
|
|
2362
|
+
await createSnapshot();
|
|
2363
|
+
await bridge.setWallet(ecdsa_1.ecdsaWalletTestData.pubKeyHash160, walletDraft);
|
|
2364
|
+
await bridge.setPendingMovedFundsSweepRequest(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest);
|
|
2365
|
+
await bridge.timeoutPendingMovedFundsSweepRequest(movedFundsSweepRequest.walletPubKeyHash, movedFundsSweepRequest);
|
|
2366
|
+
});
|
|
2367
|
+
after(async () => {
|
|
2368
|
+
await restoreSnapshot();
|
|
2369
|
+
});
|
|
2370
|
+
it("should revert", async () => {
|
|
2371
|
+
await (0, chai_1.expect)(bridge.notifyMovedFundsSweepTimeout(movedFundsSweepRequest.txHash, movedFundsSweepRequest.txOutputIndex, walletMembersIDs)).to.be.revertedWith("Sweep request must be in Pending state");
|
|
2372
|
+
});
|
|
2373
|
+
});
|
|
2374
|
+
});
|
|
2375
|
+
});
|
|
2376
|
+
async function runMovingFundsScenario(data, beforeProofActions) {
|
|
2377
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
2378
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
2379
|
+
// Simulate the wallet is a registered one.
|
|
2380
|
+
await bridge.setWallet(data.wallet.pubKeyHash, {
|
|
2381
|
+
ecdsaWalletID: data.wallet.ecdsaWalletID,
|
|
2382
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
2383
|
+
pendingRedemptionsValue: 0,
|
|
2384
|
+
createdAt: await lastBlockTime(),
|
|
2385
|
+
movingFundsRequestedAt: await lastBlockTime(),
|
|
2386
|
+
closingStartedAt: 0,
|
|
2387
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
2388
|
+
state: data.wallet.state,
|
|
2389
|
+
movingFundsTargetWalletsCommitmentHash: data.targetWalletsCommitment.length > 0
|
|
2390
|
+
? hardhat_1.ethers.utils.solidityKeccak256(["bytes20[]"], [data.targetWalletsCommitment])
|
|
2391
|
+
: hardhat_1.ethers.constants.HashZero,
|
|
2392
|
+
});
|
|
2393
|
+
// Simulate the prepared main UTXO belongs to the wallet.
|
|
2394
|
+
await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
|
|
2395
|
+
if (beforeProofActions) {
|
|
2396
|
+
await beforeProofActions();
|
|
2397
|
+
}
|
|
2398
|
+
const tx = await bridge.submitMovingFundsProof(data.movingFundsTx, data.movingFundsProof, data.mainUtxo, data.wallet.pubKeyHash);
|
|
2399
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
2400
|
+
relay.getPrevEpochDifficulty.reset();
|
|
2401
|
+
return tx;
|
|
2402
|
+
}
|
|
2403
|
+
async function runMovedFundsSweepScenario(data, beforeProofActions) {
|
|
2404
|
+
relay.getCurrentEpochDifficulty.returns(data.chainDifficulty);
|
|
2405
|
+
relay.getPrevEpochDifficulty.returns(data.chainDifficulty);
|
|
2406
|
+
// Simulate the wallet is a registered one.
|
|
2407
|
+
await bridge.setWallet(data.wallet.pubKeyHash, {
|
|
2408
|
+
ecdsaWalletID: data.wallet.ecdsaWalletID,
|
|
2409
|
+
mainUtxoHash: hardhat_1.ethers.constants.HashZero,
|
|
2410
|
+
pendingRedemptionsValue: 0,
|
|
2411
|
+
createdAt: await lastBlockTime(),
|
|
2412
|
+
movingFundsRequestedAt: 0,
|
|
2413
|
+
closingStartedAt: 0,
|
|
2414
|
+
pendingMovedFundsSweepRequestsCount: 0,
|
|
2415
|
+
state: data.wallet.state,
|
|
2416
|
+
movingFundsTargetWalletsCommitmentHash: hardhat_1.ethers.constants.HashZero,
|
|
2417
|
+
});
|
|
2418
|
+
if (data.mainUtxo.txHash !== hardhat_1.ethers.constants.HashZero) {
|
|
2419
|
+
// Simulate the prepared main UTXO belongs to the wallet.
|
|
2420
|
+
await bridge.setWalletMainUtxo(data.wallet.pubKeyHash, data.mainUtxo);
|
|
2421
|
+
}
|
|
2422
|
+
if (data.movedFundsSweepRequest) {
|
|
2423
|
+
await bridge.setPendingMovedFundsSweepRequest(data.movedFundsSweepRequest.walletPubKeyHash, data.movedFundsSweepRequest);
|
|
2424
|
+
// Just make sure the stub function `setPendingMovedFundsSweepRequest`
|
|
2425
|
+
// initialized the counter properly.
|
|
2426
|
+
(0, chai_1.assert)((await bridge.wallets(data.movedFundsSweepRequest.walletPubKeyHash))
|
|
2427
|
+
.pendingMovedFundsSweepRequestsCount === 1, "Pending moved funds request counter for the sweeping wallet should be set up to 1");
|
|
2428
|
+
}
|
|
2429
|
+
if (beforeProofActions) {
|
|
2430
|
+
await beforeProofActions();
|
|
2431
|
+
}
|
|
2432
|
+
const tx = await bridge.submitMovedFundsSweepProof(data.sweepTx, data.sweepProof, data.mainUtxo);
|
|
2433
|
+
relay.getCurrentEpochDifficulty.reset();
|
|
2434
|
+
relay.getPrevEpochDifficulty.reset();
|
|
2435
|
+
return tx;
|
|
2436
|
+
}
|
|
2437
|
+
});
|