@keep-network/tbtc-v2 0.1.1-dev.59 → 0.1.1-dev.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/artifacts/Bank.json +6 -6
  2. package/artifacts/Bridge.json +54 -54
  3. package/artifacts/Deposit.json +7 -7
  4. package/artifacts/DepositSweep.json +9 -9
  5. package/artifacts/EcdsaDkgValidator.json +1 -1
  6. package/artifacts/EcdsaInactivity.json +1 -1
  7. package/artifacts/Fraud.json +9 -9
  8. package/artifacts/KeepRegistry.json +1 -1
  9. package/artifacts/KeepStake.json +2 -2
  10. package/artifacts/KeepToken.json +2 -2
  11. package/artifacts/KeepTokenStaking.json +1 -1
  12. package/artifacts/MovingFunds.json +7 -7
  13. package/artifacts/NuCypherStakingEscrow.json +1 -1
  14. package/artifacts/NuCypherToken.json +2 -2
  15. package/artifacts/RandomBeaconStub.json +1 -1
  16. package/artifacts/Redemption.json +7 -7
  17. package/artifacts/ReimbursementPool.json +2 -2
  18. package/artifacts/Relay.json +11 -11
  19. package/artifacts/SortitionPool.json +2 -2
  20. package/artifacts/T.json +2 -2
  21. package/artifacts/TBTC.json +6 -6
  22. package/artifacts/TBTCToken.json +6 -6
  23. package/artifacts/TokenStaking.json +1 -1
  24. package/artifacts/TokenholderGovernor.json +9 -9
  25. package/artifacts/TokenholderTimelock.json +8 -8
  26. package/artifacts/VendingMachine.json +13 -13
  27. package/artifacts/VendingMachineKeep.json +1 -1
  28. package/artifacts/VendingMachineNuCypher.json +1 -1
  29. package/artifacts/WalletRegistry.json +2 -2
  30. package/artifacts/WalletRegistryGovernance.json +2 -2
  31. package/artifacts/Wallets.json +7 -7
  32. package/artifacts/solcInputs/{c86d08e5f2ce89fcf398a4658c796260.json → 6ff443beb223cf0c26c6b81361aa1799.json} +9 -3
  33. package/build/contracts/GovernanceUtils.sol/GovernanceUtils.dbg.json +1 -1
  34. package/build/contracts/bank/Bank.sol/Bank.dbg.json +1 -1
  35. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.dbg.json +1 -1
  36. package/build/contracts/bridge/BitcoinTx.sol/BitcoinTx.json +2 -2
  37. package/build/contracts/bridge/Bridge.sol/Bridge.dbg.json +1 -1
  38. package/build/contracts/bridge/Bridge.sol/Bridge.json +2 -2
  39. package/build/contracts/bridge/BridgeState.sol/BridgeState.dbg.json +1 -1
  40. package/build/contracts/bridge/BridgeState.sol/BridgeState.json +2 -2
  41. package/build/contracts/bridge/Deposit.sol/Deposit.dbg.json +1 -1
  42. package/build/contracts/bridge/Deposit.sol/Deposit.json +2 -2
  43. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.dbg.json +1 -1
  44. package/build/contracts/bridge/DepositSweep.sol/DepositSweep.json +2 -2
  45. package/build/contracts/bridge/EcdsaLib.sol/EcdsaLib.dbg.json +1 -1
  46. package/build/contracts/bridge/Fraud.sol/Fraud.dbg.json +1 -1
  47. package/build/contracts/bridge/Fraud.sol/Fraud.json +2 -2
  48. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.dbg.json +4 -0
  49. package/build/contracts/bridge/Heartbeat.sol/Heartbeat.json +10 -0
  50. package/build/contracts/bridge/IRelay.sol/IRelay.dbg.json +1 -1
  51. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.dbg.json +1 -1
  52. package/build/contracts/bridge/MovingFunds.sol/MovingFunds.json +2 -2
  53. package/build/contracts/bridge/Redemption.sol/OutboundTx.dbg.json +1 -1
  54. package/build/contracts/bridge/Redemption.sol/OutboundTx.json +2 -2
  55. package/build/contracts/bridge/Redemption.sol/Redemption.dbg.json +1 -1
  56. package/build/contracts/bridge/Redemption.sol/Redemption.json +2 -2
  57. package/build/contracts/bridge/VendingMachine.sol/VendingMachine.dbg.json +1 -1
  58. package/build/contracts/bridge/Wallets.sol/Wallets.dbg.json +1 -1
  59. package/build/contracts/bridge/Wallets.sol/Wallets.json +2 -2
  60. package/build/contracts/token/TBTC.sol/TBTC.dbg.json +1 -1
  61. package/build/contracts/vault/IVault.sol/IVault.dbg.json +1 -1
  62. package/build/contracts/vault/TBTCVault.sol/TBTCVault.dbg.json +1 -1
  63. package/contracts/bridge/BitcoinTx.sol +18 -3
  64. package/contracts/bridge/DepositSweep.sol +9 -21
  65. package/contracts/bridge/Heartbeat.sol +107 -0
  66. package/package.json +1 -1
@@ -122,7 +122,7 @@
122
122
  "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a >= b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow, so we distribute.\n return (a / 2) + (b / 2) + (((a % 2) + (b % 2)) / 2);\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a / b + (a % b == 0 ? 0 : 1);\n }\n}\n"
123
123
  },
