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