124
124
  "contracts/bridge/BitcoinTx.sol": {
125
- "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\nimport {BytesLib} from \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\nimport {ValidateSPV} from \"@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol\";\n\nimport \"./BridgeState.sol\";\n\n/// @title Bitcoin transaction\n/// @notice Allows to reference Bitcoin raw transaction in Solidity.\n/// @dev See https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format\n///\n/// Raw Bitcoin transaction data:\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|--------------|------------------------|---------------------------|\n/// | 4 | version | int32_t (LE) | TX version number |\n/// | varies | tx_in_count | compactSize uint (LE) | Number of TX inputs |\n/// | varies | tx_in | txIn[] | TX inputs |\n/// | varies | tx_out count | compactSize uint (LE) | Number of TX outputs |\n/// | varies | tx_out | txOut[] | TX outputs |\n/// | 4 | lock_time | uint32_t (LE) | Unix time or block number |\n///\n//\n/// Non-coinbase transaction input (txIn):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|------------------|------------------------|---------------------------------------------|\n/// | 36 | previous_output | outpoint | The previous outpoint being spent |\n/// | varies | script bytes | compactSize uint (LE) | The number of bytes in the signature script |\n/// | varies | signature script | char[] | The signature script, empty for P2WSH |\n/// | 4 | sequence | uint32_t (LE) | Sequence number |\n///\n///\n/// The reference to transaction being spent (outpoint):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |-------|-------|---------------|------------------------------------------|\n/// | 32 | hash | char[32] | Hash of the transaction to spend |\n/// | 4 | index | uint32_t (LE) | Index of the specific output from the TX |\n///\n///\n/// Transaction output (txOut):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|-----------------|-----------------------|--------------------------------------|\n/// | 8 | value | int64_t (LE) | Number of satoshis to spend |\n/// | 1+ | pk_script_bytes | compactSize uint (LE) | Number of bytes in the pubkey script |\n/// | varies | pk_script | char[] | Pubkey script |\n///\n/// compactSize uint format:\n///\n/// | Value | Bytes | Format |\n/// |-----------------------------------------|-------|----------------------------------------------|\n/// | >= 0 && <= 252 | 1 | uint8_t |\n/// | >= 253 && <= 0xffff | 3 | 0xfd followed by the number as uint16_t (LE) |\n/// | >= 0x10000 && <= 0xffffffff | 5 | 0xfe followed by the number as uint32_t (LE) |\n/// | >= 0x100000000 && <= 0xffffffffffffffff | 9 | 0xff followed by the number as uint64_t (LE) |\n///\n/// (*) compactSize uint is often references as VarInt)\n///\nlibrary BitcoinTx {\n using BTCUtils for bytes;\n using BTCUtils for uint256;\n using BytesLib for bytes;\n using ValidateSPV for bytes;\n using ValidateSPV for bytes32;\n\n /// @notice Represents Bitcoin transaction data.\n struct Info {\n /// @notice Bitcoin transaction version\n /// @dev `version` from raw Bitcoin transaction data.\n /// Encoded as 4-bytes signed integer, little endian.\n bytes4 version;\n /// @notice All Bitcoin transaction inputs, prepended by the number of\n /// transaction inputs.\n /// @dev `tx_in_count | tx_in` from raw Bitcoin transaction data.\n ///\n /// The number of transaction inputs encoded as compactSize\n /// unsigned integer, little-endian.\n ///\n /// Note that some popular block explorers reverse the order of\n /// bytes from `outpoint`'s `hash` and display it as big-endian.\n /// Solidity code of Bridge expects hashes in little-endian, just\n /// like they are represented in a raw Bitcoin transaction.\n bytes inputVector;\n /// @notice All Bitcoin transaction outputs prepended by the number of\n /// transaction outputs.\n /// @dev `tx_out_count | tx_out` from raw Bitcoin transaction data.\n ///\n /// The number of transaction outputs encoded as a compactSize\n /// unsigned integer, little-endian.\n bytes outputVector;\n /// @notice Bitcoin transaction locktime.\n ///\n /// @dev `lock_time` from raw Bitcoin transaction data.\n /// Encoded as 4-bytes unsigned integer, little endian.\n bytes4 locktime;\n }\n\n /// @notice Represents data needed to perform a Bitcoin SPV proof.\n struct Proof {\n /// @notice The merkle proof of transaction inclusion in a block.\n bytes merkleProof;\n /// @notice Transaction index in the block (0-indexed).\n uint256 txIndexInBlock;\n /// @notice Single byte-string of 80-byte bitcoin headers,\n /// lowest height first.\n bytes bitcoinHeaders;\n }\n\n /// @notice Represents info about an unspent transaction output.\n struct UTXO {\n /// @notice Hash of the transaction the output belongs to.\n /// @dev Byte order corresponds to the Bitcoin internal byte order.\n bytes32 txHash;\n /// @notice Index of the transaction output (0-indexed).\n uint32 txOutputIndex;\n /// @notice Value of the transaction output.\n uint64 txOutputValue;\n }\n\n /// @notice Represents Bitcoin signature in the R/S/V format.\n struct RSVSignature {\n /// @notice Signature r value.\n bytes32 r;\n /// @notice Signature s value.\n bytes32 s;\n /// @notice Signature recovery value.\n uint8 v;\n }\n\n /// @notice Validates the SPV proof of the Bitcoin transaction.\n /// Reverts in case the validation or proof verification fail.\n /// @param txInfo Bitcoin transaction data\n /// @param proof Bitcoin proof data\n /// @return txHash Proven 32-byte transaction hash.\n function validateProof(\n BridgeState.Storage storage self,\n Info calldata txInfo,\n Proof calldata proof\n ) internal view returns (bytes32 txHash) {\n require(\n txInfo.inputVector.validateVin(),\n \"Invalid input vector provided\"\n );\n require(\n txInfo.outputVector.validateVout(),\n \"Invalid output vector provided\"\n );\n\n txHash = abi\n .encodePacked(\n txInfo.version,\n txInfo.inputVector,\n txInfo.outputVector,\n txInfo.locktime\n )\n .hash256View();\n\n require(\n txHash.prove(\n proof.bitcoinHeaders.extractMerkleRootLE(),\n proof.merkleProof,\n proof.txIndexInBlock\n ),\n \"Tx merkle proof is not valid for provided header and tx hash\"\n );\n\n evaluateProofDifficulty(self, proof.bitcoinHeaders);\n\n return txHash;\n }\n\n /// @notice Evaluates the given Bitcoin proof difficulty against the actual\n /// Bitcoin chain difficulty provided by the relay oracle.\n /// Reverts in case the evaluation fails.\n /// @param bitcoinHeaders Bitcoin headers chain being part of the SPV\n /// proof. Used to extract the observed proof difficulty\n function evaluateProofDifficulty(\n BridgeState.Storage storage self,\n bytes memory bitcoinHeaders\n ) internal view {\n IRelay relay = self.relay;\n uint256 currentEpochDifficulty = relay.getCurrentEpochDifficulty();\n uint256 previousEpochDifficulty = relay.getPrevEpochDifficulty();\n\n uint256 requestedDiff = 0;\n uint256 firstHeaderDiff = bitcoinHeaders\n .extractTarget()\n .calculateDifficulty();\n\n if (firstHeaderDiff == currentEpochDifficulty) {\n requestedDiff = currentEpochDifficulty;\n } else if (firstHeaderDiff == previousEpochDifficulty) {\n requestedDiff = previousEpochDifficulty;\n } else {\n revert(\"Not at current or previous difficulty\");\n }\n\n uint256 observedDiff = bitcoinHeaders.validateHeaderChain();\n\n require(\n observedDiff != ValidateSPV.getErrBadLength(),\n \"Invalid length of the headers chain\"\n );\n require(\n observedDiff != ValidateSPV.getErrInvalidChain(),\n \"Invalid headers chain\"\n );\n require(\n observedDiff != ValidateSPV.getErrLowWork(),\n \"Insufficient work in a header\"\n );\n\n require(\n observedDiff >= requestedDiff * self.txProofDifficultyFactor,\n \"Insufficient accumulated difficulty in header chain\"\n );\n }\n\n /// @notice Extracts public key hash from the provided P2PKH or P2WPKH output.\n /// Reverts if the validation fails.\n /// @param output The transaction output\n /// @return pubKeyHash 20-byte public key hash the output locks funds on\n /// @dev Requirements:\n /// - The output must be of P2PKH or P2WPKH type and lock the funds\n /// on a 20-byte public key hash\n function extractPubKeyHash(BridgeState.Storage storage, bytes memory output)\n internal\n view\n returns (bytes20 pubKeyHash)\n {\n bytes memory pubKeyHashBytes = output.extractHash();\n\n require(\n pubKeyHashBytes.length == 20,\n \"Output's public key hash must have 20 bytes\"\n );\n\n pubKeyHash = pubKeyHashBytes.slice20(0);\n\n // We need to make sure that the 20-byte public key hash\n // is actually used in the right context of a P2PKH or P2WPKH\n // output. To do so, we must extract the full script from the output\n // and compare with the expected P2PKH and P2WPKH scripts\n // referring to that 20-byte public key hash. The output consists\n // of an 8-byte value and a variable length script. To extract the\n // script we slice the output starting from 9th byte until the end.\n bytes32 outputScriptKeccak = keccak256(\n output.slice(8, output.length - 8)\n );\n // Build the expected P2PKH script which has the following byte\n // format: <0x1976a914> <20-byte PKH> <0x88ac>. According to\n // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:\n // - 0x19: Byte length of the entire script\n // - 0x76: OP_DUP\n // - 0xa9: OP_HASH160\n // - 0x14: Byte length of the public key hash\n // - 0x88: OP_EQUALVERIFY\n // - 0xac: OP_CHECKSIG\n // which matches the P2PKH structure as per:\n // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash\n bytes32 P2PKHScriptKeccak = keccak256(\n abi.encodePacked(hex\"1976a914\", pubKeyHash, hex\"88ac\")\n );\n // Build the expected P2WPKH script which has the following format:\n // <0x160014> <20-byte PKH>. According to\n // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:\n // - 0x16: Byte length of the entire script\n // - 0x00: OP_0\n // - 0x14: Byte length of the public key hash\n // which matches the P2WPKH structure as per:\n // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH\n bytes32 P2WPKHScriptKeccak = keccak256(\n abi.encodePacked(hex\"160014\", pubKeyHash)\n );\n // Make sure the actual output script matches either the P2PKH\n // or P2WPKH format.\n require(\n outputScriptKeccak == P2PKHScriptKeccak ||\n outputScriptKeccak == P2WPKHScriptKeccak,\n \"Output must be P2PKH or P2WPKH\"\n );\n\n return pubKeyHash;\n }\n}\n"
125
+ "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\nimport {BytesLib} from \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\nimport {ValidateSPV} from \"@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol\";\n\nimport \"./BridgeState.sol\";\n\n/// @title Bitcoin transaction\n/// @notice Allows to reference Bitcoin raw transaction in Solidity.\n/// @dev See https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format\n///\n/// Raw Bitcoin transaction data:\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|--------------|------------------------|---------------------------|\n/// | 4 | version | int32_t (LE) | TX version number |\n/// | varies | tx_in_count | compactSize uint (LE) | Number of TX inputs |\n/// | varies | tx_in | txIn[] | TX inputs |\n/// | varies | tx_out_count | compactSize uint (LE) | Number of TX outputs |\n/// | varies | tx_out | txOut[] | TX outputs |\n/// | 4 | lock_time | uint32_t (LE) | Unix time or block number |\n///\n//\n/// Non-coinbase transaction input (txIn):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|------------------|------------------------|---------------------------------------------|\n/// | 36 | previous_output | outpoint | The previous outpoint being spent |\n/// | varies | script_bytes | compactSize uint (LE) | The number of bytes in the signature script |\n/// | varies | signature_script | char[] | The signature script, empty for P2WSH |\n/// | 4 | sequence | uint32_t (LE) | Sequence number |\n///\n///\n/// The reference to transaction being spent (outpoint):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |-------|-------|---------------|------------------------------------------|\n/// | 32 | hash | char[32] | Hash of the transaction to spend |\n/// | 4 | index | uint32_t (LE) | Index of the specific output from the TX |\n///\n///\n/// Transaction output (txOut):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|-----------------|-----------------------|--------------------------------------|\n/// | 8 | value | int64_t (LE) | Number of satoshis to spend |\n/// | 1+ | pk_script_bytes | compactSize uint (LE) | Number of bytes in the pubkey script |\n/// | varies | pk_script | char[] | Pubkey script |\n///\n/// compactSize uint format:\n///\n/// | Value | Bytes | Format |\n/// |-----------------------------------------|-------|----------------------------------------------|\n/// | >= 0 && <= 252 | 1 | uint8_t |\n/// | >= 253 && <= 0xffff | 3 | 0xfd followed by the number as uint16_t (LE) |\n/// | >= 0x10000 && <= 0xffffffff | 5 | 0xfe followed by the number as uint32_t (LE) |\n/// | >= 0x100000000 && <= 0xffffffffffffffff | 9 | 0xff followed by the number as uint64_t (LE) |\n///\n/// (*) compactSize uint is often references as VarInt)\n///\n/// Coinbase transaction input (txIn):\n///\n/// | Bytes | Name | BTC type | Description |\n/// |--------|------------------|------------------------|---------------------------------------------|\n/// | 32 | hash | char[32] | A 32-byte 0x0 null (no previous_outpoint) |\n/// | 4 | index | uint32_t (LE) | 0xffffffff (no previous_outpoint) |\n/// | varies | script_bytes | compactSize uint (LE) | The number of bytes in the coinbase script |\n/// | varies | height | char[] | The block height of this block (BIP34) (*) |\n/// | varies | coinbase_script | none | Arbitrary data, max 100 bytes |\n/// | 4 | sequence | uint32_t (LE) | Sequence number\n///\n/// (*) Uses script language: starts with a data-pushing opcode that indicates how many bytes to push to\n/// the stack followed by the block height as a little-endian unsigned integer. This script must be as\n/// short as possible, otherwise it may be rejected. The data-pushing opcode will be 0x03 and the total\n/// size four bytes until block 16,777,216 about 300 years from now.\nlibrary BitcoinTx {\n using BTCUtils for bytes;\n using BTCUtils for uint256;\n using BytesLib for bytes;\n using ValidateSPV for bytes;\n using ValidateSPV for bytes32;\n\n /// @notice Represents Bitcoin transaction data.\n struct Info {\n /// @notice Bitcoin transaction version\n /// @dev `version` from raw Bitcoin transaction data.\n /// Encoded as 4-bytes signed integer, little endian.\n bytes4 version;\n /// @notice All Bitcoin transaction inputs, prepended by the number of\n /// transaction inputs.\n /// @dev `tx_in_count | tx_in` from raw Bitcoin transaction data.\n ///\n /// The number of transaction inputs encoded as compactSize\n /// unsigned integer, little-endian.\n ///\n /// Note that some popular block explorers reverse the order of\n /// bytes from `outpoint`'s `hash` and display it as big-endian.\n /// Solidity code of Bridge expects hashes in little-endian, just\n /// like they are represented in a raw Bitcoin transaction.\n bytes inputVector;\n /// @notice All Bitcoin transaction outputs prepended by the number of\n /// transaction outputs.\n /// @dev `tx_out_count | tx_out` from raw Bitcoin transaction data.\n ///\n /// The number of transaction outputs encoded as a compactSize\n /// unsigned integer, little-endian.\n bytes outputVector;\n /// @notice Bitcoin transaction locktime.\n ///\n /// @dev `lock_time` from raw Bitcoin transaction data.\n /// Encoded as 4-bytes unsigned integer, little endian.\n bytes4 locktime;\n }\n\n /// @notice Represents data needed to perform a Bitcoin SPV proof.\n struct Proof {\n /// @notice The merkle proof of transaction inclusion in a block.\n bytes merkleProof;\n /// @notice Transaction index in the block (0-indexed).\n uint256 txIndexInBlock;\n /// @notice Single byte-string of 80-byte bitcoin headers,\n /// lowest height first.\n bytes bitcoinHeaders;\n }\n\n /// @notice Represents info about an unspent transaction output.\n struct UTXO {\n /// @notice Hash of the transaction the output belongs to.\n /// @dev Byte order corresponds to the Bitcoin internal byte order.\n bytes32 txHash;\n /// @notice Index of the transaction output (0-indexed).\n uint32 txOutputIndex;\n /// @notice Value of the transaction output.\n uint64 txOutputValue;\n }\n\n /// @notice Represents Bitcoin signature in the R/S/V format.\n struct RSVSignature {\n /// @notice Signature r value.\n bytes32 r;\n /// @notice Signature s value.\n bytes32 s;\n /// @notice Signature recovery value.\n uint8 v;\n }\n\n /// @notice Validates the SPV proof of the Bitcoin transaction.\n /// Reverts in case the validation or proof verification fail.\n /// @param txInfo Bitcoin transaction data\n /// @param proof Bitcoin proof data\n /// @return txHash Proven 32-byte transaction hash.\n function validateProof(\n BridgeState.Storage storage self,\n Info calldata txInfo,\n Proof calldata proof\n ) internal view returns (bytes32 txHash) {\n require(\n txInfo.inputVector.validateVin(),\n \"Invalid input vector provided\"\n );\n require(\n txInfo.outputVector.validateVout(),\n \"Invalid output vector provided\"\n );\n\n txHash = abi\n .encodePacked(\n txInfo.version,\n txInfo.inputVector,\n txInfo.outputVector,\n txInfo.locktime\n )\n .hash256View();\n\n require(\n txHash.prove(\n proof.bitcoinHeaders.extractMerkleRootLE(),\n proof.merkleProof,\n proof.txIndexInBlock\n ),\n \"Tx merkle proof is not valid for provided header and tx hash\"\n );\n\n evaluateProofDifficulty(self, proof.bitcoinHeaders);\n\n return txHash;\n }\n\n /// @notice Evaluates the given Bitcoin proof difficulty against the actual\n /// Bitcoin chain difficulty provided by the relay oracle.\n /// Reverts in case the evaluation fails.\n /// @param bitcoinHeaders Bitcoin headers chain being part of the SPV\n /// proof. Used to extract the observed proof difficulty\n function evaluateProofDifficulty(\n BridgeState.Storage storage self,\n bytes memory bitcoinHeaders\n ) internal view {\n IRelay relay = self.relay;\n uint256 currentEpochDifficulty = relay.getCurrentEpochDifficulty();\n uint256 previousEpochDifficulty = relay.getPrevEpochDifficulty();\n\n uint256 requestedDiff = 0;\n uint256 firstHeaderDiff = bitcoinHeaders\n .extractTarget()\n .calculateDifficulty();\n\n if (firstHeaderDiff == currentEpochDifficulty) {\n requestedDiff = currentEpochDifficulty;\n } else if (firstHeaderDiff == previousEpochDifficulty) {\n requestedDiff = previousEpochDifficulty;\n } else {\n revert(\"Not at current or previous difficulty\");\n }\n\n uint256 observedDiff = bitcoinHeaders.validateHeaderChain();\n\n require(\n observedDiff != ValidateSPV.getErrBadLength(),\n \"Invalid length of the headers chain\"\n );\n require(\n observedDiff != ValidateSPV.getErrInvalidChain(),\n \"Invalid headers chain\"\n );\n require(\n observedDiff != ValidateSPV.getErrLowWork(),\n \"Insufficient work in a header\"\n );\n\n require(\n observedDiff >= requestedDiff * self.txProofDifficultyFactor,\n \"Insufficient accumulated difficulty in header chain\"\n );\n }\n\n /// @notice Extracts public key hash from the provided P2PKH or P2WPKH output.\n /// Reverts if the validation fails.\n /// @param output The transaction output\n /// @return pubKeyHash 20-byte public key hash the output locks funds on\n /// @dev Requirements:\n /// - The output must be of P2PKH or P2WPKH type and lock the funds\n /// on a 20-byte public key hash\n function extractPubKeyHash(BridgeState.Storage storage, bytes memory output)\n internal\n view\n returns (bytes20 pubKeyHash)\n {\n bytes memory pubKeyHashBytes = output.extractHash();\n\n require(\n pubKeyHashBytes.length == 20,\n \"Output's public key hash must have 20 bytes\"\n );\n\n pubKeyHash = pubKeyHashBytes.slice20(0);\n\n // We need to make sure that the 20-byte public key hash\n // is actually used in the right context of a P2PKH or P2WPKH\n // output. To do so, we must extract the full script from the output\n // and compare with the expected P2PKH and P2WPKH scripts\n // referring to that 20-byte public key hash. The output consists\n // of an 8-byte value and a variable length script. To extract the\n // script we slice the output starting from 9th byte until the end.\n bytes32 outputScriptKeccak = keccak256(\n output.slice(8, output.length - 8)\n );\n // Build the expected P2PKH script which has the following byte\n // format: <0x1976a914> <20-byte PKH> <0x88ac>. According to\n // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:\n // - 0x19: Byte length of the entire script\n // - 0x76: OP_DUP\n // - 0xa9: OP_HASH160\n // - 0x14: Byte length of the public key hash\n // - 0x88: OP_EQUALVERIFY\n // - 0xac: OP_CHECKSIG\n // which matches the P2PKH structure as per:\n // https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash\n bytes32 P2PKHScriptKeccak = keccak256(\n abi.encodePacked(hex\"1976a914\", pubKeyHash, hex\"88ac\")\n );\n // Build the expected P2WPKH script which has the following format:\n // <0x160014> <20-byte PKH>. According to\n // https://en.bitcoin.it/wiki/Script#Opcodes this translates to:\n // - 0x16: Byte length of the entire script\n // - 0x00: OP_0\n // - 0x14: Byte length of the public key hash\n // which matches the P2WPKH structure as per:\n // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH\n bytes32 P2WPKHScriptKeccak = keccak256(\n abi.encodePacked(hex\"160014\", pubKeyHash)\n );\n // Make sure the actual output script matches either the P2PKH\n // or P2WPKH format.\n require(\n outputScriptKeccak == P2PKHScriptKeccak ||\n outputScriptKeccak == P2WPKHScriptKeccak,\n \"Output must be P2PKH or P2WPKH\"\n );\n\n return pubKeyHash;\n }\n}\n"
126
126
  },
127
127
  "contracts/bridge/EcdsaLib.sol": {
128
128
  "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\n\nlibrary EcdsaLib {\n using BytesLib for bytes;\n\n /// @notice Converts public key X and Y coordinates (32-byte each) to a\n /// compressed public key (33-byte). Compressed public key is X\n /// coordinate prefixed with `02` or `03` based on the Y coordinate parity.\n /// It is expected that the uncompressed public key is stripped\n /// (i.e. it is not prefixed with `04`).\n /// @param x Wallet's public key's X coordinate.\n /// @param y Wallet's public key's Y coordinate.\n /// @return Compressed public key (33-byte), prefixed with `02` or `03`.\n function compressPublicKey(bytes32 x, bytes32 y)\n internal\n pure\n returns (bytes memory)\n {\n bytes1 prefix;\n if (uint256(y) % 2 == 0) {\n prefix = hex\"02\";\n } else {\n prefix = hex\"03\";\n }\n\n return bytes.concat(prefix, x);\n }\n}\n"
@@ -173,13 +173,19 @@
173
173
  "content": "// SPDX-License-Identifier: MIT\n//\n// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀\n// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌\n// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓\n//\n// Trust math, not hardware.\n\npragma solidity ^0.8.9;\n\ninterface IWalletOwner {\n /// @notice Callback function executed once a new wallet is created.\n /// @dev Should be callable only by the Wallet Registry.\n /// @param walletID Wallet's unique identifier.\n /// @param publicKeyY Wallet's public key's X coordinate.\n /// @param publicKeyY Wallet's public key's Y coordinate.\n function __ecdsaWalletCreatedCallback(\n bytes32 walletID,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n ) external;\n\n /// @notice Callback function executed once a wallet heartbeat failure\n /// is detected.\n /// @dev Should be callable only by the Wallet Registry.\n /// @param walletID Wallet's unique identifier.\n /// @param publicKeyY Wallet's public key's X coordinate.\n /// @param publicKeyY Wallet's public key's Y coordinate.\n function __ecdsaWalletHeartbeatFailedCallback(\n bytes32 walletID,\n bytes32 publicKeyX,\n bytes32 publicKeyY\n ) external;\n}\n"
174
174
  },
175
175
  "contracts/bridge/DepositSweep.sol": {
176
- "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"./BitcoinTx.sol\";\nimport \"./BridgeState.sol\";\nimport \"./Wallets.sol\";\n\nimport \"../bank/Bank.sol\";\n\n/// @title Bridge deposit sweep\n/// @notice The library handles the logic for sweeping transactions revealed to\n/// the Bridge\n/// @dev Bridge active wallet periodically signs a transaction that unlocks all\n/// of the valid, revealed deposits above the dust threshold, combines them\n/// into a single UTXO with the existing main wallet UTXO, and relocks\n/// those transactions without a 30-day refund clause to the same wallet.\n/// This has two main effects: it consolidates the UTXO set and it disables\n/// the refund. Balances of depositors in the Bank are increased when the\n/// SPV sweep proof is submitted to the Bridge.\nlibrary DepositSweep {\n using BridgeState for BridgeState.Storage;\n using BitcoinTx for BridgeState.Storage;\n\n using BTCUtils for bytes;\n\n /// @notice Represents an outcome of the sweep Bitcoin transaction\n /// inputs processing.\n struct DepositSweepTxInputsInfo {\n // Sum of all inputs values i.e. all deposits and main UTXO value,\n // if present.\n uint256 inputsTotalValue;\n // Addresses of depositors who performed processed deposits. Ordered in\n // the same order as deposits inputs in the input vector. Size of this\n // array is either equal to the number of inputs (main UTXO doesn't\n // exist) or less by one (main UTXO exists and is pointed by one of\n // the inputs).\n address[] depositors;\n // Amounts of deposits corresponding to processed deposits. Ordered in\n // the same order as deposits inputs in the input vector. Size of this\n // array is either equal to the number of inputs (main UTXO doesn't\n // exist) or less by one (main UTXO exists and is pointed by one of\n // the inputs).\n uint256[] depositedAmounts;\n // Values of the treasury fee corresponding to processed deposits.\n // Ordered in the same order as deposits inputs in the input vector.\n // Size of this array is either equal to the number of inputs (main\n // UTXO doesn't exist) or less by one (main UTXO exists and is pointed\n // by one of the inputs).\n uint256[] treasuryFees;\n }\n\n event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);\n\n /// @notice Used by the wallet to prove the BTC deposit sweep transaction\n /// and to update Bank balances accordingly. Sweep is only accepted\n /// if it satisfies SPV proof.\n ///\n /// The function is performing Bank balance updates by first\n /// computing the Bitcoin fee for the sweep transaction. The fee is\n /// divided evenly between all swept deposits. Each depositor\n /// receives a balance in the bank equal to the amount inferred\n /// during the reveal transaction, minus their fee share.\n ///\n /// It is possible to prove the given sweep only one time.\n /// @param sweepTx Bitcoin sweep transaction data\n /// @param sweepProof Bitcoin sweep proof data\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\n /// the Ethereum chain. If no main UTXO exists for the given wallet,\n /// this parameter is ignored\n /// @dev Requirements:\n /// - `sweepTx` components must match the expected structure. See\n /// `BitcoinTx.Info` docs for reference. Their values must exactly\n /// correspond to appropriate Bitcoin transaction fields to produce\n /// a provable transaction hash.\n /// - The `sweepTx` should represent a Bitcoin transaction with 1..n\n /// inputs. If the wallet has no main UTXO, all n inputs should\n /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has\n /// an existing main UTXO, one of the n inputs must point to that\n /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH\n /// revealed deposits UTXOs. That transaction must have only\n /// one P2(W)PKH output locking funds on the 20-byte wallet public\n /// key hash.\n /// - `sweepProof` components must match the expected structure. See\n /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`\n /// field must contain a valid number of block headers, not less\n /// than the `txProofDifficultyFactor` contract constant.\n /// - `mainUtxo` components must point to the recent main UTXO\n /// of the given wallet, as currently known on the Ethereum chain.\n /// If there is no main UTXO, this parameter is ignored.\n function submitDepositSweepProof(\n BridgeState.Storage storage self,\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo\n ) external {\n // The actual transaction proof is performed here. After that point, we\n // can assume the transaction happened on Bitcoin chain and has\n // a sufficient number of confirmations as determined by\n // `txProofDifficultyFactor` constant.\n bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);\n\n // Process sweep transaction output and extract its target wallet\n // public key hash and value.\n (\n bytes20 walletPubKeyHash,\n uint64 sweepTxOutputValue\n ) = processDepositSweepTxOutput(sweepTx.outputVector);\n\n (\n Wallets.Wallet storage wallet,\n BitcoinTx.UTXO memory resolvedMainUtxo\n ) = resolveDepositSweepingWallet(self, walletPubKeyHash, mainUtxo);\n\n // Process sweep transaction inputs and extract all information needed\n // to perform deposit bookkeeping.\n DepositSweepTxInputsInfo\n memory inputsInfo = processDepositSweepTxInputs(\n self,\n sweepTx.inputVector,\n resolvedMainUtxo\n );\n\n // Helper variable that will hold the sum of treasury fees paid by\n // all deposits.\n uint256 totalTreasuryFee = 0;\n\n // Determine the transaction fee that should be incurred by each deposit\n // and the indivisible remainder that should be additionally incurred\n // by the last deposit.\n (\n uint256 depositTxFee,\n uint256 depositTxFeeRemainder\n ) = depositSweepTxFeeDistribution(\n inputsInfo.inputsTotalValue,\n sweepTxOutputValue,\n inputsInfo.depositedAmounts.length\n );\n\n // Make sure the highest value of the deposit transaction fee does not\n // exceed the maximum value limited by the governable parameter.\n require(\n depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,\n \"Transaction fee is too high\"\n );\n\n // Reduce each deposit amount by treasury fee and transaction fee.\n for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {\n // The last deposit should incur the deposit transaction fee\n // remainder.\n uint256 depositTxFeeIncurred = i ==\n inputsInfo.depositedAmounts.length - 1\n ? depositTxFee + depositTxFeeRemainder\n : depositTxFee;\n\n // There is no need to check whether\n // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`\n // since the `depositDustThreshold` should force that condition\n // to be always true.\n inputsInfo.depositedAmounts[i] =\n inputsInfo.depositedAmounts[i] -\n inputsInfo.treasuryFees[i] -\n depositTxFeeIncurred;\n totalTreasuryFee += inputsInfo.treasuryFees[i];\n }\n\n // Record this sweep data and assign them to the wallet public key hash\n // as new main UTXO. Transaction output index is always 0 as sweep\n // transaction always contains only one output.\n wallet.mainUtxoHash = keccak256(\n abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)\n );\n\n emit DepositsSwept(walletPubKeyHash, sweepTxHash);\n\n // Update depositors balances in the Bank.\n self.bank.increaseBalances(\n inputsInfo.depositors,\n inputsInfo.depositedAmounts\n );\n // Pass the treasury fee to the treasury address.\n self.bank.increaseBalance(self.treasury, totalTreasuryFee);\n\n // TODO: Handle deposits having `vault` set.\n }\n\n /// @notice Resolves sweeping wallet based on the provided wallet public key\n /// hash. Validates the wallet state and current main UTXO, as\n /// currently known on the Ethereum chain.\n /// @param walletPubKeyHash public key hash of the wallet proving the sweep\n /// Bitcoin transaction.\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\n /// the Ethereum chain. If no main UTXO exists for the given wallet,\n /// this parameter is ignored\n /// @return wallet Data of the sweeping wallet.\n /// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet\n /// resolved by cross-checking the `mainUtxo` parameter with\n /// the chain state. If the validation went well, this is the\n /// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.\n /// @dev Requirements:\n /// - Sweeping wallet must be either in Live or MovingFunds state.\n /// - If the main UTXO of the sweeping wallet exists in the storage,\n /// the passed `mainUTXO` parameter must be equal to the stored one.\n function resolveDepositSweepingWallet(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata mainUtxo\n )\n internal\n view\n returns (\n Wallets.Wallet storage wallet,\n BitcoinTx.UTXO memory resolvedMainUtxo\n )\n {\n wallet = self.registeredWallets[walletPubKeyHash];\n\n Wallets.WalletState walletState = wallet.state;\n require(\n walletState == Wallets.WalletState.Live ||\n walletState == Wallets.WalletState.MovingFunds,\n \"Wallet must be in Live or MovingFunds state\"\n );\n\n // Check if the main UTXO for given wallet exists. If so, validate\n // passed main UTXO data against the stored hash and use them for\n // further processing. If no main UTXO exists, use empty data.\n resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\n if (mainUtxoHash != bytes32(0)) {\n require(\n keccak256(\n abi.encodePacked(\n mainUtxo.txHash,\n mainUtxo.txOutputIndex,\n mainUtxo.txOutputValue\n )\n ) == mainUtxoHash,\n \"Invalid main UTXO data\"\n );\n resolvedMainUtxo = mainUtxo;\n }\n }\n\n /// @notice Processes the Bitcoin sweep transaction output vector by\n /// extracting the single output and using it to gain additional\n /// information required for further processing (e.g. value and\n /// wallet public key hash).\n /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.\n /// This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVout` function before\n /// it is passed here\n /// @return walletPubKeyHash 20-byte wallet public key hash.\n /// @return value 8-byte sweep transaction output value.\n function processDepositSweepTxOutput(bytes memory sweepTxOutputVector)\n internal\n pure\n returns (bytes20 walletPubKeyHash, uint64 value)\n {\n // To determine the total number of sweep transaction outputs, we need to\n // parse the compactSize uint (VarInt) the output vector is prepended by.\n // That compactSize uint encodes the number of vector elements using the\n // format presented in:\n // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers\n // We don't need asserting the compactSize uint is parseable since it\n // was already checked during `validateVout` validation.\n // See `BitcoinTx.outputVector` docs for more details.\n (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();\n require(\n outputsCount == 1,\n \"Sweep transaction must have a single output\"\n );\n\n bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);\n value = output.extractValue();\n // TODO: Extract `walletPubKeyHash` using `self.extractPubKeyHash`\n // in order to get stronger validation.\n bytes memory walletPubKeyHashBytes = output.extractHash();\n // The sweep transaction output should always be P2PKH or P2WPKH.\n // In both cases, the wallet public key hash should be 20 bytes length.\n require(\n walletPubKeyHashBytes.length == 20,\n \"Wallet public key hash should have 20 bytes\"\n );\n /* solhint-disable-next-line no-inline-assembly */\n assembly {\n walletPubKeyHash := mload(add(walletPubKeyHashBytes, 32))\n }\n\n return (walletPubKeyHash, value);\n }\n\n /// @notice Processes the Bitcoin sweep transaction input vector. It\n /// extracts each input and tries to obtain associated deposit or\n /// main UTXO data, depending on the input type. Reverts\n /// if one of the inputs cannot be recognized as a pointer to a\n /// revealed deposit or expected main UTXO.\n /// This function also marks each processed deposit as swept.\n /// @param sweepTxInputVector Bitcoin sweep transaction input vector.\n /// This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVin` function before\n /// it is passed here\n /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO\n /// exists for the given the wallet, this parameter's fields should\n /// be zeroed to bypass the main UTXO validation\n /// @return info Outcomes of the processing.\n function processDepositSweepTxInputs(\n BridgeState.Storage storage self,\n bytes memory sweepTxInputVector,\n BitcoinTx.UTXO memory mainUtxo\n ) internal returns (DepositSweepTxInputsInfo memory info) {\n // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO\n // for the given wallet doesn't exist and it is not expected to be\n // included in the sweep transaction input vector.\n bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);\n bool mainUtxoFound = false;\n\n // Determining the total number of sweep transaction inputs in the same\n // way as for number of outputs. See `BitcoinTx.inputVector` docs for\n // more details.\n (\n uint256 inputsCompactSizeUintLength,\n uint256 inputsCount\n ) = sweepTxInputVector.parseVarInt();\n\n // To determine the first input starting index, we must jump over\n // the compactSize uint which prepends the input vector. One byte\n // must be added because `BtcUtils.parseVarInt` does not include\n // compactSize uint tag in the returned length.\n //\n // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`\n // returns `0`, so we jump over one byte of compactSize uint.\n //\n // For >= 253 && <= 0xffff there is `0xfd` tag,\n // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no\n // tag byte included) so we need to jump over 1+2 bytes of\n // compactSize uint.\n //\n // Please refer `BTCUtils` library and compactSize uint\n // docs in `BitcoinTx` library for more details.\n uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;\n\n // Determine the swept deposits count. If main UTXO is NOT expected,\n // all inputs should be deposits. If main UTXO is expected, one input\n // should point to that main UTXO.\n info.depositors = new address[](\n !mainUtxoExpected ? inputsCount : inputsCount - 1\n );\n info.depositedAmounts = new uint256[](info.depositors.length);\n info.treasuryFees = new uint256[](info.depositors.length);\n\n // Initialize helper variables.\n uint256 processedDepositsCount = 0;\n\n // Inputs processing loop.\n for (uint256 i = 0; i < inputsCount; i++) {\n (\n bytes32 outpointTxHash,\n uint32 outpointIndex,\n uint256 inputLength\n ) = parseDepositSweepTxInputAt(\n sweepTxInputVector,\n inputStartingIndex\n );\n\n Deposit.DepositRequest storage deposit = self.deposits[\n uint256(\n keccak256(abi.encodePacked(outpointTxHash, outpointIndex))\n )\n ];\n\n if (deposit.revealedAt != 0) {\n // If we entered here, that means the input was identified as\n // a revealed deposit.\n require(deposit.sweptAt == 0, \"Deposit already swept\");\n\n if (processedDepositsCount == info.depositors.length) {\n // If this condition is true, that means a deposit input\n // took place of an expected main UTXO input.\n // In other words, there is no expected main UTXO\n // input and all inputs come from valid, revealed deposits.\n revert(\n \"Expected main UTXO not present in sweep transaction inputs\"\n );\n }\n\n /* solhint-disable-next-line not-rely-on-time */\n deposit.sweptAt = uint32(block.timestamp);\n\n info.depositors[processedDepositsCount] = deposit.depositor;\n info.depositedAmounts[processedDepositsCount] = deposit.amount;\n info.inputsTotalValue += info.depositedAmounts[\n processedDepositsCount\n ];\n info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;\n\n processedDepositsCount++;\n } else if (\n mainUtxoExpected != mainUtxoFound &&\n mainUtxo.txHash == outpointTxHash\n // TODO: We probably need to check `mainUtxo.txOutputIndex == outpointIndex` as well.\n ) {\n // If we entered here, that means the input was identified as\n // the expected main UTXO.\n info.inputsTotalValue += mainUtxo.txOutputValue;\n mainUtxoFound = true;\n\n // Main UTXO used as an input, mark it as spent.\n self.spentMainUTXOs[\n uint256(\n keccak256(\n abi.encodePacked(outpointTxHash, outpointIndex)\n )\n )\n ] = true;\n } else {\n revert(\"Unknown input type\");\n }\n\n // Make the `inputStartingIndex` pointing to the next input by\n // increasing it by current input's length.\n inputStartingIndex += inputLength;\n }\n\n // Construction of the input processing loop guarantees that:\n // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`\n // is always true at this point. We just use the first variable\n // to assert the total count of swept deposit is bigger than zero.\n require(\n processedDepositsCount > 0,\n \"Sweep transaction must process at least one deposit\"\n );\n\n // Assert the main UTXO was used as one of current sweep's inputs if\n // it was actually expected.\n require(\n mainUtxoExpected == mainUtxoFound,\n \"Expected main UTXO not present in sweep transaction inputs\"\n );\n\n return info;\n }\n\n /// @notice Parses a Bitcoin transaction input starting at the given index.\n /// @param inputVector Bitcoin transaction input vector\n /// @param inputStartingIndex Index the given input starts at\n /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is\n /// pointed in the given input's outpoint.\n /// @return outpointIndex 4-byte index of the Bitcoin transaction output\n /// which is pointed in the given input's outpoint.\n /// @return inputLength Byte length of the given input.\n /// @dev This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVin` function before it\n /// is passed here.\n function parseDepositSweepTxInputAt(\n bytes memory inputVector,\n uint256 inputStartingIndex\n )\n internal\n pure\n returns (\n bytes32 outpointTxHash,\n uint32 outpointIndex,\n uint256 inputLength\n )\n {\n outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);\n\n outpointIndex = BTCUtils.reverseUint32(\n uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))\n );\n\n inputLength = inputVector.determineInputLengthAt(inputStartingIndex);\n\n return (outpointTxHash, outpointIndex, inputLength);\n }\n\n /// @notice Determines the distribution of the sweep transaction fee\n /// over swept deposits.\n /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.\n /// @param sweepTxOutputValue Value of the sweep transaction output.\n /// @param depositsCount Count of the deposits swept by the sweep transaction.\n /// @return depositTxFee Transaction fee per deposit determined by evenly\n /// spreading the divisible part of the sweep transaction fee\n /// over all deposits.\n /// @return depositTxFeeRemainder The indivisible part of the sweep\n /// transaction fee than cannot be distributed over all deposits.\n /// @dev It is up to the caller to decide how the remainder should be\n /// counted in. This function only computes its value.\n function depositSweepTxFeeDistribution(\n uint256 sweepTxInputsTotalValue,\n uint256 sweepTxOutputValue,\n uint256 depositsCount\n )\n internal\n pure\n returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)\n {\n // The sweep transaction fee is just the difference between inputs\n // amounts sum and the output amount.\n uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;\n // Compute the indivisible remainder that remains after dividing the\n // sweep transaction fee over all deposits evenly.\n depositTxFeeRemainder = sweepTxFee % depositsCount;\n // Compute the transaction fee per deposit by dividing the sweep\n // transaction fee (reduced by the remainder) by the number of deposits.\n depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;\n\n return (depositTxFee, depositTxFeeRemainder);\n }\n}\n"
176
+ "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport {BTCUtils} from \"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\";\n\nimport \"./BitcoinTx.sol\";\nimport \"./BridgeState.sol\";\nimport \"./Wallets.sol\";\n\nimport \"../bank/Bank.sol\";\n\n/// @title Bridge deposit sweep\n/// @notice The library handles the logic for sweeping transactions revealed to\n/// the Bridge\n/// @dev Bridge active wallet periodically signs a transaction that unlocks all\n/// of the valid, revealed deposits above the dust threshold, combines them\n/// into a single UTXO with the existing main wallet UTXO, and relocks\n/// those transactions without a 30-day refund clause to the same wallet.\n/// This has two main effects: it consolidates the UTXO set and it disables\n/// the refund. Balances of depositors in the Bank are increased when the\n/// SPV sweep proof is submitted to the Bridge.\nlibrary DepositSweep {\n using BridgeState for BridgeState.Storage;\n using BitcoinTx for BridgeState.Storage;\n\n using BTCUtils for bytes;\n\n /// @notice Represents an outcome of the sweep Bitcoin transaction\n /// inputs processing.\n struct DepositSweepTxInputsInfo {\n // Sum of all inputs values i.e. all deposits and main UTXO value,\n // if present.\n uint256 inputsTotalValue;\n // Addresses of depositors who performed processed deposits. Ordered in\n // the same order as deposits inputs in the input vector. Size of this\n // array is either equal to the number of inputs (main UTXO doesn't\n // exist) or less by one (main UTXO exists and is pointed by one of\n // the inputs).\n address[] depositors;\n // Amounts of deposits corresponding to processed deposits. Ordered in\n // the same order as deposits inputs in the input vector. Size of this\n // array is either equal to the number of inputs (main UTXO doesn't\n // exist) or less by one (main UTXO exists and is pointed by one of\n // the inputs).\n uint256[] depositedAmounts;\n // Values of the treasury fee corresponding to processed deposits.\n // Ordered in the same order as deposits inputs in the input vector.\n // Size of this array is either equal to the number of inputs (main\n // UTXO doesn't exist) or less by one (main UTXO exists and is pointed\n // by one of the inputs).\n uint256[] treasuryFees;\n }\n\n event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);\n\n /// @notice Used by the wallet to prove the BTC deposit sweep transaction\n /// and to update Bank balances accordingly. Sweep is only accepted\n /// if it satisfies SPV proof.\n ///\n /// The function is performing Bank balance updates by first\n /// computing the Bitcoin fee for the sweep transaction. The fee is\n /// divided evenly between all swept deposits. Each depositor\n /// receives a balance in the bank equal to the amount inferred\n /// during the reveal transaction, minus their fee share.\n ///\n /// It is possible to prove the given sweep only one time.\n /// @param sweepTx Bitcoin sweep transaction data\n /// @param sweepProof Bitcoin sweep proof data\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\n /// the Ethereum chain. If no main UTXO exists for the given wallet,\n /// this parameter is ignored\n /// @dev Requirements:\n /// - `sweepTx` components must match the expected structure. See\n /// `BitcoinTx.Info` docs for reference. Their values must exactly\n /// correspond to appropriate Bitcoin transaction fields to produce\n /// a provable transaction hash.\n /// - The `sweepTx` should represent a Bitcoin transaction with 1..n\n /// inputs. If the wallet has no main UTXO, all n inputs should\n /// correspond to P2(W)SH revealed deposits UTXOs. If the wallet has\n /// an existing main UTXO, one of the n inputs must point to that\n /// main UTXO and remaining n-1 inputs should correspond to P2(W)SH\n /// revealed deposits UTXOs. That transaction must have only\n /// one P2(W)PKH output locking funds on the 20-byte wallet public\n /// key hash.\n /// - `sweepProof` components must match the expected structure. See\n /// `BitcoinTx.Proof` docs for reference. The `bitcoinHeaders`\n /// field must contain a valid number of block headers, not less\n /// than the `txProofDifficultyFactor` contract constant.\n /// - `mainUtxo` components must point to the recent main UTXO\n /// of the given wallet, as currently known on the Ethereum chain.\n /// If there is no main UTXO, this parameter is ignored.\n function submitDepositSweepProof(\n BridgeState.Storage storage self,\n BitcoinTx.Info calldata sweepTx,\n BitcoinTx.Proof calldata sweepProof,\n BitcoinTx.UTXO calldata mainUtxo\n ) external {\n // The actual transaction proof is performed here. After that point, we\n // can assume the transaction happened on Bitcoin chain and has\n // a sufficient number of confirmations as determined by\n // `txProofDifficultyFactor` constant.\n bytes32 sweepTxHash = self.validateProof(sweepTx, sweepProof);\n\n // Process sweep transaction output and extract its target wallet\n // public key hash and value.\n (\n bytes20 walletPubKeyHash,\n uint64 sweepTxOutputValue\n ) = processDepositSweepTxOutput(self, sweepTx.outputVector);\n\n (\n Wallets.Wallet storage wallet,\n BitcoinTx.UTXO memory resolvedMainUtxo\n ) = resolveDepositSweepingWallet(self, walletPubKeyHash, mainUtxo);\n\n // Process sweep transaction inputs and extract all information needed\n // to perform deposit bookkeeping.\n DepositSweepTxInputsInfo\n memory inputsInfo = processDepositSweepTxInputs(\n self,\n sweepTx.inputVector,\n resolvedMainUtxo\n );\n\n // Helper variable that will hold the sum of treasury fees paid by\n // all deposits.\n uint256 totalTreasuryFee = 0;\n\n // Determine the transaction fee that should be incurred by each deposit\n // and the indivisible remainder that should be additionally incurred\n // by the last deposit.\n (\n uint256 depositTxFee,\n uint256 depositTxFeeRemainder\n ) = depositSweepTxFeeDistribution(\n inputsInfo.inputsTotalValue,\n sweepTxOutputValue,\n inputsInfo.depositedAmounts.length\n );\n\n // Make sure the highest value of the deposit transaction fee does not\n // exceed the maximum value limited by the governable parameter.\n require(\n depositTxFee + depositTxFeeRemainder <= self.depositTxMaxFee,\n \"Transaction fee is too high\"\n );\n\n // Reduce each deposit amount by treasury fee and transaction fee.\n for (uint256 i = 0; i < inputsInfo.depositedAmounts.length; i++) {\n // The last deposit should incur the deposit transaction fee\n // remainder.\n uint256 depositTxFeeIncurred = i ==\n inputsInfo.depositedAmounts.length - 1\n ? depositTxFee + depositTxFeeRemainder\n : depositTxFee;\n\n // There is no need to check whether\n // `inputsInfo.depositedAmounts[i] - inputsInfo.treasuryFees[i] - txFee > 0`\n // since the `depositDustThreshold` should force that condition\n // to be always true.\n inputsInfo.depositedAmounts[i] =\n inputsInfo.depositedAmounts[i] -\n inputsInfo.treasuryFees[i] -\n depositTxFeeIncurred;\n totalTreasuryFee += inputsInfo.treasuryFees[i];\n }\n\n // Record this sweep data and assign them to the wallet public key hash\n // as new main UTXO. Transaction output index is always 0 as sweep\n // transaction always contains only one output.\n wallet.mainUtxoHash = keccak256(\n abi.encodePacked(sweepTxHash, uint32(0), sweepTxOutputValue)\n );\n\n // slither-disable-next-line reentrancy-events\n emit DepositsSwept(walletPubKeyHash, sweepTxHash);\n\n // Update depositors balances in the Bank.\n self.bank.increaseBalances(\n inputsInfo.depositors,\n inputsInfo.depositedAmounts\n );\n // Pass the treasury fee to the treasury address.\n self.bank.increaseBalance(self.treasury, totalTreasuryFee);\n\n // TODO: Handle deposits having `vault` set.\n }\n\n /// @notice Resolves sweeping wallet based on the provided wallet public key\n /// hash. Validates the wallet state and current main UTXO, as\n /// currently known on the Ethereum chain.\n /// @param walletPubKeyHash public key hash of the wallet proving the sweep\n /// Bitcoin transaction.\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\n /// the Ethereum chain. If no main UTXO exists for the given wallet,\n /// this parameter is ignored\n /// @return wallet Data of the sweeping wallet.\n /// @return resolvedMainUtxo The actual main UTXO of the sweeping wallet\n /// resolved by cross-checking the `mainUtxo` parameter with\n /// the chain state. If the validation went well, this is the\n /// plain-text main UTXO corresponding to the `wallet.mainUtxoHash`.\n /// @dev Requirements:\n /// - Sweeping wallet must be either in Live or MovingFunds state.\n /// - If the main UTXO of the sweeping wallet exists in the storage,\n /// the passed `mainUTXO` parameter must be equal to the stored one.\n function resolveDepositSweepingWallet(\n BridgeState.Storage storage self,\n bytes20 walletPubKeyHash,\n BitcoinTx.UTXO calldata mainUtxo\n )\n internal\n view\n returns (\n Wallets.Wallet storage wallet,\n BitcoinTx.UTXO memory resolvedMainUtxo\n )\n {\n wallet = self.registeredWallets[walletPubKeyHash];\n\n Wallets.WalletState walletState = wallet.state;\n require(\n walletState == Wallets.WalletState.Live ||\n walletState == Wallets.WalletState.MovingFunds,\n \"Wallet must be in Live or MovingFunds state\"\n );\n\n // Check if the main UTXO for given wallet exists. If so, validate\n // passed main UTXO data against the stored hash and use them for\n // further processing. If no main UTXO exists, use empty data.\n resolvedMainUtxo = BitcoinTx.UTXO(bytes32(0), 0, 0);\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\n if (mainUtxoHash != bytes32(0)) {\n require(\n keccak256(\n abi.encodePacked(\n mainUtxo.txHash,\n mainUtxo.txOutputIndex,\n mainUtxo.txOutputValue\n )\n ) == mainUtxoHash,\n \"Invalid main UTXO data\"\n );\n resolvedMainUtxo = mainUtxo;\n }\n }\n\n /// @notice Processes the Bitcoin sweep transaction output vector by\n /// extracting the single output and using it to gain additional\n /// information required for further processing (e.g. value and\n /// wallet public key hash).\n /// @param sweepTxOutputVector Bitcoin sweep transaction output vector.\n /// This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVout` function before\n /// it is passed here\n /// @return walletPubKeyHash 20-byte wallet public key hash.\n /// @return value 8-byte sweep transaction output value.\n function processDepositSweepTxOutput(\n BridgeState.Storage storage self,\n bytes memory sweepTxOutputVector\n ) internal view returns (bytes20 walletPubKeyHash, uint64 value) {\n // To determine the total number of sweep transaction outputs, we need to\n // parse the compactSize uint (VarInt) the output vector is prepended by.\n // That compactSize uint encodes the number of vector elements using the\n // format presented in:\n // https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers\n // We don't need asserting the compactSize uint is parseable since it\n // was already checked during `validateVout` validation.\n // See `BitcoinTx.outputVector` docs for more details.\n (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();\n require(\n outputsCount == 1,\n \"Sweep transaction must have a single output\"\n );\n\n bytes memory output = sweepTxOutputVector.extractOutputAtIndex(0);\n walletPubKeyHash = self.extractPubKeyHash(output);\n value = output.extractValue();\n\n return (walletPubKeyHash, value);\n }\n\n /// @notice Processes the Bitcoin sweep transaction input vector. It\n /// extracts each input and tries to obtain associated deposit or\n /// main UTXO data, depending on the input type. Reverts\n /// if one of the inputs cannot be recognized as a pointer to a\n /// revealed deposit or expected main UTXO.\n /// This function also marks each processed deposit as swept.\n /// @param sweepTxInputVector Bitcoin sweep transaction input vector.\n /// This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVin` function before\n /// it is passed here\n /// @param mainUtxo Data of the wallet's main UTXO. If no main UTXO\n /// exists for the given the wallet, this parameter's fields should\n /// be zeroed to bypass the main UTXO validation\n /// @return info Outcomes of the processing.\n function processDepositSweepTxInputs(\n BridgeState.Storage storage self,\n bytes memory sweepTxInputVector,\n BitcoinTx.UTXO memory mainUtxo\n ) internal returns (DepositSweepTxInputsInfo memory info) {\n // If the passed `mainUtxo` parameter's values are zeroed, the main UTXO\n // for the given wallet doesn't exist and it is not expected to be\n // included in the sweep transaction input vector.\n bool mainUtxoExpected = mainUtxo.txHash != bytes32(0);\n bool mainUtxoFound = false;\n\n // Determining the total number of sweep transaction inputs in the same\n // way as for number of outputs. See `BitcoinTx.inputVector` docs for\n // more details.\n (\n uint256 inputsCompactSizeUintLength,\n uint256 inputsCount\n ) = sweepTxInputVector.parseVarInt();\n\n // To determine the first input starting index, we must jump over\n // the compactSize uint which prepends the input vector. One byte\n // must be added because `BtcUtils.parseVarInt` does not include\n // compactSize uint tag in the returned length.\n //\n // For >= 0 && <= 252, `BTCUtils.determineVarIntDataLengthAt`\n // returns `0`, so we jump over one byte of compactSize uint.\n //\n // For >= 253 && <= 0xffff there is `0xfd` tag,\n // `BTCUtils.determineVarIntDataLengthAt` returns `2` (no\n // tag byte included) so we need to jump over 1+2 bytes of\n // compactSize uint.\n //\n // Please refer `BTCUtils` library and compactSize uint\n // docs in `BitcoinTx` library for more details.\n uint256 inputStartingIndex = 1 + inputsCompactSizeUintLength;\n\n // Determine the swept deposits count. If main UTXO is NOT expected,\n // all inputs should be deposits. If main UTXO is expected, one input\n // should point to that main UTXO.\n info.depositors = new address[](\n !mainUtxoExpected ? inputsCount : inputsCount - 1\n );\n info.depositedAmounts = new uint256[](info.depositors.length);\n info.treasuryFees = new uint256[](info.depositors.length);\n\n // Initialize helper variables.\n uint256 processedDepositsCount = 0;\n\n // Inputs processing loop.\n for (uint256 i = 0; i < inputsCount; i++) {\n (\n bytes32 outpointTxHash,\n uint32 outpointIndex,\n uint256 inputLength\n ) = parseDepositSweepTxInputAt(\n sweepTxInputVector,\n inputStartingIndex\n );\n\n Deposit.DepositRequest storage deposit = self.deposits[\n uint256(\n keccak256(abi.encodePacked(outpointTxHash, outpointIndex))\n )\n ];\n\n if (deposit.revealedAt != 0) {\n // If we entered here, that means the input was identified as\n // a revealed deposit.\n require(deposit.sweptAt == 0, \"Deposit already swept\");\n\n if (processedDepositsCount == info.depositors.length) {\n // If this condition is true, that means a deposit input\n // took place of an expected main UTXO input.\n // In other words, there is no expected main UTXO\n // input and all inputs come from valid, revealed deposits.\n revert(\n \"Expected main UTXO not present in sweep transaction inputs\"\n );\n }\n\n /* solhint-disable-next-line not-rely-on-time */\n deposit.sweptAt = uint32(block.timestamp);\n\n info.depositors[processedDepositsCount] = deposit.depositor;\n info.depositedAmounts[processedDepositsCount] = deposit.amount;\n info.inputsTotalValue += info.depositedAmounts[\n processedDepositsCount\n ];\n info.treasuryFees[processedDepositsCount] = deposit.treasuryFee;\n\n processedDepositsCount++;\n } else if (\n mainUtxoExpected != mainUtxoFound &&\n mainUtxo.txHash == outpointTxHash &&\n mainUtxo.txOutputIndex == outpointIndex\n ) {\n // If we entered here, that means the input was identified as\n // the expected main UTXO.\n info.inputsTotalValue += mainUtxo.txOutputValue;\n mainUtxoFound = true;\n\n // Main UTXO used as an input, mark it as spent.\n self.spentMainUTXOs[\n uint256(\n keccak256(\n abi.encodePacked(outpointTxHash, outpointIndex)\n )\n )\n ] = true;\n } else {\n revert(\"Unknown input type\");\n }\n\n // Make the `inputStartingIndex` pointing to the next input by\n // increasing it by current input's length.\n inputStartingIndex += inputLength;\n }\n\n // Construction of the input processing loop guarantees that:\n // `processedDepositsCount == info.depositors.length == info.depositedAmounts.length`\n // is always true at this point. We just use the first variable\n // to assert the total count of swept deposit is bigger than zero.\n require(\n processedDepositsCount > 0,\n \"Sweep transaction must process at least one deposit\"\n );\n\n // Assert the main UTXO was used as one of current sweep's inputs if\n // it was actually expected.\n require(\n mainUtxoExpected == mainUtxoFound,\n \"Expected main UTXO not present in sweep transaction inputs\"\n );\n\n return info;\n }\n\n /// @notice Parses a Bitcoin transaction input starting at the given index.\n /// @param inputVector Bitcoin transaction input vector\n /// @param inputStartingIndex Index the given input starts at\n /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is\n /// pointed in the given input's outpoint.\n /// @return outpointIndex 4-byte index of the Bitcoin transaction output\n /// which is pointed in the given input's outpoint.\n /// @return inputLength Byte length of the given input.\n /// @dev This function assumes vector's structure is valid so it must be\n /// validated using e.g. `BTCUtils.validateVin` function before it\n /// is passed here.\n function parseDepositSweepTxInputAt(\n bytes memory inputVector,\n uint256 inputStartingIndex\n )\n internal\n pure\n returns (\n bytes32 outpointTxHash,\n uint32 outpointIndex,\n uint256 inputLength\n )\n {\n outpointTxHash = inputVector.extractInputTxIdLeAt(inputStartingIndex);\n\n outpointIndex = BTCUtils.reverseUint32(\n uint32(inputVector.extractTxIndexLeAt(inputStartingIndex))\n );\n\n inputLength = inputVector.determineInputLengthAt(inputStartingIndex);\n\n return (outpointTxHash, outpointIndex, inputLength);\n }\n\n /// @notice Determines the distribution of the sweep transaction fee\n /// over swept deposits.\n /// @param sweepTxInputsTotalValue Total value of all sweep transaction inputs.\n /// @param sweepTxOutputValue Value of the sweep transaction output.\n /// @param depositsCount Count of the deposits swept by the sweep transaction.\n /// @return depositTxFee Transaction fee per deposit determined by evenly\n /// spreading the divisible part of the sweep transaction fee\n /// over all deposits.\n /// @return depositTxFeeRemainder The indivisible part of the sweep\n /// transaction fee than cannot be distributed over all deposits.\n /// @dev It is up to the caller to decide how the remainder should be\n /// counted in. This function only computes its value.\n function depositSweepTxFeeDistribution(\n uint256 sweepTxInputsTotalValue,\n uint256 sweepTxOutputValue,\n uint256 depositsCount\n )\n internal\n pure\n returns (uint256 depositTxFee, uint256 depositTxFeeRemainder)\n {\n // The sweep transaction fee is just the difference between inputs\n // amounts sum and the output amount.\n uint256 sweepTxFee = sweepTxInputsTotalValue - sweepTxOutputValue;\n // Compute the indivisible remainder that remains after dividing the\n // sweep transaction fee over all deposits evenly.\n depositTxFeeRemainder = sweepTxFee % depositsCount;\n // Compute the transaction fee per deposit by dividing the sweep\n // transaction fee (reduced by the remainder) by the number of deposits.\n depositTxFee = (sweepTxFee - depositTxFeeRemainder) / depositsCount;\n\n return (depositTxFee, depositTxFeeRemainder);\n }\n}\n"
177
177
  },
178
178
  "contracts/test/TestRelay.sol": {
179
179
  "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.9;\n\nimport \"../bridge/Bridge.sol\";\n\ncontract TestRelay is IRelay {\n uint256 private currentEpochDifficulty;\n uint256 private prevEpochDifficulty;\n\n function setCurrentEpochDifficulty(uint256 _difficulty) external {\n currentEpochDifficulty = _difficulty;\n }\n\n function setPrevEpochDifficulty(uint256 _difficulty) external {\n prevEpochDifficulty = _difficulty;\n }\n\n function getCurrentEpochDifficulty()\n external\n view\n override\n returns (uint256)\n {\n return currentEpochDifficulty;\n }\n\n function getPrevEpochDifficulty() external view override returns (uint256) {\n return prevEpochDifficulty;\n }\n}\n"
180
180
  },
181
181
  "contracts/test/TestEcdsaLib.sol": {
182
- "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.9;\n\nimport \"../bridge/EcdsaLib.sol\";\n\n/// @dev This is a contract implemented to test EcdsaLib library directly.\ncontract TestEcdsaLib {\n function compressPublicKey(bytes32 x, bytes32 y)\n public\n pure\n returns (bytes memory)\n {\n return EcdsaLib.compressPublicKey(x, y);\n }\n}\n"
182
+ "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.9;\n\nimport \"../bridge/EcdsaLib.sol\";\n\n// TODO: Rename to EcdsLibStub\n/// @dev This is a contract implemented to test EcdsaLib library directly.\ncontract TestEcdsaLib {\n function compressPublicKey(bytes32 x, bytes32 y)\n public\n pure\n returns (bytes memory)\n {\n return EcdsaLib.compressPublicKey(x, y);\n }\n}\n"
183
+ },
184
+ "contracts/bridge/Heartbeat.sol": {
185
+ "content": "// SPDX-License-Identifier: MIT\n\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ██████████████ ▐████▌ ██████████████\n// ██████████████ ▐████▌ ██████████████\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n// ▐████▌ ▐████▌\n\npragma solidity ^0.8.9;\n\nimport {BytesLib} from \"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\";\n\n/// @title Bridge wallet heartbeat\n/// @notice The library establishes expected format for heartbeat messages\n/// signed by wallet ECDSA signing group. Heartbeat messages are\n/// constructed in such a way that they can not be used as a Bitcoin\n/// transaction preimages.\n/// @dev The smallest Bitcoin non-coinbase transaction is a one spending an\n/// OP_TRUE anyonecanspend output and creating 1 OP_TRUE anyonecanspend\n/// output. Such a transaction has 61 bytes (see `BitcoinTx` documentation):\n/// 4 bytes for version\n/// 1 byte for tx_in_count\n/// 36 bytes for tx_in.previous_output\n/// 1 byte for tx_in.script_bytes (value: 0)\n/// 0 bytes for tx_in.signature_script\n/// 4 bytes for tx_in.sequence\n/// 1 byte for tx_out_count\n/// 8 bytes for tx_out.value\n/// 1 byte for tx_out.pk_script_bytes\n/// 1 byte for tx_out.pk_script\n/// 4 bytes for lock_time\n///\n///\n/// The smallest Bitcoin coinbase transaction is a one creating\n/// 1 OP_TRUE anyonecanspend output and having an empty coinbase script.\n/// Such a transaction has 65 bytes:\n/// 4 bytes for version\n/// 1 byte for tx_in_count\n/// 32 bytes for tx_in.hash (all 0x00)\n/// 4 bytes for tx_in.index (all 0xff)\n/// 1 byte for tx_in.script_bytes (value: 0)\n/// 4 bytes for tx_in.height\n/// 0 byte for tx_in.coinbase_script\n/// 4 bytes for tx_in.sequence\n/// 1 byte for tx_out_count\n/// 8 bytes for tx_out.value\n/// 1 byte for tx_out.pk_script_bytes\n/// 1 byte for tx_out.pk_script\n/// 4 bytes for lock_time\n///\n///\n/// A SIGHASH flag is used to indicate which part of the transaction is\n/// signed by the ECDSA signature. There are currently 3 flags:\n/// SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, and different combinations\n/// of these flags.\n///\n/// No matter the SIGHASH flag and no matter the combination, the following\n/// fields from the transaction are always included in the constructed\n/// preimage:\n/// 4 bytes for version\n/// 36 bytes for tx_in.previous_output (or tx_in.hash + tx_in.index for coinbase)\n/// 4 bytes for lock_time\n///\n/// Additionally, the last 4 bytes of the preimage determines the SIGHASH\n/// flag.\n///\n/// This is enough to say there is no way the preimage could be shorter\n/// than 4 + 36 + 4 + 4 = 48 bytes.\n///\n/// For this reason, we construct the heartbeat message, as a 16-byte\n/// message. The first 8 bytes are 0xffffffffffffffff. The last 8 bytes\n/// are for an arbitrary uint64, being a signed heartbeat nonce (for\n/// example, the last Ethereum block hash).\nlibrary Heartbeat {\n using BytesLib for bytes;\n\n /// @notice Determines if the signed byte array is a valid, non-fraudulent\n /// heartbeat message.\n /// @param message Message signed by the wallet. It is a potential heartbeat\n /// message, Bitcoin transaction preimage, or an arbitrary signed\n /// bytes\n /// @dev Wallet heartbeat message must be exactly 16 bytes long with the first\n /// 8 bytes set to 0xffffffffffffffff.\n /// @return True if valid heartbeat message, false otherwise.\n function isValidHeartbeatMessage(bytes calldata message)\n internal\n pure\n returns (bool)\n {\n if (message.length != 16) {\n return false;\n }\n\n if (message.slice8(0) != 0xffffffffffffffff) {\n return false;\n }\n\n return true;\n }\n}\n"
186
+ },
187
+ "contracts/test/HeartbeatStub.sol": {
188
+ "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.9;\n\nimport \"../bridge/Heartbeat.sol\";\n\n/// @dev This is a contract implemented to test Heartbeat library directly.\ncontract HeartbeatStub {\n function isValidHeartbeatMessage(bytes calldata message)\n public\n pure\n returns (bool)\n {\n return Heartbeat.isValidHeartbeatMessage(message);\n }\n}\n"
183
189
  },
184
190
  "contracts/test/TestERC721.sol": {
185
191
  "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.9;\n\nimport \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\n\ncontract TestERC721 is ERC721 {\n string public constant NAME = \"Test ERC721 Token\";\n string public constant SYMBOL = \"TT\";\n\n constructor() ERC721(NAME, SYMBOL) {}\n\n function mint(address to, uint256 tokenId) public {\n _mint(to, tokenId);\n }\n}\n"
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../build-info/5f59c349153ae55f504ed2bdc8fbe33f.json"
3
+ "buildInfo": "../../build-info/013e70f95a1ea637f765500cfd16d8d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/5f59c349153ae55f504ed2bdc8fbe33f.json"
3
+ "buildInfo": "../../../build-info/013e70f95a1ea637f765500cfd16d8d4.json"
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/5f59c349153ae55f504ed2bdc8fbe33f.json"
3
+ "buildInfo": "../../../build-info/013e70f95a1ea637f765500cfd16d8d4.json"
4
4
  }
@@ -3,8 +3,8 @@
3
3
  "contractName": "BitcoinTx",
4
4
  "sourceName": "contracts/bridge/BitcoinTx.sol",
5
5
  "abi": [],
6
- "bytecode": "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220e1498540550babd8d2a0a091ecdb878efa78c955de17a3dcf7feab07b64a43ff64736f6c63430008090033",
7
- "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220e1498540550babd8d2a0a091ecdb878efa78c955de17a3dcf7feab07b64a43ff64736f6c63430008090033",
6
+ "bytecode": "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220cd2c40d1456f625da802b418785b7ad8428472a904c1782ddef8b4c5e01bd35b64736f6c63430008090033",
7
+ "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220cd2c40d1456f625da802b418785b7ad8428472a904c1782ddef8b4c5e01bd35b64736f6c63430008090033",
8
8
  "linkReferences": {},
9
9
  "deployedLinkReferences": {}
10
10
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "_format": "hh-sol-dbg-1",
3
- "buildInfo": "../../../build-info/5f59c349153ae55f504ed2bdc8fbe33f.json"
3
+ "buildInfo": "../../../build-info/013e70f95a1ea637f765500cfd16d8d4.json"
4
4
  }